From 1cbc77125149a948f22932f135334f1b05295e36 Mon Sep 17 00:00:00 2001 From: Mathis Date: Thu, 2 May 2024 15:29:19 +0200 Subject: [PATCH] feat(app): update debug syntax, refactor HTTP status codes - Updated import and usage of `isDebugMode` function across multiple controllers and services for better readability - Improved the use of HTTP status codes in `auth.controller.ts` for more accurate responses - Refactored HTTP status codes `HttpStatusCode` enum to include detailed comments Signed-off-by: Mathis --- src/app.ts | 10 +- src/controllers/auth.controller.ts | 99 ++++++----- src/controllers/brand.controller.ts | 2 +- src/controllers/category.controller.ts | 2 +- src/controllers/model.controller.ts | 2 +- src/interfaces/requests/HttpStatusCode.ts | 205 +++++++++++++++++++++- src/services/brand.service.ts | 2 +- src/services/category.service.ts | 2 +- src/services/jwt.service.ts | 2 +- src/services/model.service.ts | 2 +- src/services/mysql.service.ts | 21 ++- src/utils/debugState.ts | 5 +- 12 files changed, 287 insertions(+), 67 deletions(-) diff --git a/src/app.ts b/src/app.ts index a863a4d..853c69e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -52,11 +52,11 @@ try { try { app.listen(process.env["APP_PORT"]); logger.info( - `Server is running !\n >> Memory total: ${ - Math.round(process.memoryUsage().rss / 1_000_000) - } Mio\n >> Memory heap: ${ - Math.round(process.memoryUsage().heapUsed / 1_000_000) - } Mio\n`, + `Server is running !\n >> Memory total: ${Math.round( + process.memoryUsage().rss / 1_000_000, + )} Mio\n >> Memory heap: ${Math.round( + process.memoryUsage().heapUsed / 1_000_000, + )} Mio\n`, ); } catch (error) { logger.error(`Server failed to start: ${error}`); diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 89cb6a3..46a4052 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -1,22 +1,31 @@ import JwtService from "@services/jwt.service"; import type { IReqEditUserData } from "@interfaces/IReqEditUserData"; +import { HttpStatusCode } from "@interfaces/requests/HttpStatusCode"; import type { IReqRegister } from "@interfaces/requests/IReqRegister"; import UserService from "@services/user.service"; +import { isDebugMode } from "@utils/debugState"; import { isEmail } from "@utils/validators/email"; import type { Request, Response } from "express"; import { Logger } from "tslog"; -import {isDebugMode} from "@utils/debugState"; const logger = new Logger({ name: "AuthController", }); -async function registerUser(req: Request, res: Response): Promise { +/** + * Registers a user. + * + * @param {Request} req - The request object. + * @param {Response} res - The response object. + * + * @return {Promise} - A promise that resolves with the registration result or rejects with an error. + */ +async function registerUser(req: Request, res: Response): Promise { const body: IReqRegister = req.body; if (!body) { logger.warn(`Invalid input data (${req.ip})`); - return res.type("application/json").status(400).json({ + return res.type("application/json").status(HttpStatusCode.BadRequest).json({ error: "Invalid input data", }); } @@ -28,14 +37,14 @@ async function registerUser(req: Request, res: Response): Promise { !body.email ) { logger.warn(`Field(s) missing (${req.ip})`); - return res.type("application/json").status(400).json({ + return res.type("application/json").status(HttpStatusCode.BadRequest).json({ error: "Field(s) missing", }); } if (!isEmail(body.email)) { logger.warn(`Invalid email format (${req.ip})`); - return res.type("application/json").status(400).json({ + return res.type("application/json").status(HttpStatusCode.BadRequest).json({ error: "Invalid email format", }); } @@ -55,16 +64,19 @@ async function registerUser(req: Request, res: Response): Promise { const RegisterServiceResult = await UserService.register(sanitizeData); - if (RegisterServiceResult.error === "gdprNotApproved") { + if (typeof RegisterServiceResult !== 'string' && RegisterServiceResult.message === "GDPR acceptance is required.") { logger.warn(`GDPR not approved (${req.ip})`); - return res.status(400).json({ + return res.status(HttpStatusCode.BadRequest).json({ error: RegisterServiceResult.error, message: "GDPR not accepted.", }); } - if (typeof RegisterServiceResult !== 'string' && RegisterServiceResult.error === 5) { + if ( + typeof RegisterServiceResult !== "string" && + RegisterServiceResult.error === 5 + ) { logger.warn(`The user already exists (${sanitizeData.email})`); - return res.type("application/json").status(400).json({ + return res.type("application/json").status(HttpStatusCode.Conflict).json({ error: RegisterServiceResult.error, message: "The user already exists.", }); @@ -72,7 +84,10 @@ async function registerUser(req: Request, res: Response): Promise { // SUCCESS //logger.info(`User registered successfully (${sanitizeData.username})`); - return res.type("application/json").status(201).json({token:RegisterServiceResult}); + return res + .type("application/json") + .status(HttpStatusCode.Ok) + .json({ token: RegisterServiceResult }); } /** @@ -86,19 +101,19 @@ async function registerUser(req: Request, res: Response): Promise { async function loginUser(req: Request, res: Response): Promise { const body = req.body; if (!body) { - res.type("application/json").status(400).json({ + res.type("application/json").status(HttpStatusCode.BadRequest).json({ error: "Invalid input data", }); } - if (!body.password || !body.username) { + if (!body.password || !body.email) { logger.warn(`Field(s) missing (${req.ip})`); - res.type("application/json").status(400).json({ + res.type("application/json").status(HttpStatusCode.BadRequest).json({ error: "Field(s) missing", }); } const loginData = { - username: `${body.username}`, + email: `${body.email}`, password: `${body.password}`, }; console.log(body); @@ -107,13 +122,13 @@ async function loginUser(req: Request, res: Response): Promise { if (LoginServiceResult.error === "userNotFound") { console.log("POOL"); - res.type("application/json").status(404).json({ + res.type("application/json").status(HttpStatusCode.NotFound).json({ error: LoginServiceResult.error, message: "User not found.", }); } if (LoginServiceResult.error === "invalidPassword") { - res.type("application/json").status(401).json({ + res.type("application/json").status(HttpStatusCode.NotAcceptable).json({ error: LoginServiceResult.error, message: "Invalid password.", }); @@ -126,35 +141,35 @@ async function getAllUsers(req: Request, res: Response) { const bearerToken = authHeader?.split(" ")[1]; if (!bearerToken) { logger.warn(`Bearer token not provided (${req.ip})`); - return res.type("application/json").status(401).json({ - error: "Unauthorized", + return res.type("application/json").status(HttpStatusCode.Forbidden).json({ + error: "Invalid token", }); } const payload = await JwtService.verify(bearerToken); - if (!payload) { + if (!payload || !payload.sub) { logger.warn(`Unauthorized access attempt (${req.ip})`); - return res.type("application/json").status(401).json({ - error: "Unauthorized", + return res.type("application/json").status(HttpStatusCode.Forbidden).json({ + error: "Invalid token", }); } const sourceUser = await UserService.getFromId(payload.sub); if (!sourceUser) { - return res.type("application/json").status(404).json({ + return res.type("application/json").status(HttpStatusCode.ImATeapot).json({ error: "You dont exist anymore", }); } if (!sourceUser.is_admin) { - return res.type("application/json").status(403).json({ + return res.type("application/json").status(HttpStatusCode.Forbidden).json({ error: "Unauthorized", }); } const AllUserResponse = await UserService.getAll(); if (!AllUserResponse.users) { - return res.type("application/json").status(500).json({ + return res.type("application/json").status(HttpStatusCode.InternalServerError).json({ error: "Internal server error", }); } - return res.type("application/json").status(200).json(AllUserResponse); + return res.type("application/json").status(HttpStatusCode.Found).json(AllUserResponse); } async function getUser(req: Request, res: Response) { @@ -162,25 +177,25 @@ async function getUser(req: Request, res: Response) { const bearerToken = authHeader?.split(" ")[1]; if (!bearerToken) { logger.warn(`Bearer token not provided (${req.ip})`); - return res.type("application/json").status(401).json({ + return res.type("application/json").status(HttpStatusCode.Unauthorized).json({ error: "Unauthorized", }); } const payload = await JwtService.verify(bearerToken); - if (!payload) { + if (!payload || !payload.sub) { logger.warn(`Unauthorized access attempt (${req.ip})`); - return res.type("application/json").status(401).json({ + return res.type("application/json").status(HttpStatusCode.Unauthorized).json({ error: "Unauthorized", }); } const sourceUser = await UserService.getFromId(payload.sub); if (!sourceUser) { - return res.type("application/json").status(404).json({ + return res.type("application/json").status(HttpStatusCode.ImATeapot).json({ error: "You dont exist anymore", }); } - if (!sourceUser.is_admin) { - return res.type("application/json").status(403).json({ + if ("username" in sourceUser && !sourceUser.is_admin) { + return res.type("application/json").status(HttpStatusCode.Unauthorized).json({ error: "Unauthorized", }); } @@ -188,7 +203,7 @@ async function getUser(req: Request, res: Response) { const dbUser = await UserService.getFromId(userId); if (!dbUser) { logger.warn(`User not found (${req.ip})`); - return res.type("application/json").status(404).json({ + return res.type("application/json").status(HttpStatusCode.NotFound).json({ error: "User not found", }); } @@ -196,7 +211,7 @@ async function getUser(req: Request, res: Response) { delete dbUser.passwordHash; // @ts-ignore delete dbUser._id; - return res.type("application/json").status(200).json(dbUser); + return res.type("application/json").status(HttpStatusCode.Found).json(dbUser); } //FEAT - Implement re-auth by current password in case of password change @@ -216,7 +231,7 @@ async function editUser(req: Request, res: Response) { }); } const payload = await JwtService.verify(bearerToken); - if (!payload) { + if (!payload || !payload.sub) { logger.warn(`Unauthorized access attempt (${req.ip})`); return res.type("application/json").status(401).json({ error: "Unauthorized", @@ -373,24 +388,24 @@ async function getSelf(req: Request, res: Response) { }); } const payload = await JwtService.verify(bearerToken); - if (!payload) { + if (!payload || !payload.sub) { logger.warn(`Unauthorized access attempt (${req.ip})`); return res.type("application/json").status(401).json({ error: "Unauthorized", }); } - const dbUser = await UserService.getFromId(payload.sub); - if (!dbUser) { + const GetUserResult = await UserService.getFromId(payload.sub); + if (!GetUserResult) { return res.type("application/json").status(404).json({ error: "User not found", }); } return res.type("application/json").status(200).json({ - id: dbUser.id, - username: dbUser.username, - firstName: dbUser.firstname, - lastName: dbUser.firstname, - isAdmin: dbUser.firstname, + id: GetUserResult.id, + username: GetUserResult.username, + firstName: GetUserResult.firstname, + lastName: GetUserResult.firstname, + isAdmin: GetUserResult.firstname, }); } diff --git a/src/controllers/brand.controller.ts b/src/controllers/brand.controller.ts index ea32eb9..60d51b4 100644 --- a/src/controllers/brand.controller.ts +++ b/src/controllers/brand.controller.ts @@ -3,7 +3,7 @@ import { Logger } from "tslog"; import type IDbBrand from "@interfaces/database/IDbBrand"; import BrandService from "@services/brand.service"; -import {isDebugMode} from "@utils/debugState"; +import { isDebugMode } from "@utils/debugState"; //import {body} from "express-validator"; const logger = new Logger({ diff --git a/src/controllers/category.controller.ts b/src/controllers/category.controller.ts index 0d18547..b80e5ff 100644 --- a/src/controllers/category.controller.ts +++ b/src/controllers/category.controller.ts @@ -1,8 +1,8 @@ import type IDbCategory from "@interfaces/database/IDbCategory"; import CategoryService from "@services/category.service"; +import { isDebugMode } from "@utils/debugState"; import type { Request, Response } from "express"; import { Logger } from "tslog"; -import {isDebugMode} from "@utils/debugState"; //import {validationResult} from "express-validator"; const logger = new Logger({ diff --git a/src/controllers/model.controller.ts b/src/controllers/model.controller.ts index 8b8218b..1152d4a 100644 --- a/src/controllers/model.controller.ts +++ b/src/controllers/model.controller.ts @@ -1,9 +1,9 @@ import type IDbModel from "@interfaces/database/IDbModel"; import CategoryService from "@services/category.service"; import ModelService from "@services/model.service"; +import { isDebugMode } from "@utils/debugState"; import type { Request, Response } from "express"; import { Logger } from "tslog"; -import {isDebugMode} from "@utils/debugState"; //import {validationResult} from "express-validator"; const logger = new Logger({ diff --git a/src/interfaces/requests/HttpStatusCode.ts b/src/interfaces/requests/HttpStatusCode.ts index c0b535d..2265cbd 100644 --- a/src/interfaces/requests/HttpStatusCode.ts +++ b/src/interfaces/requests/HttpStatusCode.ts @@ -1,57 +1,258 @@ export enum HttpStatusCode { + /** + * Status code for a request indicating that the server received the request headers + * and the client should proceed to send the request body. + * @enum {number} + */ Continue = 100, + + /** + * Status code for a request indicating that the requester has asked the server to switch protocols. + * @enum {number} + */ SwitchingProtocols = 101, + + /** + * Status code for a request indicating that the server has received and is processing the request, + * but no response is available yet. + * @enum {number} + */ Processing = 102, + /** + * Successful HTTP response + * @enum {number} + */ Ok = 200, + /** + * Request has been fulfilled; new resource created as a result. + * @enum {number} + */ Created = 201, + /** + * Request accepted, but not yet processed. + * @enum {number} + */ Accepted = 202, + /** + * Request successful. Meta-information returned is from original server, not local. + * @enum {number} + */ NonAuthoritativeInformation = 203, + /** + * Request processed. No content returned. + * @enum {number} + */ NoContent = 204, + /** + * Server specifies part of document should be reset. + * @enum {number} + */ ResetContent = 205, + /** + * Partial content returned due to GET. + * @enum {number} + */ PartialContent = 206, + /** + * Multiple Resource Meta-Data: Could be used for collection instances. + * @enum {number} + */ MultiStatus = 207, + /** + * Status code used to indicate that a certain request has been already reported. + * @enum {number} + */ AlreadyReported = 208, + /** + * The server has fulfilled the request for the content, and the content is being conveyed in a manner described by the Content-Encoding, Content-Range, and Content-Type headers. + * @enum {number} + */ ImUsed = 226, + /** + * @enum {number} + * @description HTTP status code for multiple choices + */ MultipleChoices = 300, + + /** + * @enum {number} + * @description HTTP status code for moved permanently + */ MovedPermanently = 301, + + /** + * @enum {number} + * @description HTTP status code for found + */ Found = 302, + + /** + * @enum {number} + * @description HTTP status code for see other + */ SeeOther = 303, + + /** + * @enum {number} + * @description HTTP status code for not modified + */ NotModified = 304, + + /** + * @enum {number} + * @description HTTP status code for use proxy + */ UseProxy = 305, + + /** + * @enum {number} + * @description HTTP status code for temporary redirect + */ TemporaryRedirect = 307, + + /** + * @enum {number} + * @description HTTP status code for permanent redirect + */ PermanentRedirect = 308, + /** @description Client error: The server could not understand the request due to invalid syntax. */ BadRequest = 400, + + /** @description Client error: The client must authenticate itself to get the requested response. */ Unauthorized = 401, + + /** @description Client error: This response code is reserved for future use. */ PaymentRequired = 402, + + /** @description Client error: The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. */ Forbidden = 403, + + /** @description Client error: The server cannot find requested resource. */ NotFound = 404, + + /** @description Client error: The request method is not supported by the server and cannot be handled. */ MethodNotAllowed = 405, + + /** @description Client error: This response is sent when the web server, after performing server-driven content negotiation, doesn't find any content following the criteria given by the user agent. */ NotAcceptable = 406, + + /** @description Client error: This is similar to 401 (Unauthorised), but indicates that the client must authenticate itself to get the requested response. */ ProxyAuthenticationRequired = 407, + + /** @description Client error: This response is sent on an idle connection by some servers, even without any previous request by the client. */ RequestTimeout = 408, + + /** @description Client error: This response is sent when a request conflicts with the current state of the server. */ Conflict = 409, + + /** @description Client error: This response is sent when the requested content has been permanently deleted from server, with no forwarding address. */ Gone = 410, + + /** @description Client error: The server refuses to accept the request without a defined Content- Length. */ LengthRequired = 411, + + /** @description Client error: The precondition given in the request evaluated to false by the server. */ PreconditionFailed = 412, + + /** @description Client error: Request entity is larger than limits defined by server. */ PayloadTooLarge = 413, + + /** @description Client error: The URI requested by the client is longer than the server is willing to interpret. */ UriTooLong = 414, + + /** @description Client error: The media format of the requested data is not supported by the server, so the server is rejecting the request. */ UnsupportedMediaType = 415, + + /** @description Client error: The range specified by the Range header field in the request can't be fulfilled. */ RangeNotSatisfiable = 416, + + /** @description Client error: This response code means the expectation indicated by the Expect request header field can't be met by the server. */ ExpectationFailed = 417, + + /** @description Client error: The server refuses to brew coffee because it is, permanently, a teapot. */ ImATeapot = 418, + /** + * @enum + * @name MisdirectedRequest + * @description Represents HTTP status code 421: Misdirected Request. + * The client should switch to a different protocol such as TLS/1.0. + */ MisdirectedRequest = 421, + + /** + * @enum + * @name UnprocessableEntity + * @description Represents HTTP status code 422: Unprocessable Entity. + * The request was well-formed but was unable to be followed due to semantic errors. + */ UnprocessableEntity = 422, + + /** + * @enum + * @name Locked + * @description Represents HTTP status code 423: Locked. + * The resource that is being accessed is locked. + */ Locked = 423, + + /** + * @enum + * @name FailedDependency + * @description Represents HTTP status code 424: Failed Dependency. + * The request failed because it depended on another request and that request failed. + */ FailedDependency = 424, + + /** + * @enum + * @name TooEarly + * @description Represents HTTP status code 425: Too Early. + * Indicates that the server is unwilling to risk processing a request that might be replayed. + */ TooEarly = 425, + + /** + * @enum + * @name UpgradeRequired + * @description Represents HTTP status code 426: Upgrade Required. + * The client should switch to a different protocol. + */ UpgradeRequired = 426, + + /** + * @enum + * @name PreconditionRequired + * @description Represents HTTP status code 428: Precondition Required. + * The client must first fulfill certain precondition. + */ PreconditionRequired = 428, + + /** + * @enum + * @name TooManyRequests + * @description Represents HTTP status code 429: Too Many Requests. + * The user has sent too many requests in a certain amount of time. + */ TooManyRequests = 429, + + /** + * @enum + * @name RequestHeaderFieldsTooLarge + * @description Represents HTTP status code 431: Request Header Fields Too Large. + * The server is unwilling to process the request because its header fields are too large. + */ RequestHeaderFieldsTooLarge = 431, + + /** + * @enum + * @name UnavailableForLegalReasons + * @description Represents HTTP status code 451: Unavailable For Legal Reasons. + * The server is denying access for legal reasons. + */ UnavailableForLegalReasons = 451, InternalServerError = 500, @@ -64,5 +265,5 @@ export enum HttpStatusCode { InsufficientStorage = 507, LoopDetected = 508, NotExtended = 510, - NetworkAuthenticationRequired = 511 -} \ No newline at end of file + NetworkAuthenticationRequired = 511, +} diff --git a/src/services/brand.service.ts b/src/services/brand.service.ts index cb9026e..c41ddba 100644 --- a/src/services/brand.service.ts +++ b/src/services/brand.service.ts @@ -1,8 +1,8 @@ import type IDbBrand from "@interfaces/database/IDbBrand"; import MysqlService from "@services/mysql.service"; +import { isDebugMode } from "@utils/debugState"; import { Logger } from "tslog"; import { v4 as uuidv4 } from "uuid"; -import {isDebugMode} from "@utils/debugState"; const DbHandler = new MysqlService.Handler("BrandService"); const logger = new Logger({ diff --git a/src/services/category.service.ts b/src/services/category.service.ts index 1a1fda1..26e3c61 100644 --- a/src/services/category.service.ts +++ b/src/services/category.service.ts @@ -1,8 +1,8 @@ import type { IDbCategory } from "@interfaces/database/IDbCategory"; import MysqlService from "@services/mysql.service"; +import { isDebugMode } from "@utils/debugState"; import { Logger } from "tslog"; import { v4 as uuidv4 } from "uuid"; -import {isDebugMode} from "@utils/debugState"; const DbHandler = new MysqlService.Handler("CategoryService"); const logger = new Logger({ diff --git a/src/services/jwt.service.ts b/src/services/jwt.service.ts index 54a0850..4538790 100644 --- a/src/services/jwt.service.ts +++ b/src/services/jwt.service.ts @@ -1,3 +1,4 @@ +import { isDebugMode } from "@utils/debugState"; import { type JWTHeaderParameters, type JWTPayload, @@ -5,7 +6,6 @@ import { jwtVerify, } from "jose"; import { Logger } from "tslog"; -import {isDebugMode} from "@utils/debugState"; const logger = new Logger({ name: "JwtService", diff --git a/src/services/model.service.ts b/src/services/model.service.ts index c6b8d53..35d80d6 100644 --- a/src/services/model.service.ts +++ b/src/services/model.service.ts @@ -1,8 +1,8 @@ import type IDbModel from "@interfaces/database/IDbModel"; import MysqlService from "@services/mysql.service"; +import { isDebugMode } from "@utils/debugState"; import { Logger } from "tslog"; import { v4 as uuidv4 } from "uuid"; -import {isDebugMode} from "@utils/debugState"; const DbHandler = new MysqlService.Handler("ModelService"); const logger = new Logger({ diff --git a/src/services/mysql.service.ts b/src/services/mysql.service.ts index d8dd2d2..6a64677 100644 --- a/src/services/mysql.service.ts +++ b/src/services/mysql.service.ts @@ -10,9 +10,9 @@ import type { IDbStatusResult } from "@interfaces/database/IDbStatusResult"; import type { IDbUser } from "@interfaces/database/IDbUser"; import type { IDbVehicle } from "@interfaces/database/IDbVehicle"; import type { IUserUpdate } from "@interfaces/services/IUserUpdate"; +import { isDebugMode } from "@utils/debugState"; import mysql, { type Connection, type ConnectionOptions } from "mysql2"; import { Logger } from "tslog"; -import {isDebugMode} from "@utils/debugState"; const access: ConnectionOptions = { host: `${process.env["MYSQL_HOST"]}`, @@ -38,9 +38,10 @@ class MysqlHandler { this.Logger.error(`\n\n> Error connecting to MySQL: \n${err}\n`); process.exit(1); } - if (isDebugMode()) this.Logger.debug( - `\n\n> Connected to MySQL database (${access.database})\n`, - ); + if (isDebugMode()) + this.Logger.debug( + `\n\n> Connected to MySQL database (${access.database})\n`, + ); }); } closeConnection() { @@ -82,12 +83,16 @@ class MysqlHandler { (key: string) => `${key}`, ); const values = Object.values(data.values).map((val) => val); - if (isDebugMode()) this.Logger.debug( - `\n\n>-> Factorized ${_sqlQueryKeys.length} keys for a prepare Query.\n>-> Action: ${data.actionName}\n`, - ); + if (isDebugMode()) + this.Logger.debug( + `\n\n>-> Factorized ${_sqlQueryKeys.length} keys for a prepare Query.\n>-> Action: ${data.actionName}\n`, + ); const sqlQueryKeys = _sqlQueryKeys.join(", "); if (_id && _id.length > 2) { - if (isDebugMode()) this.Logger.trace(`\n\n> Id post-pushed in factorized data.\n ${_id}`); + if (isDebugMode()) + this.Logger.trace( + `\n\n> Id post-pushed in factorized data.\n ${_id}`, + ); values.push(_id); } diff --git a/src/utils/debugState.ts b/src/utils/debugState.ts index d5b725b..fb1ce22 100644 --- a/src/utils/debugState.ts +++ b/src/utils/debugState.ts @@ -1,9 +1,8 @@ import process from "node:process"; - export function isDebugMode() { - if (process.env["DEBUG"] === 'true') { + if (process.env["DEBUG"] === "true") { return true; } return false; -} \ No newline at end of file +}