From 8e14bf77b453d5a3c5a77c42c8430ffe158d57d4 Mon Sep 17 00:00:00 2001 From: Mathis Date: Thu, 23 May 2024 12:31:57 +0200 Subject: [PATCH] refactor: reorganize code and enhance readability Code imports rearranged and updated for better consistency. Missing semicolons were added in multiple files to improve code readability and standards compliance. The usage of whitespace and indentation was also standardized across multiple files to improve overall code clarity. --- biome.json | 2 +- src/app.ts | 4 +- src/controllers/auth.controller.ts | 111 ++--- src/interfaces/db/mariadb.interface.ts | 34 +- src/interfaces/db/mongodb.types.ts | 37 ++ src/interfaces/services/register.types.ts | 18 +- src/middlewares/authentication.middleware.ts | 72 ++-- src/routers/auth.router.ts | 7 +- .../authentication/intcode.service.ts | 14 +- src/services/authentication/jwt.service.ts | 12 +- .../authentication/register.service.ts | 66 +-- src/services/databases/databases.service.ts | 382 +++++++++++++++--- src/services/databases/mariadb.service.ts | 29 +- src/services/databases/mongodb.service.ts | 14 +- src/services/users.service.ts | 27 +- src/utils/converters.util.ts | 6 +- src/utils/headers.util.ts | 9 +- src/utils/logs.util.ts | 2 +- src/utils/validators.util.ts | 11 +- 19 files changed, 603 insertions(+), 254 deletions(-) create mode 100644 src/interfaces/db/mongodb.types.ts diff --git a/biome.json b/biome.json index d46d406..89eb673 100644 --- a/biome.json +++ b/biome.json @@ -18,7 +18,7 @@ "recommended": true, "performance": { "recommended": true, - "noDelete": "off", + "noDelete": "off" }, "suspicious": { "noExplicitAny": "warn" diff --git a/src/app.ts b/src/app.ts index 5f3213f..9e3ac27 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,11 +1,11 @@ import * as process from "node:process"; +import AuthRouter from "@routers/auth.router"; import { EnvUtils } from "@utils/env.util"; import { LogsUtils } from "@utils/logs.util"; import compression from "compression"; import cors from "cors"; import express, { type Express } from "express"; import helmet from "helmet"; -import AuthRouter from "@routers/auth.router"; console.log("\n\n> Starting...\n\n\n\n"); @@ -61,4 +61,4 @@ try { } catch (error) { logger.error(`Server failed to start: ${error}`); process.exit(1); -} \ No newline at end of file +} diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 293fb6e..90f8025 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -1,95 +1,108 @@ -import {Request, Response} from "express"; -import {LogsUtils} from "@utils/logs.util"; -import {HttpStatusCode} from "axios"; -import {IRegisterInput} from "@interfaces/services/register.types"; -import ValidatorsUtils from "@utils/validators.util"; -import UsersService from "@services/users.service"; -import RegisterService from "@services/authentication/register.service"; -import {HeaderUtil} from "@utils/headers.util"; +import type { UserInDatabase } from "@interfaces/db/mariadb.interface"; +import type { IRegisterInput } from "@interfaces/services/register.types"; import JwtService from "@services/authentication/jwt.service"; -import {JWTPayload} from "jose"; -import {UserInDatabase} from "@interfaces/db/mariadb.interface"; +import RegisterService from "@services/authentication/register.service"; +import UsersService from "@services/users.service"; import ConvertersUtils from "@utils/converters.util"; +import { HeaderUtil } from "@utils/headers.util"; +import { LogsUtils } from "@utils/logs.util"; +import ValidatorsUtils from "@utils/validators.util"; +import { HttpStatusCode } from "axios"; +import type { Request, Response } from "express"; +import type { JWTPayload } from "jose"; -const logs = new LogsUtils('AuthController') +const logs = new LogsUtils("AuthController"); async function registerController(req: Request, res: Response) { const body = req.body; if (!body.username || !body.email || !body.password) { - logs.warn('Missing required fields', req.ip); - return res.status(HttpStatusCode.BadRequest).json({ error: 'Missing required fields' }); + logs.warn("Missing required fields", req.ip); + return res + .status(HttpStatusCode.BadRequest) + .json({ error: "Missing required fields" }); } if (!ValidatorsUtils.isEmail(body.email)) { - logs.warn('Invalid email format', req.ip); - return res.status(HttpStatusCode.BadRequest).json({ error: 'Invalid email format' }); + logs.warn("Invalid email format", req.ip); + return res + .status(HttpStatusCode.BadRequest) + .json({ error: "Invalid email format" }); } if (!ValidatorsUtils.isUsername(body.username)) { - logs.warn('Invalid username format', req.ip); - return res.status(HttpStatusCode.BadRequest).json({ error: 'Invalid username format' }); + logs.warn("Invalid username format", req.ip); + return res + .status(HttpStatusCode.BadRequest) + .json({ error: "Invalid username format" }); } if (!ValidatorsUtils.isPassword(body.password)) { - logs.warn('Invalid password format', req.ip); - return res.status(HttpStatusCode.BadRequest).json({ error: 'Invalid password format' }); + logs.warn("Invalid password format", req.ip); + return res + .status(HttpStatusCode.BadRequest) + .json({ error: "Invalid password format" }); } const UserIfExist = { byEmail: await UsersService.get.byEmail(body.email), - byUsername: await UsersService.get.byUsername(body.username) - } + byUsername: await UsersService.get.byUsername(body.username), + }; if (UserIfExist.byEmail.length > 0 || UserIfExist.byUsername.length > 0) { - logs.warn('User already exists', req.ip); - return res.status(HttpStatusCode.Found).json({ error: 'User already exists' }); + logs.warn("User already exists", req.ip); + return res + .status(HttpStatusCode.Found) + .json({ error: "User already exists" }); } const data: IRegisterInput = { username: body.username, - email: body.email, - password: body.password - } + email: body.email, + password: body.password, + }; if (body.displayName) data.displayName = body.displayName; - const registerResult = await RegisterService(data) + const registerResult = await RegisterService(data); if (!registerResult.success) { - logs.error(registerResult.message, req.ip); - return res.status(HttpStatusCode.InternalServerError).json({ error: registerResult.message }); - } + logs.error(registerResult.message, req.ip); + return res + .status(HttpStatusCode.InternalServerError) + .json({ error: registerResult.message }); + } //TODO Image handling - - logs.info('User registered successfully', req.ip); + logs.info("User registered successfully", req.ip); return res.status(HttpStatusCode.Created).json({ - message: 'User registered successfully', + message: "User registered successfully", token: registerResult.token, - id: registerResult.id + id: registerResult.id, }); } async function GetSelfController(req: Request, res: Response) { - const originToken = await HeaderUtil.getToken(req) as string; - const token = await JwtService.verify(originToken) as JWTPayload; - const userInDatabases = await UsersService.get.byId(token.sub as string) as Array; - if (userInDatabases.length === 0 || !userInDatabases[0]) { - logs.warn('User not found', req.ip); - return res.status(HttpStatusCode.Gone).json({ error: 'User not found' }); - } - const User = userInDatabases[0] - return res.status(HttpStatusCode.Found).json({ + const originToken = (await HeaderUtil.getToken(req)) as string; + const token = (await JwtService.verify(originToken)) as JWTPayload; + const userInDatabases = (await UsersService.get.byId( + token.sub as string, + )) as Array; + if (userInDatabases.length === 0 || !userInDatabases[0]) { + logs.warn("User not found", req.ip); + return res.status(HttpStatusCode.Gone).json({ error: "User not found" }); + } + const User = userInDatabases[0]; + return res.status(HttpStatusCode.Found).json({ id: User.id, username: User.username, - email: User.email, - displayName: User.display_name, + email: User.email, + displayName: User.display_name, isAdmin: ConvertersUtils.bufferToBool(User.admin as Buffer), followers: await UsersService.get.followers(User.id), - following: await UsersService.get.following(User.id), + following: await UsersService.get.following(User.id), }); } const AuthController = { register: registerController, - me: GetSelfController -} + me: GetSelfController, +}; -export default AuthController; \ No newline at end of file +export default AuthController; diff --git a/src/interfaces/db/mariadb.interface.ts b/src/interfaces/db/mariadb.interface.ts index 61a4fdb..5dac529 100644 --- a/src/interfaces/db/mariadb.interface.ts +++ b/src/interfaces/db/mariadb.interface.ts @@ -1,21 +1,21 @@ export interface UserInDatabase { - id: string, - username: string, - display_name: string, - email: string, - iat?: number, - uat?: number, - deactivated?: ArrayBuffer, - hash: string, - email_activation?: number | null, - admin?: ArrayBuffer, - avatar_id?: string | null, - gdpr?: number + id: string; + username: string; + display_name: string; + email: string; + iat?: number; + uat?: number; + deactivated?: ArrayBuffer; + hash: string; + email_activation?: number | null; + admin?: ArrayBuffer; + avatar_id?: string | null; + gdpr?: number; } export interface FollowInDatabase { - id: string, - source_id: string, - target_id: string, - iat?: number -} \ No newline at end of file + id: string; + source_id: string; + target_id: string; + iat?: number; +} diff --git a/src/interfaces/db/mongodb.types.ts b/src/interfaces/db/mongodb.types.ts new file mode 100644 index 0000000..5fe5196 --- /dev/null +++ b/src/interfaces/db/mongodb.types.ts @@ -0,0 +1,37 @@ +import type { WithId } from "mongodb"; + +export interface IPost extends WithId { + id: string; + authorId: string; + message: string; + visibility: ContentVisibility; + image?: string; + upvote?: IUpVote[]; + metadata: IMetadata; +} + +export interface IComment { + id: string; + parentId?: string; + authorId: string; + message: string; + metadata: IMetadata; +} + +export interface IUpVote { + sourceId: string; + iat: number; +} + +export interface IMetadata { + iat: number; + uat?: number; + reported?: boolean; +} + +export enum ContentVisibility { + public = 0, + followers = 1, + friend = 2, + hidden = 3, +} diff --git a/src/interfaces/services/register.types.ts b/src/interfaces/services/register.types.ts index e3fe094..2717e9d 100644 --- a/src/interfaces/services/register.types.ts +++ b/src/interfaces/services/register.types.ts @@ -1,13 +1,13 @@ export interface IRegisterInput { - username: string, - displayName?: string, - email: string, - password: string + username: string; + displayName?: string; + email: string; + password: string; } export interface IRegisterOutput { - success: boolean, - message: string - id?: string, - token?: string -} \ No newline at end of file + success: boolean; + message: string; + id?: string; + token?: string; +} diff --git a/src/middlewares/authentication.middleware.ts b/src/middlewares/authentication.middleware.ts index 043261b..a69a8c6 100644 --- a/src/middlewares/authentication.middleware.ts +++ b/src/middlewares/authentication.middleware.ts @@ -1,60 +1,82 @@ -import type {NextFunction, Request, Response} from "express"; +import type { UserInDatabase } from "@interfaces/db/mariadb.interface"; import JwtService from "@services/authentication/jwt.service"; -import {DatabasesService} from "@services/databases/databases.service"; -import {UserInDatabase} from "@interfaces/db/mariadb.interface"; -import {HttpStatusCode} from "axios"; -import {HeaderUtil} from "@utils/headers.util"; +import { DatabasesService } from "@services/databases/databases.service"; +import { HeaderUtil } from "@utils/headers.util"; +import { HttpStatusCode } from "axios"; +import type { NextFunction, Request, Response } from "express"; -const db = new DatabasesService('OnlyDevs'); +const db = new DatabasesService("OnlyDevs"); async function UserMiddleware(req: Request, res: Response, next: NextFunction) { const originToken = await HeaderUtil.getToken(req); if (!originToken) { - return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "Unauthorized" }); } const tokenPayload = await JwtService.verify(`${originToken}`); if (!tokenPayload || !tokenPayload.sub) { - return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "Unauthorized" }); } - const UserFound = await db.getUserById(tokenPayload.sub) - const User: UserInDatabase | undefined = UserFound[0] as UserInDatabase + const UserFound = await db.getUserById(tokenPayload.sub); + const User: UserInDatabase | undefined = UserFound[0] as UserInDatabase; if (!User) { - return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized, you dont exist." }); + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "Unauthorized, you dont exist." }); } if (User.email_activation) { - return res.status(HttpStatusCode.Forbidden).json({ message: "You should verify your email first."}) + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "You should verify your email first." }); } - return next() + return next(); } -async function AdminMiddleware(req: Request, res: Response, next: NextFunction) { +async function AdminMiddleware( + req: Request, + res: Response, + next: NextFunction, +) { const originToken = await HeaderUtil.getToken(req); if (!originToken) { - return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "Unauthorized" }); } const tokenPayload = await JwtService.verify(`${originToken}`); if (!tokenPayload || !tokenPayload.sub) { - return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "Unauthorized" }); } - const UserFound = await db.getUserById(tokenPayload.sub) - const User: UserInDatabase | undefined = UserFound[0] as UserInDatabase + const UserFound = await db.getUserById(tokenPayload.sub); + const User: UserInDatabase | undefined = UserFound[0] as UserInDatabase; if (!User) { - return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized, you dont exist." }); + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "Unauthorized, you dont exist." }); } if (User.email_activation) { - return res.status(HttpStatusCode.Forbidden).json({ message: "You should verify your email first."}) + return res + .status(HttpStatusCode.Forbidden) + .json({ message: "You should verify your email first." }); } - const adminState = User.admin + const adminState = User.admin; console.log(adminState); if (!adminState) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized, you are not an admin." }); - } - return next() + return res + .status(HttpStatusCode.PreconditionRequired) + .json({ message: "Unauthorized, you are not an admin." }); + } + return next(); } export const AuthMiddleware = { user: UserMiddleware, admin: AdminMiddleware, -} \ No newline at end of file +}; diff --git a/src/routers/auth.router.ts b/src/routers/auth.router.ts index 78d5a1f..6475969 100644 --- a/src/routers/auth.router.ts +++ b/src/routers/auth.router.ts @@ -1,7 +1,6 @@ import AuthController from "@controllers/auth.controller"; -import express, {type Router} from "express"; -import {AuthMiddleware} from "src/middlewares/authentication.middleware"; - +import express, { type Router } from "express"; +import { AuthMiddleware } from "src/middlewares/authentication.middleware"; const AuthRouter: Router = express.Router(); @@ -9,4 +8,4 @@ const AuthRouter: Router = express.Router(); AuthRouter.route("/register").post(AuthController.register); AuthRouter.route("/me").get(AuthMiddleware.user, AuthController.me); -export default AuthRouter; \ No newline at end of file +export default AuthRouter; diff --git a/src/services/authentication/intcode.service.ts b/src/services/authentication/intcode.service.ts index b8742ab..ca20cd8 100644 --- a/src/services/authentication/intcode.service.ts +++ b/src/services/authentication/intcode.service.ts @@ -1,11 +1,11 @@ const IntCodeService = { generate: () => { const a = Math.floor(Math.random() * Date.now()); - const b = a.toString().replace(/0/g, ''); - const c = b.split('') - const code = c.join('').slice(0, 6).toString() - return Number.parseInt(code) - } -} + const b = a.toString().replace(/0/g, ""); + const c = b.split(""); + const code = c.join("").slice(0, 6).toString(); + return Number.parseInt(code); + }, +}; -export default IntCodeService; \ No newline at end of file +export default IntCodeService; diff --git a/src/services/authentication/jwt.service.ts b/src/services/authentication/jwt.service.ts index 6acf8c0..dda664c 100644 --- a/src/services/authentication/jwt.service.ts +++ b/src/services/authentication/jwt.service.ts @@ -1,14 +1,14 @@ +import { EnvUtils } from "@utils/env.util"; +import { LogsUtils } from "@utils/logs.util"; import { type JWTHeaderParameters, type JWTPayload, SignJWT, jwtVerify, } from "jose"; -import {LogsUtils} from "@utils/logs.util"; -import {EnvUtils} from "@utils/env.util"; -const logs = new LogsUtils('JwtService') -const envs = new EnvUtils('JwtService') +const logs = new LogsUtils("JwtService"); +const envs = new EnvUtils("JwtService"); /** * Verify a JWT token. @@ -24,7 +24,7 @@ async function JwtVerifyService( try { const result = await jwtVerify( jwt, - new TextEncoder().encode(`${envs.get('JWT_SECRET')}`), + new TextEncoder().encode(`${envs.get("JWT_SECRET")}`), {}, ); return result.payload; @@ -61,7 +61,7 @@ async function JwtSignService( .setIssuer(`OnlyDevs`) .setAudience(audience) .setExpirationTime(expTime) - .sign(new TextEncoder().encode(`${envs.get('JWT_SECRET')}`)); + .sign(new TextEncoder().encode(`${envs.get("JWT_SECRET")}`)); } logs.debug("Service loaded."); diff --git a/src/services/authentication/register.service.ts b/src/services/authentication/register.service.ts index d3b1404..21b3bf2 100644 --- a/src/services/authentication/register.service.ts +++ b/src/services/authentication/register.service.ts @@ -1,47 +1,55 @@ -import {IRegisterInput, IRegisterOutput} from "@interfaces/services/register.types"; -import {UserInDatabase} from "@interfaces/db/mariadb.interface"; +import type { UserInDatabase } from "@interfaces/db/mariadb.interface"; +import type { + IRegisterInput, + IRegisterOutput, +} from "@interfaces/services/register.types"; import CredentialService from "@services/authentication/credentials.service"; import IntCodeService from "@services/authentication/intcode.service"; -import {v4} from "uuid"; -import {DatabasesService} from "@services/databases/databases.service"; import JwtService from "@services/authentication/jwt.service"; +import { DatabasesService } from "@services/databases/databases.service"; +import { v4 } from "uuid"; -const db = new DatabasesService('OnlyDevs') +const db = new DatabasesService("OnlyDevs"); //TODO Logs async function registerService(data: IRegisterInput): Promise { - const uuid = v4().toString() + const uuid = v4().toString(); const User: UserInDatabase = { id: `${uuid}`, username: data.username, display_name: data.displayName || data.username, - hash: await CredentialService.hash(data.password), - email: data.email, - email_activation: IntCodeService.generate() - } + hash: await CredentialService.hash(data.password), + email: data.email, + email_activation: IntCodeService.generate(), + }; - const dbResult = await db.insertUser(User) + const dbResult = await db.insertUser(User); if (dbResult) { //await sendActivationEmail(User.email, User.email_activation); - const token = await JwtService.sign({ - sub: uuid, - iat: Date.now(), - }, { - alg: "HS256" - }, '7d', 'Registered user') - return { - success: true, - message: "User registered successfully", + const token = await JwtService.sign( + { + sub: uuid, + iat: Date.now(), + }, + { + alg: "HS256", + }, + "7d", + "Registered user", + ); + return { + success: true, + message: "User registered successfully", id: User.id, - token: token - }; - } else { - return { - success: false, - message: "Failed to register user", - }; - } + token: token, + }; + } else { + return { + success: false, + message: "Failed to register user", + }; + } } -export default registerService; \ No newline at end of file +export default registerService; diff --git a/src/services/databases/databases.service.ts b/src/services/databases/databases.service.ts index 2b99ce1..e1733d9 100644 --- a/src/services/databases/databases.service.ts +++ b/src/services/databases/databases.service.ts @@ -1,7 +1,11 @@ -import {MariadbService} from "@services/databases/mariadb.service"; -//import {MongodbService} from "@services/databases/mongodb.service"; -import {LogsUtils} from "@utils/logs.util"; -import {FollowInDatabase, UserInDatabase} from "@interfaces/db/mariadb.interface"; +import type { + FollowInDatabase, + UserInDatabase, +} from "@interfaces/db/mariadb.interface"; +import type { IComment, IPost } from "@interfaces/db/mongodb.types"; +import { MariadbService } from "@services/databases/mariadb.service"; +import { MongodbService } from "@services/databases/mongodb.service"; +import { LogsUtils } from "@utils/logs.util"; interface MariaDbStatusResult { fieldCount: number; @@ -16,92 +20,356 @@ interface MariaDbStatusResult { export class DatabasesService { private readonly _appName; private readonly maria: MariadbService; - //private readonly mongo: MongodbService; + private readonly mongo: MongodbService; private readonly logs: LogsUtils; constructor(appName?: string) { - this._appName = appName || 'App'; - this.maria = new MariadbService(this._appName + ' DbInteractions'); - //this.mongo = new MongodbService(this._appName + ' DbInteractions'); - this.logs = new LogsUtils(this._appName + ' DbInteractions'); + this._appName = appName || "App"; + this.maria = new MariadbService(`${this._appName} DbInteractions`); + this.mongo = new MongodbService(`${this._appName} DbInteractions`); + this.logs = new LogsUtils(`${this._appName} DbInteractions`); } async getAllUsers() { - const result = await this.maria.query("SELECT * FROM users") as unknown as Array; - this.logs.debug('Fetching users from database...', `${result?.length} user(s) found.`) - return result; + const result = (await this.maria.query( + "SELECT * FROM users", + )) as unknown as Array; + this.logs.debug( + "Fetching users from database...", + `${result?.length} user(s) found.`, + ); + return result; } async getUserById(id: string) { - const result = await this.maria.execute(`SELECT * FROM users WHERE id = ?`, [id]) as unknown as Array; - this.logs.debug(`Fetching user with id ${id} from database...`, `${result?.length} user(s) found.`); - return result; - } + const result = (await this.maria.execute( + "SELECT * FROM users WHERE id = ?", + [id], + )) as unknown as Array; + this.logs.debug( + `Fetching user with id ${id} from database...`, + `${result?.length} user(s) found.`, + ); + return result; + } async getUserByUsername(username: string) { - const result = await this.maria.execute(`SELECT * FROM users WHERE username = ?`, [username]) as unknown as Array; - this.logs.debug(`Fetching user with username "${username}" from database...`, `${result?.length} user(s) found.`); - return result; + const result = (await this.maria.execute( + "SELECT * FROM users WHERE username = ?", + [username], + )) as unknown as Array; + this.logs.debug( + `Fetching user with username "${username}" from database...`, + `${result?.length} user(s) found.`, + ); + return result; } async getUserByEmail(email: string) { - const result = await this.maria.execute(`SELECT * FROM users WHERE email = ?`, [email]) as unknown as Array; - this.logs.debug(`Fetching user with email "${email}" from database...`, `${result?.length} user(s) found.`); - return result; + const result = (await this.maria.execute( + "SELECT * FROM users WHERE email = ?", + [email], + )) as unknown as Array; + this.logs.debug( + `Fetching user with email "${email}" from database...`, + `${result?.length} user(s) found.`, + ); + return result; } async getFollowingById(id: string) { - const result = await this.maria.execute(`SELECT * FROM follows WHERE source_id = ?`, [id]) as unknown as Array; - this.logs.debug(`Fetching followed users for user with id ${id} from database...`, `${result?.length} user(s) found.`); - return result; + const result = (await this.maria.execute( + "SELECT * FROM follows WHERE source_id = ?", + [id], + )) as unknown as Array; + this.logs.debug( + `Fetching followed users for user with id ${id} from database...`, + `${result?.length} user(s) found.`, + ); + return result; } - async getFollowersById(id:string) { - const result = await this.maria.execute(`SELECT * FROM follows WHERE target_id = ?`, [id]) as unknown as Array; - this.logs.debug(`Fetching followers for user with id ${id} from database...`, `${result?.length} user(s) found.`); - return result; + async getFollowersById(id: string) { + const result = (await this.maria.execute( + "SELECT * FROM follows WHERE target_id = ?", + [id], + )) as unknown as Array; + this.logs.debug( + `Fetching followers for user with id ${id} from database...`, + `${result?.length} user(s) found.`, + ); + return result; } async insertUser(user: UserInDatabase): Promise { const factorized = await this.maria.factorize({ values: user, - actionName: 'Inserting a user', - throwOnError: true + actionName: "Inserting a user", + throwOnError: true, }); //TODO if no id stop const valuesArray = factorized._valuesArray; - const questionMarks = factorized._questionMarksFields + const questionMarks = factorized._questionMarksFields; const keysArray = factorized._keysTemplate; - const _sql = `INSERT INTO users (${keysArray}) VALUES (${questionMarks})` + const _sql = `INSERT INTO users (${keysArray}) VALUES (${questionMarks})`; try { - const result = await this.maria.execute(_sql, valuesArray) as unknown as MariaDbStatusResult - this.logs.debug(`Inserted new user with id ${user.id}`, `Rows affected: ${result.affectedRows}`); - return true + const result = (await this.maria.execute( + _sql, + valuesArray, + )) as unknown as MariaDbStatusResult; + this.logs.debug( + `Inserted new user with id ${user.id}`, + `Rows affected: ${result.affectedRows}`, + ); + return true; } catch (err) { - this.logs.softError('An error occurred.', err) - return false + this.logs.softError("An error occurred.", err); + return false; } - } + } //ToTest async editUser(userId: string, data: object): Promise { const factorized = await this.maria.factorize({ - values: data, - actionName: 'Editing a user', - throwOnError: true - }); - const valuesArray = factorized._valuesArray; - const keysArray = factorized._keysTemplate.split(','); - const setFields = keysArray.map((key) => `${key} = ?`).join(', '); - const _sql = `UPDATE users SET ${setFields} WHERE id = ?`; - valuesArray.push(userId); - try { - const result = await this.maria.execute(_sql, valuesArray) as unknown as MariaDbStatusResult; - this.logs.debug(`Edited user with id ${userId}`, `Rows affected: ${result.affectedRows}`); - return result; - } catch (err) { - this.logs.softError('An error occurred.', err); - return {}; - } + values: data, + actionName: "Editing a user", + throwOnError: true, + }); + const valuesArray = factorized._valuesArray; + const keysArray = factorized._keysTemplate.split(","); + const setFields = keysArray.map((key) => `${key} = ?`).join(", "); + const _sql = `UPDATE users SET ${setFields} WHERE id = ?`; + valuesArray.push(userId); + try { + const result = (await this.maria.execute( + _sql, + valuesArray, + )) as unknown as MariaDbStatusResult; + this.logs.debug( + `Edited user with id ${userId}`, + `Rows affected: ${result.affectedRows}`, + ); + return result; + } catch (err) { + this.logs.softError("An error occurred.", err); + return {}; + } + } + //ToTest + async createFollow(sourceId: string, targetId: string): Promise { + const _sql = "INSERT INTO follows (source_id, target_id) VALUES (?, ?)"; + try { + const result = (await this.maria.execute(_sql, [ + sourceId, + targetId, + ])) as unknown as MariaDbStatusResult; + this.logs.debug( + `Created follow relationship between user with id ${sourceId} and user with id ${targetId}`, + `Rows affected: ${result.affectedRows}`, + ); + return true; + } catch (err) { + this.logs.softError("An error occurred.", err); + return false; + } + } + //ToTest + async deleteFollow(sourceId: string, targetId: string): Promise { + const _sql = "DELETE FROM follows WHERE source_id = ? AND target_id = ?"; + try { + const result = (await this.maria.execute(_sql, [ + sourceId, + targetId, + ])) as unknown as MariaDbStatusResult; + this.logs.debug( + `Deleted follow relationship between user with id ${sourceId} and user with id ${targetId}`, + `Rows affected: ${result.affectedRows}`, + ); + return true; + } catch (err) { + this.logs.softError("An error occurred.", err); + return false; + } + } + //ToTest + async deleteUser(userId: string): Promise { + const _sql = "DELETE FROM users WHERE id = ?"; + try { + const result = (await this.maria.execute(_sql, [ + userId, + ])) as unknown as MariaDbStatusResult; + this.logs.debug( + `Deleted user with id ${userId}`, + `Rows affected: ${result.affectedRows}`, + ); + return true; + } catch (err) { + this.logs.softError("An error occurred.", err); + return false; + } + } + + async createPost(data: IPost): Promise { + try { + const result = await this.mongo.use().collection("posts").insertOne(data); + if (result.acknowledged) { + return data.id; + } + return false; + } catch (err) { + this.logs.softError("Error when inserting a post.", err); + return false; + } + } + + async getPostById(id: string): Promise { + try { + const result = (await this.mongo + .use() + .collection("posts") + .findOne({ id })) as unknown as IPost; + if (!result) return false; + return result; + } catch (err) { + this.logs.softError("Error when fetching a post.", err); + return false; + } + } + + async getPostsByUserId(userId: string): Promise { + try { + const result = (await this.mongo + .use() + .collection("posts") + .find({ userId }) + .toArray()) as unknown as IPost[]; + return result; + } catch (err) { + this.logs.softError("Error when fetching posts by user id.", err); + return []; + } + } + + async createComment(data: IComment): Promise { + try { + const result = await this.mongo + .use() + .collection("comments") + .insertOne(data); + if (result.acknowledged) { + return data.id; + } + return false; + } catch (err) { + this.logs.softError("Error when inserting a comment.", err); + return false; + } + } + + async getCommentsByPostId(postId: string): Promise { + try { + const result = (await this.mongo + .use() + .collection("comments") + .find({ postId }) + .toArray()) as unknown as IComment[]; + return result; + } catch (err) { + this.logs.softError("Error when fetching comments by post id.", err); + return []; + } + } + + async getPostsWithCommentsByUserId( + userId: string, + ): Promise> { + try { + const posts = await this.getPostsByUserId(userId); + const postIds = posts.map((post) => post.id); + const comments = await Promise.all( + postIds.map((postId) => this.getCommentsByPostId(postId)), + ); + const result = posts.map((post, index) => ({ + post, + comments: comments[index], + })); + //Log the result + + return result; + } catch (err) { + this.logs.softError( + "Error when fetching posts with comments by user id.", + err, + ); + return []; + } + } + + async deleteComment(commentId: string): Promise { + try { + const result = await this.mongo + .use() + .collection("comments") + .deleteOne({ id: commentId }); + this.logs.debug( + `Deleted comment with id ${commentId}`, + `Rows affected: ${result.deletedCount}`, + ); + return true; + } catch (err) { + this.logs.softError("An error occurred.", err); + return false; + } + } + + async deletePost(postId: string): Promise { + try { + const result = await this.mongo + .use() + .collection("posts") + .deleteOne({ id: postId }); + this.logs.debug( + `Deleted post with id ${postId}`, + `Rows affected: ${result.deletedCount}`, + ); + return true; + } catch (err) { + this.logs.softError("An error occurred.", err); + return false; + } + } + + async getFollowersCountById(id: string) { + const result = (await this.maria.execute( + "SELECT COUNT(*) FROM follows WHERE target_id = ?", + [id], + )) as unknown as number; + this.logs.debug( + `Fetching followers count for user with id ${id} from database...`, + `${result} follower(s) found.`, + ); + return result; + } + + async getFollowingCountById(id: string) { + const result = (await this.maria.execute( + "SELECT COUNT(*) AS count FROM follows WHERE source_id = ?", + [id], + )) as unknown as Array<{ count: number }>; + this.logs.debug( + `Fetching count of followed users for user with id ${id} from database...`, + `${result?.[0]?.count} user(s) found.`, + ); + return result?.[0]?.count || 0; + } + + async getMostFollowedUser() { + const _sql = "SELECT target_id, COUNT(*) AS count FROM follows GROUP BY target_id ORDER BY count DESC LIMIT 3"; + const result = (await this.maria.query(_sql)) as unknown as Array<{ target_id: string; count: number }>; + this.logs.debug( + "Fetching most followed users from database...", + `${result?.length} user(s) found.`, + ); + } } -export const justForTesting = new DatabasesService('OnlyDevs') \ No newline at end of file +export const justForTesting = new DatabasesService("OnlyDevs"); diff --git a/src/services/databases/mariadb.service.ts b/src/services/databases/mariadb.service.ts index 2fb26bb..3a9eae9 100644 --- a/src/services/databases/mariadb.service.ts +++ b/src/services/databases/mariadb.service.ts @@ -1,10 +1,12 @@ +import type { + ISqlFactorizeInput, + ISqlFactorizeOutput, +} from "@interfaces/sqlfactorizer.interface"; +import { EnvUtils } from "@utils/env.util"; +import { LogsUtils } from "@utils/logs.util"; import mysql, { type Connection } from "mysql2"; -import {LogsUtils} from "@utils/logs.util"; -import {EnvUtils} from "@utils/env.util"; -import {ISqlFactorizeInput, ISqlFactorizeOutput} from "@interfaces/sqlfactorizer.interface"; export class MariadbService { - private envs: EnvUtils; private logs: LogsUtils; private readonly contextName: string; @@ -16,17 +18,17 @@ export class MariadbService { this.contextName = contextName || "Unknown"; this.logs = new LogsUtils(this.contextName); this.Connection = mysql.createConnection({ - host: `${this.envs.get('MYSQL_HOST')}`, - port: Number.parseInt(`${this.envs.get('MYSQL_PORT')}`), - user: `${this.envs.get('MYSQL_USERNAME')}`, - database: `${this.envs.get('MYSQL_DATABASE')}`, - password: `${this.envs.get('MYSQL_PASSWORD')}`, + host: `${this.envs.get("MYSQL_HOST")}`, + port: Number.parseInt(`${this.envs.get("MYSQL_PORT")}`), + user: `${this.envs.get("MYSQL_USERNAME")}`, + database: `${this.envs.get("MYSQL_DATABASE")}`, + password: `${this.envs.get("MYSQL_PASSWORD")}`, }); this.Connection.connect((err) => { if (err) { this.logs.error(`Error connecting to MySQL:`, err); } - this.logs.debug('Connected to MariaDB', this.envs.get('MYSQL_DATABASE')) + this.logs.debug("Connected to MariaDB", this.envs.get("MYSQL_DATABASE")); }); } closeConnection() { @@ -70,12 +72,13 @@ export class MariadbService { ); const values = Object.values(data.values).map((val) => val.toString()); this.logs.debug( - `Factorized ${_sqlQueryKeys.length} keys for a prepare Query.`, `Action: ${data.actionName}`, + `Factorized ${_sqlQueryKeys.length} keys for a prepare Query.`, + `Action: ${data.actionName}`, ); if (_id.length > 0) { this.logs.trace(`Id post-pushed in factorized data.`, _id); values.push(_id); - _sqlQueryKeys.push('id') + _sqlQueryKeys.push("id"); } const sqlQueryKeys = _sqlQueryKeys.join(", "); @@ -141,4 +144,4 @@ export class MariadbService { } }); } -} \ No newline at end of file +} diff --git a/src/services/databases/mongodb.service.ts b/src/services/databases/mongodb.service.ts index a1af8ca..8b007d6 100644 --- a/src/services/databases/mongodb.service.ts +++ b/src/services/databases/mongodb.service.ts @@ -10,14 +10,14 @@ export class MongodbService { this.envs = new EnvUtils(`MongoDB >> ${contextName}`); this.logs = new LogsUtils(`MongoDB >> ${contextName}`); try { - const uri = `mongodb://${this.envs.get("MONGO_USERNAME")}:${this.envs.get("MONGO_PASSWORD")}@localhost:${this.envs.get("MONGO_PORT")}/`; + const uri = `mongodb://${this.envs.get("MONGO_USERNAME")}:${this.envs.get( + "MONGO_PASSWORD", + )}@localhost:${this.envs.get("MONGO_PORT")}/`; this.logs.trace("MongoDB URI:", uri); this.client = new MongoClient(uri); - this.client - .connect() - .then(() => { - this.logs.debug("Connected to MongoDB", 'All databases'); - }) + this.client.connect().then(() => { + this.logs.debug("Connected to MongoDB", "All databases"); + }); } catch (error) { this.logs.error(`Error connecting to MongoDB:`, error); throw new Error(); @@ -29,7 +29,7 @@ export class MongodbService { return this.client.db(`${this.envs.get("MONGO_DATABASE")}`); } catch (err) { this.logs.error("Error using MongoDB:", err); - throw err; + throw err; } } diff --git a/src/services/users.service.ts b/src/services/users.service.ts index b379f14..96f77dc 100644 --- a/src/services/users.service.ts +++ b/src/services/users.service.ts @@ -1,30 +1,29 @@ -import {DatabasesService} from "@services/databases/databases.service"; +import { DatabasesService } from "@services/databases/databases.service"; - -const db = new DatabasesService('OnlyDevs') +const db = new DatabasesService("OnlyDevs"); async function getByEmail(email: string) { - return await db.getUserByEmail(email) + return await db.getUserByEmail(email); } async function getByUsername(username: string) { - return await db.getUserByUsername(username) + return await db.getUserByUsername(username); } async function getAll() { - return db.getAllUsers() + return db.getAllUsers(); } async function getById(id: string) { - return await db.getUserById(id) + return await db.getUserById(id); } async function getFollowers(id: string) { - return await db.getFollowersById(id) + return await db.getFollowersById(id); } async function getFollowing(id: string) { - return await db.getFollowingById(id) + return await db.getFollowingById(id); } const get = { @@ -33,11 +32,11 @@ const get = { byId: getById, all: getAll, followers: getFollowers, - following: getFollowing -} + following: getFollowing, +}; const UsersService = { - get -} + get, +}; -export default UsersService; \ No newline at end of file +export default UsersService; diff --git a/src/utils/converters.util.ts b/src/utils/converters.util.ts index dc96289..983ada3 100644 --- a/src/utils/converters.util.ts +++ b/src/utils/converters.util.ts @@ -6,7 +6,7 @@ const ConvertersUtils = { }, boolToBuffer: (bol: boolean) => { return new Uint8Array([bol ? 1 : 0]).buffer; - } -} + }, +}; -export default ConvertersUtils; \ No newline at end of file +export default ConvertersUtils; diff --git a/src/utils/headers.util.ts b/src/utils/headers.util.ts index 6bbd4c8..e8ab9ea 100644 --- a/src/utils/headers.util.ts +++ b/src/utils/headers.util.ts @@ -1,14 +1,13 @@ -import type {Request} from "express"; - +import type { Request } from "express"; async function getTokenFromHeader(req: Request) { const token: string | undefined = req.headers.authorization?.split(" ")[1]; - if (!token ||token.length <= 0) { - return false + if (!token || token.length <= 0) { + return false; } return token; } export const HeaderUtil = { getToken: getTokenFromHeader, -} \ No newline at end of file +}; diff --git a/src/utils/logs.util.ts b/src/utils/logs.util.ts index 2e976f7..c31fe6a 100644 --- a/src/utils/logs.util.ts +++ b/src/utils/logs.util.ts @@ -6,7 +6,7 @@ export class LogsUtils { constructor(contextName: string) { this.Logger = new Logger({ name: contextName, - prettyLogTimeZone: `local` + prettyLogTimeZone: `local`, }); } diff --git a/src/utils/validators.util.ts b/src/utils/validators.util.ts index 2180d66..a86bde9 100644 --- a/src/utils/validators.util.ts +++ b/src/utils/validators.util.ts @@ -1,7 +1,7 @@ const Validators = { isEmail: (value: string) => { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,32}$/; - return emailRegex.test(value) + return emailRegex.test(value); }, isUsername: (value: string) => { const usernameRegex = /^[a-zA-Z0-9._]{3,14}$/; @@ -10,9 +10,10 @@ const Validators = { //displayName isPassword: (value: string) => { - const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,32}$/; + const passwordRegex = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,32}$/; return passwordRegex.test(value); - } -} + }, +}; -export default Validators \ No newline at end of file +export default Validators;