298 lines
8.9 KiB
JavaScript

const initSqlJs = require('sql.js');
const fs = require('fs');
const { resourceUsage } = require('process');
let db = null;
const DB_PATH = './ka.db';
// Helper functions
function getNextKANumber() {
const result = db.exec('SELECT ka_number FROM articles ORDER BY id DESC LIMIT 1')
if (result.length > 0 && result[0].values.length > 0) {
const lastKANumber = result[0].values[0][0];
const number = parseInt(lastKANumber.replace('KA', '')) + 1;
return `KA${String(number).padStart(3, '0')}`;
}
return 'KA001';
}
async function initDb() {
const SQL = await initSqlJs();
console.log('Initializing Database...');
// Loading Database if it already exists
if (fs.existsSync(DB_PATH)) {
const buffer = fs.readFileSync(DB_PATH);
db = new SQL.Database(buffer);
console.log('Database Loaded')
} else {
// Creating a new one if it does not
console.log('Database not found. Creating new instance...')
db = new SQL.Database();
// Creating Knowledge Article table
db.run(`
CREATE TABLE articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ka_number TEXT UNIQUE,
title TEXT,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_by TEXT,
updated_at DATETIME,
updated_by TEXT
)
`);
db.run("CREATE INDEX idx_ka_number ON articles(ka_number)");
// Creating Users table
db.run(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
email TEXT UNIQUE NOT NULL,
pass_hash TEXT,
display_name TEXT,
auth_provider TEXT DEFAULT 'local',
entra_id TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
db.run("CREATE INDEX idx_email ON users(email)");
db.run("CREATE INDEX idx_entra_id ON users(entra_id)");
db.run("CREATE INDEX idx_username ON users(username)");
// Saving the created db to file
const data = db.export();
fs.writeFileSync(DB_PATH, Buffer.from(data));
console.log(`Database created at ${DB_PATH}`);
}
return db;
}
/**
* Gets all articles from the database
* @returns {[Object]} - returns an array of all stored articles
*/
function getAllArticles() {
const result = db.exec("SELECT * FROM articles ORDER BY created_at DESC");
if (result.length === 0) {
return [];
}
const columns = result[0].columns;
const rows = result[0].values;
const articles = rows.map(row => {
const article = {};
columns.forEach((col, index) => {
article[col] = row[index];
});
return article;
})
return articles;
}
/**
* Gets the article from the database associated with a KA number
* @param {string} ka_num - The KA number associated with an article
* @returns {Object} - the associated article from the database
*/
function getArticle(ka_num) {
const stmt = db.prepare('SELECT * FROM articles where ka_number = ?');
stmt.bind([ka_num]);
if (stmt.step()) {
const article = stmt.getAsObject();
stmt.free();
return article;
}
stmt.free();
return null;
}
/**
* Creates a new entry in the articles table, atomatically assigns KA number, and returns the newly created article
* @param {string} title - The title of the article
* @param {string} content - The content of the article
* @param {string} author - The username of the article creator
* @returns {Object} - The newly created article from the database
*/
function createArticle(title, content, author) {
const ka_num = getNextKANumber();
db.run(
"INSERT INTO articles (ka_number, title, content, created_by) VALUES (?, ?, ?, ?)",
[ka_num, title, content, author]
);
// Saving updated DB to file
const data = db.export();
fs.writeFileSync(DB_PATH, Buffer.from(data));
return getArticle(ka_num);
}
/**
* Updates the stored article that is associated with the KA number, and returns the updated article
* @param {string} ka_num - The KA number of the article beaing updated
* @param {string} title - The title of the article
* @param {string} content - The content of the article
* @param {string} author - The username that updated the article
* @returns {Object} - The newly updatd article from the database
*/
function updateArticle(ka_num, title, content, author) {
db.run(
"UPDATE articles SET title = ?, content = ?, updated_at = CURRENT_TIMESTAMP, updated_by = ? WHERE ka_number = ?",
[title, content, author, ka_num]
);
// Saving updated DB to file
const data = db.export();
fs.writeFileSync(DB_PATH, Buffer.from(data));
return getArticle(ka_num);
}
/**
* Deletes the associated article from the article table
* @param {*} ka_num - The KA number associated to the article
*/
function deleteArticle(ka_num) {
db.run(
"DELETE FROM articles WHERE ka_number = ?",
[ka_num]
);
// Saving updated DB to file
const data = db.export();
fs.writeFileSync(DB_PATH, Buffer.from(data));
}
/**
* Searches the article table for any words matching in the title, content, or ka number and returns an array of them
* @param {string} query - The search query that will be used for looking up the assocaited articles
* @returns {[Object]} - An arry of articles that match the query content
*/
function searchArticles(query) {
const searchTerm = `%${query}%`;
const stmt = db.prepare(
"SELECT * FROM articles WHERE title LIKE ? OR content LIKE ? or ka_number LIKE ? ORDER BY created_at DESC"
);
stmt.bind([searchTerm, searchTerm, searchTerm]);
const results = [];
while (stmt.step()) {
results.push(stmt.getAsObject());
}
stmt.free();
return results;
}
/**
* Creates a new user and returns the newly created user object from the database
* @param {string} username - The username for the newly created user
* @param {string} email - The email address for the newly created user
* @param {string} passHash - The hashed password for validation purposes
* @param {string} displayName - The name that will be desplayed when an article is created or updated
* @param {string} authProvider - the source of the authentication: 'local' or 'entra'
* @param {string} entraId - The ID number for the associated entra account, can be null if auth provider is local
* @returns {Object} - The user object of the newly created user
*/
function createUser(username, email, passHash, displayName, authProvider = 'local', entraId = null) {
db.run("INSERT INTO users (username, email, pass_hash, display_name, auth_provider, entra_id) VALUES (?, ?, ?, ?, ?, ?)",
[username, email, passHash, displayName, authProvider, entraId]
)
// Saving DB with newly created record
const data = db.export();
fs.writeFileSync(DB_PATH, Buffer.from(data));
const user = getUserByEmail(email);
return user;
}
/**
* Looks up users table and returns the user object for the user profile that matches the provided email
* @param {string} email - The email address used for the created user
* @returns {Object} - The matching user object from the database
*/
function getUserByEmail(email) {
const stmt = db.prepare("SELECT * FROM users WHERE email = ? LIMIT 1");
stmt.bind([email]);
if (stmt.step()) {
const user = stmt.getAsObject();
stmt.free();
return user;
}
stmt.free();
return null;
}
/**
* Looks up the user account with the specified username from the database and returns the user object
* @param {*} username - The username associated to the profile being looked up
* @returns {Object} - The user object that matches the provided username
*/
function getUserByUsername(username) {
const stmt = db.prepare("SELECT * FROM users WHERE username = ?");
stmt.bind([username]);
if (stmt.step()) {
const user = stmt.getAsObject();
stmt.free();
return user;
}
stmt.free();
return null;
}
/**
* Looks up the user by the primary key and returns the user object
* @param {Int} id - The id number of the associated user account
* @returns {Object} - The user object associated to the primary key provided
*/
function getUserById(id) {
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
stmt.bind([id]);
if (stmt.step()) {
const user = stmt.getAsObject();
stmt.free();
return user;
}
stmt.free();
return null;
}
module.exports = {
initDb,
getAllArticles,
getArticle,
createArticle,
updateArticle,
deleteArticle,
searchArticles,
createUser,
getUserByUsername,
getUserByEmail,
getUserById
};