brief-05-back/src/services/user.service.ts
Mathis 34f028ef9f
feat(app): updated log messages in multiple files
Changes applied to several services and controllers files:
- Enhanced logging messages with line breaks in `brand.controller.ts`, `auth.controller.ts`, `model.controller.ts`, and `category.controller.ts`.
- Adjusted logging in `mysql.service.ts`, `brand.service.ts`, `user.service.ts`, `category.service.ts`, `model.service.ts` and `jwt.service.ts` to commence on a new line for better readability.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 14:18:47 +02:00

300 lines
7.6 KiB
TypeScript

import type { IDbUser } from "@interfaces/database/IDbUser";
import type { IReqLogin } from "@interfaces/requests/IReqLogin";
import type { IReqRegister } from "@interfaces/requests/IReqRegister";
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";
const logger = new Logger({
name: "UserService",
});
const DbHandler = new MySqlService.Handler("UserService");
/**
* Retrieves a user from the database by the given email address.
*
* @param {string} targetEmail - The email address of the user to retrieve.
* @returns {Promise<IDbUser | ISError>}
* - A promise that resolves with the user.
* - If the user is not found, an error object is returned.
* - If an error occurs during the database operation, an error object is returned.
*/
async function getUserByEmail(targetEmail: string): Promise<IDbUser | ISError> {
try {
const dbUser = await MySqlService.User.getByEmail(DbHandler, targetEmail);
if (dbUser === undefined) {
logger.info(`User not found (${targetEmail})`);
return {
error: ErrorType.NotFound,
message: "The user was not fund.",
};
}
return dbUser;
} catch (err) {
logger.error(err);
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
/**
* Retrieves a user from the database based on the provided ID.
*
* @param {string} id - The ID of the user to retrieve.
* @returns {Promise<IDbUser | ISError>} - A promise that resolves with the user object if found, or an error object if not.
*/
async function getUserFromIdService(id: string): Promise<IDbUser | ISError> {
try {
if (!id || id.length !== 36) {
logger.info(`Invalid ID (${id})`);
return {
error: ErrorType.InvalidData,
message: "Invalid ID length.",
};
}
const dbUser = await MySqlService.User.getById(DbHandler, id);
if (dbUser === undefined) {
logger.info(`User not found (${id})`);
return {
error: ErrorType.NotFound,
message: "The user was not found.",
};
}
return dbUser;
} catch (err) {
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
async function register(inputData: IReqRegister): Promise<ISError | string> {
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}`);
// 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,
);
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`,
);
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(`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})`);
return {
error: "userNotFound",
};
}
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;
}
/**
* Retrieves all users from the database.
*
* @async
* @function getAllUsersService
* @returns {Promise<{iat: number, users: Array<user>, length: number}>} - The response object containing the users array and its length.
*/
async function getAllUsersService() {
const users = await Db.collection("users").find().toArray();
// biome-ignore lint/complexity/noForEach: <explanation>
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,
};
}
/**
* 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})`);
return {
error: "userNotFound",
};
}
logger.info(`EDIT :> User updated (${targetId})`);
return {
error: "none",
};
}
/**
* Delete a user from the database.
*
* @param {string} targetId - The ID of the user to be deleted.
* @return {Promise<boolean>} - A promise that resolves to true if the user is successfully deleted, or false if an error occurs.
*/
async function deleteUserService(targetId) {
logger.info(`Deleting user ${targetId}`);
try {
await Db.collection("users").deleteOne({
id: targetId,
});
return true;
} catch (e) {
logger.warn(e);
return false;
}
}
logger.debug("\nService loaded.");
const UserService = {
register: register,
login: login,
getAll: getAllUsersService,
getFromId: getUserFromIdService,
edit: editUserService,
delete: deleteUserService,
};
export default UserService;