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;