Compare commits

..

No commits in common. "bc12f94e41a8dcf236c2fc515593658f10bee498" and "0635c512cc8af13d2a2cd572771f76f737266210" have entirely different histories.

12 changed files with 201 additions and 295 deletions

View File

@ -397,8 +397,6 @@ async function getSelf(req: Request, res: Response) {
}); });
} }
logger.debug("\nController loaded.");
const AuthController = { const AuthController = {
register: registerUser, register: registerUser,
login: loginUser, login: loginUser,

View File

@ -171,8 +171,6 @@ async function deleteBrand(req: Request, res: Response): Promise<Response> {
//TODO get models of the brand //TODO get models of the brand
logger.debug("\nController loaded.");
const BrandController = { const BrandController = {
create: createBrand, create: createBrand,
update: updateBrand, update: updateBrand,

View File

@ -177,8 +177,6 @@ async function getBySlugCategory(
}); });
} }
logger.debug("\nController loaded.");
const CategoryController = { const CategoryController = {
create: createCategory, create: createCategory,
update: updateCategory, update: updateCategory,

View File

@ -131,8 +131,6 @@ async function deleteModel(req: Request, res: Response): Promise<Response> {
//TODO get model with vehicle available. //TODO get model with vehicle available.
logger.debug("\nController loaded.");
const ModelController = { const ModelController = {
create: createModel, create: createModel,
update: updateModel, update: updateModel,

View File

@ -1,48 +0,0 @@
/**
* Represents the output of the factorization function.
*/
export interface IDbFactorizeOutput {
/**
* Description: The variable `_valuesArray` is an array that can contain values of type `string`, `boolean`, `number`, or `Date`.
* (The value associated with the keys of `_keysTemplate`)
*
* @type {Array<string | boolean | number | Date>}
*/
_valuesArray: Array<string | boolean | number | Date>;
/**
* Represents the SQL Query template for the keys.
* @type {string}
*/
_keysTemplate: string;
/**
* The total number of fields.
*
* @type {number}
*/
totalFields: number;
}
/**
* Interface IDbFactorizeInput represents the input required to factorize a SQL query.
*/
export interface IDbFactorizeInput {
/**
* An object containing values that will be in a SQL Query.
*
* @type {Array<string | boolean | number | Date>}
*/
values: object;
/**
* Represents the name of the action that will result of the prepared SQL Query.
*
* @type {string}
*/
actionName: string;
/**
* Indicates whether an error should be thrown when encountering an error.
* If set to true, an error will be thrown. If set to false or not provided, the error will not be thrown.
*
* @type {boolean}
*/
throwOnError?: true;
}

View File

@ -1,8 +0,0 @@
export interface IUserUpdate {
id?: string;
username?: string;
firstname?: string;
lastname?: string;
dob?: Date;
gdpr?: Date;
}

View File

@ -168,7 +168,7 @@ async function deleteBrand(brandId: string): Promise<boolean> {
return true; return true;
} }
logger.debug("\nService loaded."); logger.debug("Service loaded.");
const BrandService = { const BrandService = {
create: createBrand, create: createBrand,

View File

@ -127,7 +127,7 @@ async function deleteCategory(id: string): Promise<unknown> {
} }
} }
logger.debug("\nService loaded."); logger.debug("Service loaded.");
const CategoryService = { const CategoryService = {
create: createCategory, create: createCategory,

View File

@ -64,7 +64,7 @@ async function JwtSignService(
.sign(new TextEncoder().encode(`${process.env["JWT_SECRET"]}`)); .sign(new TextEncoder().encode(`${process.env["JWT_SECRET"]}`));
} }
logger.debug("\nService loaded."); logger.debug("Service loaded.");
const JwtService = { const JwtService = {
verify: JwtVerifyService, verify: JwtVerifyService,

View File

@ -135,7 +135,7 @@ async function getAllModels(): Promise<IDbModel[] | null> {
} }
} }
logger.debug("\nService loaded."); logger.debug("Service loaded.");
/** /**
* ModelService is responsible for managing models. * ModelService is responsible for managing models.

View File

@ -7,8 +7,6 @@ import type { IDbUser } from "@interfaces/database/IDbUser";
import type { IDbVehicle } from "@interfaces/database/IDbVehicle"; import type { IDbVehicle } from "@interfaces/database/IDbVehicle";
import mysql, { type Connection, type ConnectionOptions } from "mysql2"; import mysql, { type Connection, type ConnectionOptions } from "mysql2";
import { Logger } from "tslog"; import { Logger } from "tslog";
import {IUserUpdate} from "@interfaces/services/IUserUpdate";
import {IDbFactorizeInput, IDbFactorizeOutput} from "@interfaces/database/IDbFactorize";
const access: ConnectionOptions = { const access: ConnectionOptions = {
host: `${process.env["MYSQL_HOST"]}`, host: `${process.env["MYSQL_HOST"]}`,
@ -34,7 +32,7 @@ class MysqlHandler {
this.Logger.error(`Error connecting to MySQL: ${err}`); this.Logger.error(`Error connecting to MySQL: ${err}`);
process.exit(1); process.exit(1);
} }
this.Logger.info(`\n\n> Connected to MySQL database (${access.database})\n`); this.Logger.info(`Connected to MySQL database (${access.database})`);
}); });
} }
closeConnection() { closeConnection() {
@ -54,40 +52,6 @@ class MysqlHandler {
}); });
} }
/**
* Factorize the input data values into a database query.
*
* @param {IDbFactorizeInput} data - The input data containing values to factorize.
* @return {Promise<IDbFactorizeOutput>} - A promise resolving to the factorized output.
*/
factorize(data: IDbFactorizeInput): Promise<IDbFactorizeOutput> {
return new Promise((resolve, reject)=>{
try {
const _sqlQueryKeys = Object.keys(data.values).map((key: string) => `\'${key}\' = ?`)
const values = Object.values(data.values).map((val)=>val)
this.Logger.debug(`\n\n>-> Factorized ${_sqlQueryKeys.length} keys for a prepare Query.\n>-> Action: ${data.actionName}\n`)
const sqlQueryKeys = _sqlQueryKeys.join(', ')
const factorizedOutput: IDbFactorizeOutput = {
_keysTemplate: sqlQueryKeys,
totalFields: _sqlQueryKeys.length,
_valuesArray: values
}
resolve(factorizedOutput);
} catch (err) {
if (data.throwOnError) throw new Error(`${err}`)
this.Logger.error(`\n|\n${err}\n|`)
reject(`${err}`)
}
})
}
/**
* Executes a query using the provided queryString and values.
* @param {string} queryString - The SQL query string to execute.
* @param {Array<string | boolean | Date | number>} values - The values to be inserted into the query.
* @returns {Promise<unknown>} - A promise that resolves with the query results or rejects with an error.
*/
execute( execute(
queryString: string, queryString: string,
values: Array<string | boolean | Date | number>, values: Array<string | boolean | Date | number>,
@ -174,25 +138,38 @@ const MySqlService = {
/** /**
* Updates a user in the database. * Updates a user in the database.
* @param {MysqlHandler} handler - The MySQL handler object. * @param {MysqlHandler} handler - The MySQL handler object.
* @param {IUserUpdate} data - The updated user data. * @param {IDbUser} data - The updated user data.
* @returns {Promise<IDbStatusResult>} - A promise that resolves to the result of the update operation. * @returns {Promise<IDbStatusResult>} - A promise that resolves to the result of the update operation.
* @throws {Error} If an error occurs during the update operation. * @throws {Error} If an error occurs during the update operation.
*/ */
update(handler: MysqlHandler, data: IUserUpdate): Promise<IDbStatusResult> { update(handler: MysqlHandler, data: IDbUser): Promise<IDbStatusResult> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!data.id) return reject("Id is undefined"); if (!data.id) return reject("Id is undefined");
if (data.id.length !== 36) return reject("Id invalid"); if (data.id.length !== 36) return reject("Id invalid");
if (data.gdpr && typeof data.gdpr !== typeof Date) {
return reject("Invalid gdpr date.")
}
try { try {
const _values = [];
const _template = ` const _template = `
${data.username ? "`username` = ?," && _values.push(data.username) as unknown as void : null} ${data.username ? "`username` = ?," : null}
${data.firstname ? "`firstname` = ?," : null} ${data.firstname ? "`firstname` = ?," : null}
${data.lastname ? "`lastname` = ?," : null} ${data.lastname ? "`lastname` = ?," : null}
${data.dob ? "`dob` = ?," : null} ${data.dob ? "`dob` = ?," : null}
${data.gdpr ? "`gdpr` = ?," : null}` ${data.email ? "`email` = ?," : null}
${data.is_mail_verified ? "`is_mail_verified` = ?," : null}
${data.is_admin ? "`is_admin` = ?," : null}
${data.gdpr ? "`gdpr` = ?," : null}
${data.hash ? "`hash` = ?" : null}`;
const _values = [
data.username,
data.firstname,
data.lastname,
data.dob,
data.email,
data.is_mail_verified,
data.is_admin,
data.gdpr,
data.hash,
data.id,
];
const _sql = `UPDATE "users" SET ${_template} WHERE 'id' = ?`; const _sql = `UPDATE "users" SET ${_template} WHERE 'id' = ?`;
handler.execute(_sql, _values).then((result) => { handler.execute(_sql, _values).then((result) => {

View File

@ -5,6 +5,7 @@ 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";
@ -27,7 +28,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(`\n\n> User not found (${targetEmail})\n`); logger.info(`User not found (${targetEmail})`);
return { return {
error: ErrorType.NotFound, error: ErrorType.NotFound,
message: "The user was not fund.", message: "The user was not fund.",
@ -52,7 +53,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(`\n\n> Invalid ID (${id})\n`); logger.info(`Invalid ID (${id})`);
return { return {
error: ErrorType.InvalidData, error: ErrorType.InvalidData,
message: "Invalid ID length.", message: "Invalid ID length.",
@ -75,22 +76,14 @@ 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> {
try {
if (inputData.password.length < 6) { if (inputData.password.length < 6) {
return { return {
error: ErrorType.InvalidData, error: ErrorType.InvalidData,
message: "Password must be at least 6 characters long.", message: "Password must be at least 6 characters long.",
}; };
} }
//TODO check Object content keys
const passwordHash = await CredentialService.hash(`${inputData.password}`); const passwordHash = await CredentialService.hash(`${inputData.password}`);
// Does the new user has accepted GDPR ? // Does the new user has accepted GDPR ?
@ -114,7 +107,7 @@ async function register(inputData: IReqRegister): Promise<ISError | string> {
} }
if (dbUserIfExist.id) { if (dbUserIfExist.id) {
logger.info( logger.info(
`\n\n> User already exist for email "${inputData.email}".\n(${dbUserIfExist.username}::${dbUserIfExist.id})\n`, `User already exist for email "${inputData.email}".\n(${dbUserIfExist.username}::${dbUserIfExist.id})\n`,
); );
return { return {
error: ErrorType.UnAuthorized, error: ErrorType.UnAuthorized,
@ -140,12 +133,12 @@ async function register(inputData: IReqRegister): Promise<ISError | string> {
message: "Error when inserting user in database.", message: "Error when inserting user in database.",
}; };
} }
logger.info(`\n\n> New user created ! (${inputData.username}::${currentId})\n`); logger.info(`New user created ! (${inputData.username}::${currentId})`);
// JWT // JWT
const token = await JwtService.sign( const token = await JwtService.sign(
{ {
sub: currentId, sub: NewUser.id,
}, },
{ {
alg: "HS512", alg: "HS512",
@ -154,123 +147,124 @@ async function register(inputData: IReqRegister): Promise<ISError | string> {
"user", "user",
); );
return token; return token;
} catch (err) {
logger.error(`\n\n${err}\n`);
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
} }
//ToTest async function login(ReqData: IReqLogin) {
/** //const passwordHash = await getHashFromPassword(sanitizedData.password);
* Logs in a user with the provided input data. const dbUser = await MysqlService.User.getByUsername(
* DbHandler,
* @param inputData - The input data for the login operation. ReqData.username,
* @property email - The email of the user. );
* @property password - The password of the user. if (!dbUser) {
* @returns A promise that resolves to either an error or a token string. console.log(`LoginService :> User does not exist (${ReqData.username})`);
* @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 { return {
error: dbUser.error, error: "userNotFound",
message: dbUser.message,
}; };
} }
if (!dbUser.id) { if (ReqData.password.length < 6) {
console.log("X");
console.log(`LoginService :> Invalid password (${ReqData.username})`);
return { return {
error: ErrorType.NotFound, error: "invalidPassword",
message: "User not found.",
}; };
} }
const isPasswordValid = await CredentialService.compare( const isPasswordValid = await CredentialService.compare(
inputData.password, ReqData.password,
dbUser.hash dbUser.hash,
); );
if (!isPasswordValid) { if (!isPasswordValid) {
console.log(isPasswordValid);
console.log(`LoginService :> Invalid password (${ReqData.username})`);
return { return {
error: ErrorType.UnAuthorized, error: "invalidPassword",
message: "Invalid password.",
}; };
} }
const token = await JwtService.sign( // 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, sub: dbUser.id,
p: [{
isAdmin: dbUser.is_admin,
gdpr: dbUser.gdpr
}]
}, },
{ {
alg: "HS512", alg: "HS512",
}, },
"1d", "7d",
"user" "user",
); );
return token;
} catch (err) { console.log("USERDATA :>");
logger.error(`\n\n${err}\n`); console.log(userData);
return { return userData;
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
} }
//TOTest
/** /**
* Retrieves all users from the database. * Retrieves all users from the database.
* *
* @returns {Promise<Array<IDbUser> | ISError>} The list of users, or an error object if an error occurred. * @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(): Promise<Array<IDbUser> | ISError> { async function getAllUsersService() {
try { const users = await Db.collection("users").find().toArray();
const allUsers = await MySqlService.User.getAll(DbHandler); // biome-ignore lint/complexity/noForEach: <explanation>
if (allUsers === undefined) { users.forEach((user) => {
logger.error(`Error retrieving all users.`); delete user.passwordHash;
delete user._id;
delete user.gdpr;
});
logger.info(`Query ${users.length} user(s)`);
return { return {
error: ErrorType.DatabaseError, iat: Date.now(),
message: "An unknown error occurred.", users: users,
length: users.length,
}; };
} }
return allUsers;
} catch (err) {
logger.error(`\n\n${err}\n`);
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
async function editUserService(targetId, inputData: IDbUser): Promise<ISError | boolean> { /**
if (!targetId || targetId.length !== 36) { * Edits a user in the database.
logger.info(`\n\n> Invalid ID (${targetId})\n`); *
* @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 { return {
error: ErrorType.InvalidData, error: "userNotFound",
message: "Invalid ID length.",
}; };
} }
const dbUser = await MySqlService.User.getById(DbHandler, targetId)
if (!dbUser.id) { logger.info(`EDIT :> User updated (${targetId})`);
return { return {
error: ErrorType.NotFound, error: "none",
message: "User not found.",
}; };
} }
const result = await MySqlService.User.update(DbHandler, {
username: inputData.username,
firstname: inputData.firstname,
lastname: inputData.lastname,
dob: inputData.dob,
})
}
/** /**
* Delete a user from the database. * Delete a user from the database.
@ -291,8 +285,7 @@ async function deleteUserService(targetId) {
} }
} }
logger.debug("\nService loaded."); logger.debug("Service loaded.");
const UserService = { const UserService = {
register: register, register: register,
login: login, login: login,