import type {IDbCategory} from "@interfaces/database/IDbCategory"; import type {IDbModel} from "@interfaces/database/IDbModel"; import type {IDbUser} from "@interfaces/database/IDbUser"; import type {IDbBrand} from "@interfaces/database/IDbBrand"; import mysql, {type Connection, type ConnectionOptions} from 'mysql2'; import {Logger} from "tslog"; const access: ConnectionOptions = { host: `${process.env["MYSQL_HOST"]}`, port: Number.parseInt(`${process.env["MYSQL_PORT"]}`), user: `${process.env["MYSQL_USER"]}`, database: `${process.env["MYSQL_DATABASE"]}`, password: `${process.env["MYSQL_PASS"]}` }; class MysqlHandler { private readonly handlerName: string; private Logger: Logger private Connection: Connection; constructor(handlerName?: string) { this.handlerName = handlerName || 'Unknown'; this.Logger = new Logger({ name: `DB>> ${this.handlerName}` }); this.Connection = mysql.createConnection(access); this.Connection.connect((err) => { if (err) { this.Logger.error(`Error connecting to MySQL: ${err}`); throw new Error() } this.Logger.info(`Connected to MySQL database (${access.database})`); }); } closeConnection() { this.Connection.end(); }; query(queryString: string) { return new Promise((resolve, reject) => { this.Connection.query(queryString, (err, results) => { if (err) { this.Logger.error(`Error executing query: ${err}`); reject(err); } else { resolve(results); } }); }); } execute(queryString: string, values: Array): Promise { return new Promise((resolve, reject) => { this.Connection.execute(queryString, values, (err: mysql.QueryError | null, results: mysql.QueryResult) => { if (err) { this.Logger.error(`Error executing query: ${err}`); reject(err); } else { resolve(results); } }); }); } /** * Unprepare a previously prepared SQL query. * * @param {string} queryString * - The SQL query string to unprepare. * @return {Promise} * - A promise that resolves if the unprepare operation is successful, * or rejects with an error if there was an error unpreparing the query. */ unprepare(queryString: string): Promise { return new Promise((resolve, reject) => { try { resolve(this.Connection.unprepare(queryString)); } catch (err) { reject(err) } }); } } const MySqlService = { Handler : MysqlHandler, User: { /** * Insert a user into the database. * * @param {MysqlHandler} handler - The MySQL database handler. * @param {IDbUser} data - The user data to insert. * @returns {Promise} A promise that resolves if the user was inserted successfully, or rejects with an error. * @throws {Error} If an error occurs while executing the query. */ insert(handler: MysqlHandler, data: IDbUser): Promise { return new Promise((resolve, reject) => { if (!data.id) return reject('Id is undefined'); if (data.id.length !== 36) return reject('Id invalid'); const _sql = "INSERT INTO `users`(`id`,`username`, `firstname`, `lastname`, `dob`, `email`, `is_mail_verified`, `is_admin`, `gdpr`, `hash`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" const _values = [ data.id, data.username, data.firstname, data.lastname, data.dob, data.email, data.is_mail_verified, data.is_admin, data.gdpr, data.hash ] try { resolve(handler.execute(_sql, _values) as unknown) } catch (err: unknown) { reject(err as Error); } }) }, /** * Updates user data in the database. * @param {MysqlHandler} handler - The MySQL handler object. * @param {IDbUser} data - The user data to be updated. * @returns {Promise} - A promise that resolves when the update is successful. * @throws {Error} - If an error occurs during the update process. */ update(handler: MysqlHandler, data: IDbUser): Promise { return new Promise((resolve, reject) => { if (!data.id) return reject('Id is undefined'); if (data.id.length !== 36) return reject('Id invalid'); try { const _template = ` ${data.username ? "`username` = ?," : null} ${data.firstname ? "`firstname` = ?," : null} ${data.lastname ? "`lastname` = ?," : null} ${data.dob ? "`dob` = ?," : null} ${data.email ? "`email` = ?," : null} ${data.is_mail_verified ? "`is_mail_verified` = ?," : null} ${data.is_admin ? "`is_admin` = ?," : null} ${data.gdpr ? "`gdpr` = ?," : null} ${data.hash ? "`hash` = ?" : null}` const _values = [ data.username, data.firstname, data.lastname, data.dob, data.email, data.is_mail_verified, data.is_admin, data.gdpr, data.hash, data.id ] const _sql = `UPDATE "users" SET ${_template} WHERE 'id' = ?`; return resolve(handler.execute(_sql, _values)); } catch (err: unknown) { reject(err as Error); } }); }, /** * Retrieves a user from the database based on their ID. * * @param {MysqlHandler} handler - The MySQL handler object used to execute the query. * @param {string} userId - The ID of the user to retrieve from the database. * * @return {Promise} - A promise that resolves with the retrieved user object (of type IDbUser). * - Rejects with an error if the ID is invalid or if an error occurs during the database operation. */ getById(handler: MysqlHandler, userId: string): Promise { return new Promise((resolve, reject) => { if (userId.length !== 36) return reject('Id invalid'); const _sql = "SELECT * FROM `users` WHERE `id` = ?"; const _values = [userId]; try { resolve(handler.execute(_sql, _values) as unknown as IDbUser); } catch (err: unknown) { reject(err as Error); } }); }, /** * Retrieves all users from the database. * * @param {MysqlHandler} handler - The MySQL handler object used to query the database. * @return {Promise>} - A promise that resolves with an array of user objects from the database. * @throws {Error} - If an error occurs while querying the database. */ getAll(handler: MysqlHandler): Promise> { return new Promise((resolve, reject) => { const _sql = "SELECT * FROM `users`"; try { resolve(handler.query(_sql) as unknown as Array); } catch (err: unknown) { reject(err as Error); } }); }, /** * Retrieves a user from the database by email. * * @param {MysqlHandler} handler - The MySQL database handler instance. * @param {string} email - The email of the user to retrieve. * @return {Promise} - A promise that resolves to the retrieved user object. * @throws {Error} - If an error occurs while retrieving the user. */ getByEmail(handler: MysqlHandler, email: string): Promise { return new Promise((resolve, reject) => { if (!email) return reject('email is undefined') const _sql = "SELECT * FROM `users` WHERE `email` = ?"; const _values = [email]; try { resolve(handler.execute(_sql, _values) as unknown as IDbUser); } catch (err: unknown) { reject(err as Error); } }); }, /** * Retrieves the admin state for a given user ID. * * @param {MysqlHandler} handler - The MySQL handler object used for executing the query. * @param {string} userId - The ID of the user to get the admin state for. * @return {Promise} A Promise that resolves to a boolean value indicating whether the user is an admin or not. * @throws {Error} if an error occurs during the execution of the query. */ getAdminStateForId(handler: MysqlHandler, userId: string) : Promise { return new Promise((resolve, reject) => { const _sql = "SELECT `is_admin` FROM `users` WHERE `id` = ?"; const _values = [userId]; try { const isAdmin = handler.execute(_sql, _values) isAdmin.then((result) => { if (result !== true) return resolve(false); return resolve(true) }); } catch (err: unknown) { reject(err as Error); } }); }, /** * Deletes a user from the database. * @param {MysqlHandler} handler - The MySQL handler object. * @param {string} userId - The ID of the user to delete. * @return {Promise} - A Promise that resolves when the deletion is successful, or rejects with an error. */ delete(handler: MysqlHandler, userId: string): Promise { return new Promise((resolve, reject) => { if (!userId) return reject('Id is undefined'); if (userId.length !== 36) return reject('Id invalid'); const _sql = "DELETE FROM `users` WHERE `id` = ?"; const _values = [userId]; try { resolve(handler.execute(_sql, _values)); } catch (err: unknown) { reject(err as Error); } }); } }, Brand: { /** * Inserts a record into the `brands` table. * * @param {MysqlHandler} handler - The MySQL handler instance. * @param {IDbBrand} data - The data object representing the record to be inserted. * @returns {Promise} A promise that resolves with the result of the insertion. * The promise is rejected with an error if the insertion fails. */ insert(handler: MysqlHandler, data: IDbBrand): Promise { return new Promise((resolve, reject) => { if (!data.id) return reject('Id is undefined'); if (data.id.length !== 36) return reject('Id invalid'); const _sql = "INSERT INTO `brands`(`id`,`display_name`, `slug_name`, `image_blob`) VALUES (?, ?, ?, ?)" const _values = [ data.id, data.display_name, data.slug_name, data.image_blob ] try { resolve(handler.execute(_sql, _values)) } catch (err: unknown) { reject(err as Error); } }) }, /** * Updates a brand in the database. * @param {MysqlHandler} handler - The MySQL handler. * @param {IDbBrand} data - The brand data to be updated. * @returns {Promise} - A promise that resolves with the number of affected rows in the database. * @throws {Error} - If an error occurs during the update process. */ update(handler: MysqlHandler, data: IDbBrand): Promise { return new Promise((resolve, reject) => { if (!data.id) return reject('Id is undefined'); if (data.id.length !== 36) return reject('Id invalid'); try { const _template = ` ${data.slug_name ? "`slug_name` = ?," : null} ${data.display_name ? "`display_name` = ?," : null} ${data.image_blob ? "`slug_name` = ?," : null}` const _values = [ data.slug_name, data.display_name, data.image_blob, data.id ] const _sql = `UPDATE "brands" SET ${_template} WHERE 'id' = ?`; return resolve(handler.execute(_sql, _values) as unknown as number); } catch (err: unknown) { reject(err as Error); } }) }, /** * Retrieves all records from the `brands` table. * * @param {MysqlHandler} handler - The MySQL handler object to execute the query. * * @return {Promise>} A promise that resolves to an array of IDbBrand objects representing the retrieved records. If an error occurs during the query execution, the promise will be rejected with an Error object. */ getAll(handler: MysqlHandler): Promise> { return new Promise((resolve, reject) => { const _sql = "SELECT * FROM `brands`"; try { resolve(handler.query(_sql) as unknown as Array); } catch (err: unknown) { reject(err as Error); } }) }, /** * Fetches a category by its Id * * @param {MysqlHandler} handler - The instance of the MysqlHandler used for executing the query * @param {string} brandId - The Id of the category to be fetched * @return {Promise} - A promise that, when resolved, returns the category matching the Id * @throws {Error} - If an error occurs during execution * @throws {string} - If brandId is undefined or invalid */ getById(handler: MysqlHandler, brandId: string): Promise { return new Promise((resolve, reject) => { if (!brandId) return reject('Id is undefined') if (brandId.length !== 36) return reject('Id invalid'); const _sql = "SELECT * FROM `brands` WHERE `id` = ?"; const _values = [brandId]; try { resolve(handler.execute(_sql, _values) as unknown as IDbBrand); } catch (err: unknown) { //TODO Reject with null and logger error reject(err as Error); } }) }, /** * Retrieves a brand from the database by its slug. * * @param {MysqlHandler} handler - The MySQL handler object used to connect to the database. * @param {string} brandSlug - The slug of the brand to retrieve. * @returns {Promise} - A promise that resolves with the brand object if found, or rejects with an error message. */ getBySlug(handler: MysqlHandler, brandSlug: string): Promise { return new Promise((resolve, reject) => { if (!brandSlug) return reject('slug is undefined') const _sql = "SELECT * FROM `brands` WHERE `slug_name` = ?"; const _values = [brandSlug]; try { resolve(handler.execute(_sql, _values) as unknown as IDbBrand); } catch (err: unknown) { reject(err as Error); } }) }, /** * Deletes a brand from the database. * * @param {MysqlHandler} handler - The MySQL handler object used to interact with the database. * @param {string} brandId - The ID of the brand to be deleted. * * @return {Promise} - A Promise that resolves with the result of the deletion operation. * If the deletion is successful, the Promise resolves with the result. * If an error occurs, the Promise rejects with the error. * * @throws {Error} If the brandId is undefined or invalid. */ delete(handler: MysqlHandler, brandId: string): Promise { //TODO check if has models linked before actions return new Promise((resolve, reject) => { if (!brandId) return reject('Id is undefined'); if (brandId.length !== 36) return reject('Id invalid'); const _sql = "DELETE FROM `brands` WHERE `id` = ?"; const _values = [brandId]; try { resolve(handler.execute(_sql, _values)); } catch (err: unknown) { reject(err as Error); } }) } //TODO Get models of the brand }, Model: { /** * Retrieves all records from the 'models' table. * * @param {MysqlHandler} handler - The MySQL handler object used to execute the query. * @return {Promise>} - A promise that resolves to an array of IDbModel objects representing the retrieved records from the 'models' table. * @throws {Error} - If there is an error executing the query. */ getAll(handler: MysqlHandler): Promise> { return new Promise((resolve, reject) => { const _sql = "SELECT * FROM `models`"; try { resolve(handler.query(_sql) as unknown as Array); } catch (err: unknown) { reject(err as Error); } }); }, /** * Retrieves a database model by slug from a given MySQL handler. * * @param {MysqlHandler} handler - The MySQL handler instance. * @param {string} modelSlug - The slug of the model to retrieve. * @return {Promise} A promise that resolves with the retrieved model. * @throws {Error} If there was an error executing the query. */ getBySlug(handler: MysqlHandler, modelSlug: string): Promise { return new Promise((resolve, reject) => { const _sql = "SELECT * FROM `models` WHERE `slug_name` = ?"; const _values = [modelSlug]; try { resolve(handler.execute(_sql, _values) as unknown as IDbModel); } catch (err: unknown) { reject(err as Error); } }); }, /** * Retrieves a model by its ID from the database. * * @param {MysqlHandler} handler - The MySQL handler object used to execute the SQL query. * @param {string} modelId - The ID of the model to retrieve. * @return {Promise} - A promise that resolves with the retrieved model, or rejects with an error. */ getById(handler: MysqlHandler, modelId: string): Promise { return new Promise((resolve, reject) => { const _sql = "SELECT * FROM `models` WHERE `id` = ?"; const _values = [modelId]; try { resolve(handler.execute(_sql, _values) as unknown as IDbModel); } catch (err: unknown) { reject(err as Error); } }); }, /** * Inserts a new record into the `models` table. * * @param {MysqlHandler} handler - The MySQL handler instance. * @param {IDbModel} data - The data to be inserted. * @returns {Promise} - A Promise that resolves with the execution result or rejects with an error. */ insert(handler: MysqlHandler, data: IDbModel): Promise { return new Promise((resolve, reject) => { if (!data.id) return reject('Id is undefined'); if (data.id.length !== 36) return reject('Id invalid'); const _sql = "INSERT INTO `models`(`slug_name`,`display_name`, `brand_id`, `category_id`, `image_blob`, `is_trending`, `base_price`, `id`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" const _values = [ data.slug_name, data.display_name, data.brand_id, data.category_id, data.image_blob, data.is_trending, data.base_price, data.id ] try { resolve(handler.execute(_sql, _values)) } catch (err: unknown) { reject(err as Error); } }) } }, Category: { /** * Inserts a category into the database. * * @param {MysqlHandler} handler - The MySQL handler object. * @param {IDbCategory} data - The category data to be inserted. * @return {Promise} - A promise that resolves if the insertion is successful, and rejects with an error if it fails. */ insert(handler: MysqlHandler, data: IDbCategory): Promise { return new Promise((resolve, reject) => { if (!data.id) return reject('Id is undefined'); if (data.id.length !== 36) return reject('Id invalid'); const _sql = "INSERT INTO `categories`(`id`,`slug_name`, `display_name`) VALUES (?, ?, ?)" const _values = [ data.id, data.slug_name, data.display_name ] try { resolve(handler.execute(_sql, _values)) } catch (err: unknown) { reject(err as Error); } }) }, /** * Updates a category in the database. * * @param {MysqlHandler} handler - The MySQL handler instance. * @param {IDbCategory} data - The category data to update. * @returns {Promise} - A promise that resolves with the number of affected rows in the database. * @throws {Error} - If an error occurs during execution. */ update(handler: MysqlHandler, data: IDbCategory): Promise { return new Promise((resolve, reject) => { if (!data.id) return reject('Id is undefined'); if (data.id.length !== 36) return reject('Id invalid'); try { const _template = ` ${data.slug_name ? "`slug_name` = ?," : null} ${data.display_name ? "`display_name` = ?," : null}` const _values = [ data.slug_name, data.display_name, data.id ] const _sql = `UPDATE "categories" SET ${_template} WHERE 'id' = ?`; return resolve(handler.execute(_sql, _values) as unknown as number); } catch (err: unknown) { reject(err as Error); } }) }, /** * Retrieves all categories from the database. * * @param {MysqlHandler} handler - The MySQL handler used for executing the query. * * @return {Promise>} - A promise that resolves with an array of category objects from the database. * - The category objects are of type IDbCategory. * - If an error occurs, the promise will be rejected with an Error object. */ getAll(handler: MysqlHandler): Promise> { return new Promise((resolve, reject) => { const _sql = "SELECT * FROM `categories`"; try { resolve(handler.query(_sql) as unknown as Array); } catch (err: unknown) { reject(err as Error); } }); }, /** * Retrieves a category from the database by its slug. * * @param {MysqlHandler} handler - The MySQL handler object to execute the query. * @param {string} categorySlug - The slug of the category to retrieve. * @returns {Promise} - A promise that resolves with the retrieved category object. * @throws {Error} - If an error occurs while executing the query. */ getBySlug(handler: MysqlHandler, categorySlug: string): Promise { return new Promise((resolve, reject) => { if (!categorySlug) return reject('slug is undefined') const _sql = "SELECT * FROM `categories` WHERE `slug_name` = ?"; const _values = [categorySlug]; try { resolve(handler.execute(_sql, _values) as unknown as IDbCategory); } catch (err: unknown) { reject(err as Error); } }) }, /** * Retrieves a category from the database by its ID. * * @param {MysqlHandler} handler - The MySQL handler object used for execution. * @param {string} categoryId - The ID of the category to retrieve. * @returns {Promise} - A Promise that resolves with the category object. * @throws {Error} - If an error occurs during execution. */ getById(handler: MysqlHandler, categoryId: string): Promise { return new Promise((resolve, reject) => { if (!categoryId) return reject('slug is undefined') if (categoryId.length !== 36) return reject('Id invalid'); const _sql = "SELECT * FROM `categories` WHERE `id` = ?"; const _values = [categoryId]; try { resolve(handler.execute(_sql, _values) as unknown as IDbCategory); } catch (err: unknown) { reject(err as Error); } }); }, delete(handler: MysqlHandler, categoryId: string) { return new Promise((resolve, reject) => { if (!categoryId) return reject('Id is undefined'); if (categoryId.length !== 36) return reject('Id invalid'); const _sql = "DELETE FROM `categories` WHERE `id` = ?"; const _values = [categoryId]; try { resolve(handler.execute(_sql, _values)); } catch (err: unknown) { reject(err as Error); } }) } //TODO Get models in category } } export default MySqlService