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 <yidhra@tuta.io>
This commit is contained in:
Mathis H (Avnyr) 2024-04-30 16:19:12 +02:00
parent df28d3aa52
commit bc12f94e41
Signed by: Mathis
GPG Key ID: DD9E0666A747D126

View File

@ -5,7 +5,6 @@ import { ErrorType, type ISError } from "@interfaces/services/ISError";
import CredentialService from "@services/credential.service"; import CredentialService from "@services/credential.service";
import JwtService from "@services/jwt.service"; import JwtService from "@services/jwt.service";
import MySqlService from "@services/mysql.service"; import MySqlService from "@services/mysql.service";
import MysqlService from "@services/mysql.service";
import { Logger } from "tslog"; import { Logger } from "tslog";
import { v4 } from "uuid"; import { v4 } from "uuid";
@ -28,7 +27,7 @@ async function getUserByEmail(targetEmail: string): Promise<IDbUser | ISError> {
try { try {
const dbUser = await MySqlService.User.getByEmail(DbHandler, targetEmail); const dbUser = await MySqlService.User.getByEmail(DbHandler, targetEmail);
if (dbUser === undefined) { if (dbUser === undefined) {
logger.info(`User not found (${targetEmail})`); logger.info(`\n\n> User not found (${targetEmail})\n`);
return { return {
error: ErrorType.NotFound, error: ErrorType.NotFound,
message: "The user was not fund.", message: "The user was not fund.",
@ -53,7 +52,7 @@ async function getUserByEmail(targetEmail: string): Promise<IDbUser | ISError> {
async function getUserFromIdService(id: string): Promise<IDbUser | ISError> { async function getUserFromIdService(id: string): Promise<IDbUser | ISError> {
try { try {
if (!id || id.length !== 36) { if (!id || id.length !== 36) {
logger.info(`Invalid ID (${id})`); logger.info(`\n\n> Invalid ID (${id})\n`);
return { return {
error: ErrorType.InvalidData, error: ErrorType.InvalidData,
message: "Invalid ID length.", message: "Invalid ID length.",
@ -76,194 +75,201 @@ async function getUserFromIdService(id: string): Promise<IDbUser | ISError> {
} }
} }
//ToTest
/**
* Registers a new user.
*
* @param {IReqRegister} inputData - The input data for registration.
* @return {Promise<ISError | string>} - A Promise that resolves to either an error or a token.
*/
async function register(inputData: IReqRegister): Promise<ISError | string> { async function register(inputData: IReqRegister): Promise<ISError | string> {
if (inputData.password.length < 6) { try {
return { if (inputData.password.length < 6) {
error: ErrorType.InvalidData, return {
message: "Password must be at least 6 characters long.", error: ErrorType.InvalidData,
}; message: "Password must be at least 6 characters long.",
} };
//TODO check Object content keys }
const passwordHash = await CredentialService.hash(`${inputData.password}`);
// Does the new user has accepted GDPR ? const passwordHash = await CredentialService.hash(`${inputData.password}`);
if (inputData.gdpr !== true) {
return {
error: ErrorType.InvalidData,
message: "GDPR acceptance is required.",
};
}
const currentDate = new Date();
// Check if exist and return // Does the new user has accepted GDPR ?
const dbUserIfExist: IDbUser | ISError = await getUserByEmail( if (inputData.gdpr !== true) {
inputData.email, return {
); error: ErrorType.InvalidData,
if ("error" in dbUserIfExist) { message: "GDPR acceptance is required.",
return { };
error: dbUserIfExist.error, }
message: dbUserIfExist.message, const currentDate = new Date();
};
} // Check if exist and return
if (dbUserIfExist.id) { const dbUserIfExist: IDbUser | ISError = await getUserByEmail(
logger.info( inputData.email,
`User already exist for email "${inputData.email}".\n(${dbUserIfExist.username}::${dbUserIfExist.id})\n`,
); );
return { if ("error" in dbUserIfExist) {
error: ErrorType.UnAuthorized, return {
message: "User already exists.", error: dbUserIfExist.error,
}; message: dbUserIfExist.message,
} };
const currentId = v4(); }
const NewUser = await MySqlService.User.insert(DbHandler, { if (dbUserIfExist.id) {
id: currentId, logger.info(
email: inputData.email, `\n\n> User already exist for email "${inputData.email}".\n(${dbUserIfExist.username}::${dbUserIfExist.id})\n`,
username: inputData.username, );
firstname: inputData.firstName, return {
lastname: inputData.lastName, error: ErrorType.UnAuthorized,
dob: inputData.dob, message: "User already exists.",
hash: passwordHash, };
gdpr: currentDate, }
is_admin: false, const currentId = v4();
is_mail_verified: false, const NewUser = await MySqlService.User.insert(DbHandler, {
}); id: currentId,
if ("error" in NewUser || NewUser.affectedRows === 0) { 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 { return {
error: ErrorType.DatabaseError, 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) { //ToTest
//const passwordHash = await getHashFromPassword(sanitizedData.password); /**
const dbUser = await MysqlService.User.getByUsername( * Logs in a user with the provided input data.
DbHandler, *
ReqData.username, * @param inputData - The input data for the login operation.
); * @property email - The email of the user.
if (!dbUser) { * @property password - The password of the user.
console.log(`LoginService :> User does not exist (${ReqData.username})`); * @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<ISError | string> {
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 { 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: <explanation>
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. * Retrieves all users from the database.
* *
* @async * @returns {Promise<Array<IDbUser> | ISError>} The list of users, or an error object if an error occurred.
* @function getAllUsersService
* @returns {Promise<{iat: number, users: Array<user>, length: number}>} - The response object containing the users array and its length.
*/ */
async function getAllUsersService() { async function getAllUsersService(): Promise<Array<IDbUser> | ISError> {
const users = await Db.collection("users").find().toArray(); try {
// biome-ignore lint/complexity/noForEach: <explanation> const allUsers = await MySqlService.User.getAll(DbHandler);
users.forEach((user) => { if (allUsers === undefined) {
delete user.passwordHash; logger.error(`Error retrieving all users.`);
delete user._id; return {
delete user.gdpr; error: ErrorType.DatabaseError,
}); message: "An unknown error occurred.",
logger.info(`Query ${users.length} user(s)`); };
return { }
iat: Date.now(), return allUsers;
users: users, } catch (err) {
length: users.length, logger.error(`\n\n${err}\n`);
}; return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
} }
/** async function editUserService(targetId, inputData: IDbUser): Promise<ISError | boolean> {
* Edits a user in the database. if (!targetId || targetId.length !== 36) {
* logger.info(`\n\n> Invalid ID (${targetId})\n`);
* @param {string} targetId - The ID of the user to be edited. return {
* @param {object} sanitizedData - The sanitized data to update the user with. error: ErrorType.InvalidData,
* @returns {object} - An object indicating the result of the operation. message: "Invalid ID length.",
* If the user is not found, the error property will be a string "userNotFound". };
* Otherwise, the error property will be a string "none". }
*/ const dbUser = await MySqlService.User.getById(DbHandler, targetId)
async function editUserService(targetId, sanitizedData) { if (!dbUser.id) {
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})`);
return { return {
error: "userNotFound", error: ErrorType.NotFound,
}; message: "User not found.",
};
} }
const result = await MySqlService.User.update(DbHandler, {
logger.info(`EDIT :> User updated (${targetId})`); username: inputData.username,
return { firstname: inputData.firstname,
error: "none", lastname: inputData.lastname,
}; dob: inputData.dob,
})
} }
/** /**