From bc12f94e41a8dcf236c2fc515593658f10bee498 Mon Sep 17 00:00:00 2001 From: Mathis Date: Tue, 30 Apr 2024 16:19:12 +0200 Subject: [PATCH] feat(services): refactor user service and improve error handling - Introduced error handling in `register`, `login`, `getAllUsersService`, and `editUserService` methods. - Improved logging by adding breaks for better visibility. - Refactored code for better readability and maintainability. - Eliminated unnecessary and duplicate imports. - Transitioned from MongoDB to MySQL service. - Added comment flags for testing purposes. Issue: #18 Signed-off-by: Mathis --- src/services/user.service.ts | 342 ++++++++++++++++++----------------- 1 file changed, 174 insertions(+), 168 deletions(-) diff --git a/src/services/user.service.ts b/src/services/user.service.ts index db7b3ae..dc84210 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -5,7 +5,6 @@ import { ErrorType, type ISError } from "@interfaces/services/ISError"; import CredentialService from "@services/credential.service"; import JwtService from "@services/jwt.service"; import MySqlService from "@services/mysql.service"; -import MysqlService from "@services/mysql.service"; import { Logger } from "tslog"; import { v4 } from "uuid"; @@ -28,7 +27,7 @@ async function getUserByEmail(targetEmail: string): Promise { try { const dbUser = await MySqlService.User.getByEmail(DbHandler, targetEmail); if (dbUser === undefined) { - logger.info(`User not found (${targetEmail})`); + logger.info(`\n\n> User not found (${targetEmail})\n`); return { error: ErrorType.NotFound, message: "The user was not fund.", @@ -53,7 +52,7 @@ async function getUserByEmail(targetEmail: string): Promise { async function getUserFromIdService(id: string): Promise { try { if (!id || id.length !== 36) { - logger.info(`Invalid ID (${id})`); + logger.info(`\n\n> Invalid ID (${id})\n`); return { error: ErrorType.InvalidData, message: "Invalid ID length.", @@ -76,194 +75,201 @@ async function getUserFromIdService(id: string): Promise { } } +//ToTest +/** + * Registers a new user. + * + * @param {IReqRegister} inputData - The input data for registration. + * @return {Promise} - A Promise that resolves to either an error or a token. + */ async function register(inputData: IReqRegister): Promise { - if (inputData.password.length < 6) { - return { - error: ErrorType.InvalidData, - message: "Password must be at least 6 characters long.", - }; - } - //TODO check Object content keys - const passwordHash = await CredentialService.hash(`${inputData.password}`); + try { + if (inputData.password.length < 6) { + return { + error: ErrorType.InvalidData, + message: "Password must be at least 6 characters long.", + }; + } - // Does the new user has accepted GDPR ? - if (inputData.gdpr !== true) { - return { - error: ErrorType.InvalidData, - message: "GDPR acceptance is required.", - }; - } - const currentDate = new Date(); + const passwordHash = await CredentialService.hash(`${inputData.password}`); - // Check if exist and return - const dbUserIfExist: IDbUser | ISError = await getUserByEmail( - inputData.email, - ); - if ("error" in dbUserIfExist) { - return { - error: dbUserIfExist.error, - message: dbUserIfExist.message, - }; - } - if (dbUserIfExist.id) { - logger.info( - `User already exist for email "${inputData.email}".\n(${dbUserIfExist.username}::${dbUserIfExist.id})\n`, + // Does the new user has accepted GDPR ? + if (inputData.gdpr !== true) { + return { + error: ErrorType.InvalidData, + message: "GDPR acceptance is required.", + }; + } + const currentDate = new Date(); + + // Check if exist and return + const dbUserIfExist: IDbUser | ISError = await getUserByEmail( + inputData.email, ); - return { - error: ErrorType.UnAuthorized, - message: "User already exists.", - }; - } - const currentId = v4(); - const NewUser = await MySqlService.User.insert(DbHandler, { - id: currentId, - email: inputData.email, - username: inputData.username, - firstname: inputData.firstName, - lastname: inputData.lastName, - dob: inputData.dob, - hash: passwordHash, - gdpr: currentDate, - is_admin: false, - is_mail_verified: false, - }); - if ("error" in NewUser || NewUser.affectedRows === 0) { + if ("error" in dbUserIfExist) { + return { + error: dbUserIfExist.error, + message: dbUserIfExist.message, + }; + } + if (dbUserIfExist.id) { + logger.info( + `\n\n> User already exist for email "${inputData.email}".\n(${dbUserIfExist.username}::${dbUserIfExist.id})\n`, + ); + return { + error: ErrorType.UnAuthorized, + message: "User already exists.", + }; + } + const currentId = v4(); + const NewUser = await MySqlService.User.insert(DbHandler, { + id: currentId, + email: inputData.email, + username: inputData.username, + firstname: inputData.firstName, + lastname: inputData.lastName, + dob: inputData.dob, + hash: passwordHash, + gdpr: currentDate, + is_admin: false, + is_mail_verified: false, + }); + if ("error" in NewUser || NewUser.affectedRows === 0) { + return { + error: ErrorType.DatabaseError, + message: "Error when inserting user in database.", + }; + } + logger.info(`\n\n> New user created ! (${inputData.username}::${currentId})\n`); + + // JWT + const token = await JwtService.sign( + { + sub: currentId, + }, + { + alg: "HS512", + }, + "1d", + "user", + ); + return token; + } catch (err) { + logger.error(`\n\n${err}\n`); return { error: ErrorType.DatabaseError, - message: "Error when inserting user in database.", + message: "An unknown error occurred.", }; } - logger.info(`New user created ! (${inputData.username}::${currentId})`); - - // JWT - const token = await JwtService.sign( - { - sub: NewUser.id, - }, - { - alg: "HS512", - }, - "1d", - "user", - ); - return token; } -async function login(ReqData: IReqLogin) { - //const passwordHash = await getHashFromPassword(sanitizedData.password); - const dbUser = await MysqlService.User.getByUsername( - DbHandler, - ReqData.username, - ); - if (!dbUser) { - console.log(`LoginService :> User does not exist (${ReqData.username})`); +//ToTest +/** + * Logs in a user with the provided input data. + * + * @param inputData - The input data for the login operation. + * @property email - The email of the user. + * @property password - The password of the user. + * @returns A promise that resolves to either an error or a token string. + * @throws {ISError} - If an error occurs in the login process. + * @throws {string} - If the login was successful, returns a token string. + */ +async function login(inputData: IReqLogin): Promise { + try { + const dbUser = await getUserByEmail(inputData.email); + if ("error" in dbUser) { + return { + error: dbUser.error, + message: dbUser.message, + }; + } + if (!dbUser.id) { + return { + error: ErrorType.NotFound, + message: "User not found.", + }; + } + const isPasswordValid = await CredentialService.compare( + inputData.password, + dbUser.hash + ); + if (!isPasswordValid) { + return { + error: ErrorType.UnAuthorized, + message: "Invalid password.", + }; + } + const token = await JwtService.sign( + { + sub: dbUser.id, + p: [{ + isAdmin: dbUser.is_admin, + gdpr: dbUser.gdpr + }] + }, + { + alg: "HS512", + }, + "1d", + "user" + ); + return token; + } catch (err) { + logger.error(`\n\n${err}\n`); return { - error: "userNotFound", + error: ErrorType.DatabaseError, + message: "An unknown error occurred.", }; } - if (ReqData.password.length < 6) { - console.log("X"); - console.log(`LoginService :> Invalid password (${ReqData.username})`); - return { - error: "invalidPassword", - }; - } - const isPasswordValid = await CredentialService.compare( - ReqData.password, - dbUser.hash, - ); - if (!isPasswordValid) { - console.log(isPasswordValid); - console.log(`LoginService :> Invalid password (${ReqData.username})`); - return { - error: "invalidPassword", - }; - } - // biome-ignore lint/style/useConst: - let userData = { - error: "none", - jwt: "", - user: { - id: dbUser.id, - username: dbUser.username, - displayName: dbUser.displayName, - }, - }; - - userData.jwt = await JwtService.sign( - { - sub: dbUser.id, - }, - { - alg: "HS512", - }, - "7d", - "user", - ); - - console.log("USERDATA :>"); - console.log(userData); - return userData; } +//TOTest /** * Retrieves all users from the database. * - * @async - * @function getAllUsersService - * @returns {Promise<{iat: number, users: Array, length: number}>} - The response object containing the users array and its length. + * @returns {Promise | ISError>} The list of users, or an error object if an error occurred. */ -async function getAllUsersService() { - const users = await Db.collection("users").find().toArray(); - // biome-ignore lint/complexity/noForEach: - users.forEach((user) => { - delete user.passwordHash; - delete user._id; - delete user.gdpr; - }); - logger.info(`Query ${users.length} user(s)`); - return { - iat: Date.now(), - users: users, - length: users.length, - }; +async function getAllUsersService(): Promise | ISError> { + try { + const allUsers = await MySqlService.User.getAll(DbHandler); + if (allUsers === undefined) { + logger.error(`Error retrieving all users.`); + return { + error: ErrorType.DatabaseError, + message: "An unknown error occurred.", + }; + } + return allUsers; + } catch (err) { + logger.error(`\n\n${err}\n`); + return { + error: ErrorType.DatabaseError, + message: "An unknown error occurred.", + }; + } } -/** - * Edits a user in the database. - * - * @param {string} targetId - The ID of the user to be edited. - * @param {object} sanitizedData - The sanitized data to update the user with. - * @returns {object} - An object indicating the result of the operation. - * If the user is not found, the error property will be a string "userNotFound". - * Otherwise, the error property will be a string "none". - */ -async function editUserService(targetId, sanitizedData) { - if (sanitizedData.password) { - const passwordHash = await getHashFromPassword(sanitizedData.password); - delete sanitizedData.password; - logger.info(`Changing password for user "${targetId}"`); - sanitizedData.passwordHash = passwordHash; - } - const updatedUserResult = await Db.collection("users").updateOne( - { - id: targetId, - }, - { - $set: sanitizedData, - }, - ); - if (updatedUserResult.modifiedCount === 0) { - logger.info(`EDIT :> User not found (${targetId})`); +async function editUserService(targetId, inputData: IDbUser): Promise { + if (!targetId || targetId.length !== 36) { + logger.info(`\n\n> Invalid ID (${targetId})\n`); + return { + error: ErrorType.InvalidData, + message: "Invalid ID length.", + }; + } + const dbUser = await MySqlService.User.getById(DbHandler, targetId) + if (!dbUser.id) { return { - error: "userNotFound", - }; + error: ErrorType.NotFound, + message: "User not found.", + }; } - - logger.info(`EDIT :> User updated (${targetId})`); - return { - error: "none", - }; + const result = await MySqlService.User.update(DbHandler, { + username: inputData.username, + firstname: inputData.firstname, + lastname: inputData.lastname, + dob: inputData.dob, + }) } /**