import { createConnection, Connection } from "mysql"; import { conf } from "./ConfigHelper"; /** * Database helper * * @author Pierre HUBERT */ export enum JoinType { NORMAL, LEFT } export interface JoinTableInfo { table: string, tableAlias ?: string, condition: string } export interface QueryInformation { table: string, tableAlias?: string, joinType ?: JoinType, joins ?: Array, fields ?: Array, where ?: Object, customWhere ?: string, customWhereArgs ?: Array, groupBy ?: string, order ?: string, limit ?: number, } export interface UpdateInformation { table: string, set: Object, customWhere ?: string, customWhereArgs ?: Array, where ?: Object, } export interface CountQueryInformation { table: string, where ?: Object, customWhere ?: string, customWhereArgs ?: Array } export class DatabaseHelper { private static connection : Connection; /** * Connect to database */ static async connect() { this.connection = createConnection({ host: conf().database.host, user: conf().database.user, password: conf().database.password, database: conf().database.dbName }); await new Promise((resolve, reject) => { this.connection.connect(err => { if(err) { console.error("Could not connect to database !"); console.error(err); reject(err); return; } console.info("Connected to database."); resolve(); }); }) } /** * Get an instance of the connection to the database */ static getConnection() : Connection { return this.connection; } /** * Query the database (SELECT) * * @param info Information about the query */ static async Query(info: QueryInformation) : Promise> { // Prepare SQL request let request = "SELECT "; // Requested fields request += info.fields ? info.fields.join(",") : "*"; request += " FROM " + info.table; if(info.tableAlias) request += " " + info.tableAlias + " "; // Joins condition if(info.joins) { if(info.joinType == JoinType.LEFT) request += " LEFT "; info.joins.forEach(join => { request += " JOIN " + join.table + (join.tableAlias ? " " + join.tableAlias : "") + " ON " + join.condition }); } let args = []; // Add where arguments if(info.where) { request += " WHERE "; for(const k in info.where) { if(!info.where.hasOwnProperty(k)) continue; const v = info.where[k].toString(); request += k; request += v.startsWith("%") || v.endsWith("%") ? " LIKE " : " = " request += "? AND " args.push(v); }; // Remove the last (useless) AND request = request.substr(0, request.length - 4) } // Add custom WHERE clause if(info.customWhere) { if(!info.where) request += " WHERE " + info.customWhere + " "; else request += " AND (" + info.customWhere + ")"; if(info.customWhereArgs) info.customWhereArgs.forEach((e) => args.push(e)); } // Group by clause (if any) if(info.groupBy) { request += " GROUP BY " + info.groupBy + " "; } // Order (if any) if(info.order) request += " ORDER BY " + info.order + " "; // Limit (if any) if(info.limit) request += " LIMIT " + info.limit; // Execute request return await new Promise((resolve, reject) => { this.connection.query( request, args, (err, result, fields) => { if(err) { reject(err); return; } resolve(result); }); }); } /** * Query a single row on the database * * @param info Information about the request * @returns First matching row / null if none found */ static async QueryRow(info : QueryInformation) : Promise { info.limit = 1; const result = await this.Query(info); if(result.length == 0) return null; return result[0]; } /** * Insert an new entry into the database * * @param info Information about the entry * @returns The ID of the inserted column (if any) */ static async InsertRow(table : string, values : any) : Promise { return new Promise((resolve, reject) => { this.connection.query("INSERT INTO " + table + " SET ?", values, (err, results, f) => { if(err) reject(err); else resolve(results.insertId); }); }); } /** * Perform update on the database * * @param info Information about the request * @returns The number of affected rows */ static async UpdateRows(info : UpdateInformation) : Promise { let sql = "UPDATE " + info.table + " SET "; let args = []; // Process updates let isFirst = true; for (const key in info.set) { if (info.set.hasOwnProperty(key)) { const value = info.set[key]; if(!isFirst) sql += ", "; else isFirst = false; sql += key + " = ? " args.push(value); } } // Process conditions isFirst = true; if(info.where) { sql += " WHERE "; for (const key in info.where) { if (info.where.hasOwnProperty(key)) { const value = info.where[key]; if(!isFirst) sql += " AND "; else isFirst = false; sql += key + " = ? " args.push(value); } } } // Security : block unconditionned updates else if(!info.customWhere) throw Error("Error : Updates without conditions are blocked for security!"); // Process custom conditions if(info.customWhere) { if(info.where) sql += " AND (" + info.customWhere + ") "; else sql += " WHERE " + info.customWhere + " "; if(info.customWhereArgs) info.customWhereArgs.forEach(e => args.push(e)); } // Execute request return await new Promise((resolve, reject) => { this.connection.query(sql, args, (err, results, f) => { if(err){ reject(err); return; } resolve(results.affectedRows); }) }); } /** * Delete entries from a table * * @param table Target table * @param where Where arguments */ static async DeleteRows(table: string, where: any) { let whereArgs = ""; let args = []; // Process conditions for (const key in where) { if (where.hasOwnProperty(key)) { const value = where[key]; whereArgs += (whereArgs == "" ? "" : " AND ") + key + " = ?"; args.push(value); } } if(whereArgs == "") throw Error("Error : table could accidentally get purged!"); return new Promise((resolve, reject) => { this.connection.query("DELETE FROM " + table + " WHERE " + whereArgs, args, (err, r, f) => { if(err) reject(err) else resolve(); }); }) } /** * Perform a COUNT query on the database * * @param info Information about the count query */ static async Count(info: CountQueryInformation) : Promise { let sql = "SELECT COUNT(*) as count FROM " + info.table; let args = []; if(info.where) { sql += " WHERE "; for (const key in info.where) { if (info.where.hasOwnProperty(key)) { const value = info.where[key]; sql += "AND " + key + " = ? "; args.push(value); } } sql = sql.replace("WHERE AND", "WHERE"); } if(info.customWhere) { if(info.where) sql += " AND (" + info.customWhere + ")"; else sql += "WHERE " + info.customWhere; if(info.customWhereArgs) info.customWhereArgs.forEach(e => args.push(e)); } return await new Promise((r, e) => { this.connection.query(sql, args, (err, results, f) => { if(err){ e(err); return; } r(results[0].count); }) }); } }