Compare commits

...

133 Commits

Author SHA1 Message Date
7fead5486b fix(db): modify 'rents' SQL table structure
The commit modifies the 'rents' SQL table:
- Replaces the improperly quoted table name with correct syntax: from 'rents' to `rents`.
- Changes `id` field data type from tinyint to varchar.
- Updates database dump's completion timestamp.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 14:56:37 +02:00
5cc214a29b schema
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 14:55:07 +02:00
65a6ae2e3c feat(routes): update rent related routes
- The "/affected" route has been updated to now use the `RentController.getAssignedToUser` method.
- The "/affected/all" route, which was previously not implemented, now utilizes the `RentController.getAll` function.
- These changes will help facilitate the retrieval of individual and all rented vehicles.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 13:18:46 +02:00
83d07a2812 feat(services): add availability check in rent service
A new condition was added in `rent.service.ts` to check if a vehicle is available before executing the rent operation. It also logs an error if the vehicle is not available.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 13:18:33 +02:00
e8acfd7b30 feat(services): update condition checks in model.service.ts
Updated the condition checks in the 'delete' and 'fetch' methods of the `model.service.ts`. Now, it properly checks if the model exists by examining the first item of the result array, instead of considering the result array itself. This resolves issues where fetching or deleting a model with a non-existent slug would incorrectly attempt to perform operations considering the empty array as valid input.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 13:18:15 +02:00
438ae4b5d0 fix(controllers): update model slug in updateModel function
The model slug has been updated to use `req.params["modelSlug"]` instead of `body.slug_name` in the `updateModel` function, ensuring the correct slug is used when checking if the model exists.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 13:17:58 +02:00
6ccc899320 feat(interfaces): add isAvailable field to IDbVehicle interface
The IDbVehicle interface has been updated to include an optional `isAvailable` field. This field can be used to check the availability status of a vehicle.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 13:17:47 +02:00
5afb6e22bf feat(services): update getBySlug function in MySQL services
The `getBySlug` function originally returned a single database model by slug from the MySQL handler. It has been updated to return a list of database models by slug name. Error handling for failed query executions is also included.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 13:17:37 +02:00
bdba2f6e29 feat(controllers): add rent controller
- Implemented functionality for creating, updating, and getting rent information.
- Created `createRent`, `updateRent`, `getAllAssigned`, and `getAssignedToUser` functions in the `rent.controller.ts` file.
- These functions handle all the interactions related to renting vehicles.

Issue: #25
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 13:17:25 +02:00
30c6f2e3f1 feat(services): remove unused import from rent.service.ts
The `body` import from "express-validator" has been removed from rent.service.ts as it was not used. Also, an error handler for deleting rent was updated to always return false on encountering an error, improving the error handling mechanism.

Issue: #24
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 12:02:36 +02:00
b47ec6c440 feat(services): add error logging in rent.service
A logging feature is added in the `rent.service.ts` to log any errors when attempting to delete rent. This feature helps in diagnosing issues during rent deletion.

Issue: #24
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 12:01:56 +02:00
b4f200cb32 feat(inspectionProfiles): increase TypeScript code redundancy threshold
The minimum size for the TypeScript language in the duplicated code check has been increased. This update reduces the sensitivity of the code redundancy inspection, potentially reducing false positive results.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 12:01:03 +02:00
ec53fcb247 feat(services): add rent service functions
This commit introduces new functions to handle rental services in the src/services/rent.service.ts. Functions created handle rental CRUD operations such as createRentService, updateRentService, deleteRentService, also additional utility functions such as getUserRentService and getRentByIdService that deal with user-associated rentals were introduced. These new functions serve to simplify rental management and improve our services for rental handling.

Issue: #24
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 12:00:48 +02:00
371d960cf3 feat(services): update getById method in mysql.service
- Update the getById method in mysql.service to return an array of vehicles instead of a single vehicle
- Add a new getById method to retrieve array of rental information by rental ID
- Improve the documentation to match the modifications in both methods' functionalities

Issue: #23
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 11:59:58 +02:00
3525fb12e6 feat(routes): add create vehicle function to route
This commit hooks up the `VehicleController.create` function to the route handling post requests at `/veh/new`. With this change, admin users can add new vehicles via this endpoint.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 11:23:13 +02:00
82afff9878 feat(services): add update function in mysql service
This commit adds an `update` function to the `mysql.service.ts`. This new function will update a rent record in the database. It takes a database handler object and a rent object containing the updated data, and returns a Promise that resolves to the status result of the update operation. The function will throw an error if an issue occurs during the update operation.

Issue: #23
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 11:23:00 +02:00
fef2bda082 refactor(services): simplify SQL query strings and reformat function parameters
This commit includes the following changes in `mysql.service.ts` file:
- Simplify SQL query strings for `UPDATE` and `INSERT` commands in various methods to make them more readable.
- Reformat parameters in `getAllModelsFromCategory` and `delete` methods for better readability.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 11:18:30 +02:00
f87aecaf75 feat(routes): update endpoints in rentRouter
Update `rentRouter.ts` to include `VehicleController` methods in route handlers.
- Add `VehicleController.getAll` to "/veh/all" route.
- Update "/veh/:vehicleId" route to include `VehicleController.getById`, `VehicleController.update`, and `VehicleController.delete`.

Issue: #22
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 11:17:45 +02:00
170b9a5693 feat(controllers): implement vehicle controller
Added a new `vehicle.controller.ts` file under controllers. It includes multiple functions related to vehicle management such as `createVehicle`, `updateVehicle`, `getAllVehicle`, `getVehicleById`, `getAvailableVehicle`, and `deleteVehicle`. These functions handle the creation, updates, retrieval, and deletion of a vehicle respectively. With these additions, the application now has the ability to manage vehicles through the `VehicleController` module.

Issue: #22
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 11:17:23 +02:00
c9ca39ddfa feat(services): update code formatting in vehicle.service.ts
- Modify import statement format and standardize indentation across the entire file.
- Add line breaks and commas where needed for better readability.
- Remove unnecessary comments and white space.
- Ensure consistent usage of semicolons.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 11:01:54 +02:00
da028ea2c4 feat(services): update CRUD operations in mysql.service.ts
In this commit:
- Implemented factorize method to standardize data processing and update operations for users, brands, models, categories, and rents.
- Updated SQL syntax to improve code hygiene and readability.
- Added debug mode checks to improve performance in production.
- Added exception handling for possible errors during SQL query execution.
- Added additional methods to get assigned vehicles to a user and all assigned vehicles from the database.
- Added missing delete method for vehicle and rent tables.

Issue: #23 & #21
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 10:30:57 +02:00
1c643b3625 feat(interfaces): add optional id property to IDbRent interface
A new optional `id` property has been added to `IDbRent` interface in the `interfaces` scope. This allows for more flexibility when working with rent objects in the database.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 09:52:07 +02:00
33d6793758 refactor(others): rename database table rent to rents
The database table `rent` has been renamed to `rents` to better align with naming conventions. The related DROP TABLE and CREATE TABLE commands have been updated accordingly in `db.sql`.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 09:51:42 +02:00
c95ac03680 feat(others): update database schema
- Change `LastIntrospectionLocalTimestamp` date in `brief_05` schema
- Rename table `rent` to `rents`
- Update `id` column `DasType` to `varchar(36)|0s`

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-03 09:51:24 +02:00
03fc5307e6 feat(services): add deleteVehicleService function to vehicle service
A new function `deleteVehicleService` is added to `vehicle.service.ts` to handle the vehicle deletion process. It will return true if the operation is successful, and false if it fails or encounters an error.

Issue: #21
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 16:51:32 +02:00
b6a2a74ba0 feat(services): add multiple vehicle services
Implemented `getAllVehiclesService`, `getVehicleByIdService` and `getAvailableVehicleService` in `vehicle.service.ts`. Each of these services fetches respective data from the MySQL database, handles errors, and returns the data along with a timestamp.

Issue: #21
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 16:49:13 +02:00
f310bedeff feat(services): add vehicle update functionality in vehicle.service.ts
An `updateVehicleService` function has been introduced in `vehicle.service.ts` to enable vehicle data updates in the application. The function handles data validation, updates the MySQL database, and logs the process status.

Issue: #21
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 16:39:50 +02:00
838ea4ad22 feat(services): add new vehicle service
This commit introduces a new file `vehicle.service.ts` which includes a function for creating new vehicles. It uses MySQL service for data handling and includes error handling and logging functionalities. The changes help in improving the code modularity and providing a specific service for vehicle management.

Issue: #21
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 16:37:28 +02:00
00064fd054 feat(controllers): tag functions to test in auth.controller.ts
Marked several functions in `auth.controller.ts` file with `//ToTest` comments indicating that these functions are ready for testing. The tagged functions are `getAllUsers`, `getUser`, `editUser`, `deleteUser`, `deleteSelf` and `getSelf`. This is important for ensuring that all these features function as expected.

Issue: #19
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 16:13:46 +02:00
70a6e5df54 refactor(controllers): enhance error handling in auth controller
Update error handling in `auth.controller.ts` to provide more accurate responses based on operation results. This includes rigorous checking of response types before proceeding with particular operations. Notable changes include switching from string errors to error codes, and ensuring necessary properties exist in objects before accessing them.

Issue: #19
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 16:12:21 +02:00
7f52a9d75e feat(controllers): improve response error handling in auth.controller
- Improve error handling in methods of the `auth.controller`
- Refactor conditional checks for errors to handle non-string types and missing payload data.
- Update response error messages to be more consistent and informative.

Issue: #19
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 15:41:38 +02:00
62742e6afe feat(services): update user service
- Moved import statement to top for better organization.
- Removed unnecessary comments and lines.
- Simplified return statements for clarity and efficiency.
- Streamlined deletion process by directly returning the result.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 15:40:48 +02:00
bdfc598218 feat(services): update user service dynamic types and enhance error handling
- Refactored `editUserService` function to use `IUserUpdate` type for `inputData` instead of `IDbUser`.
- Enhanced error handling in `editUserService` and `deleteUserService` by checking for affected rows in the returned result.
- Updated `deleteUserService` from using MongoDB method to MySqlService method.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 15:37:39 +02:00
1cbc771251 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 <yidhra@tuta.io>
2024-05-02 15:29:19 +02:00
8644b5add6 feat(services): update user.service.ts for better error handling and debugging
The updated `user.service.ts` now includes better error handling using optional chaining and also generates a JSDoc definition for HTML status codes. An extra logger trace has also been added, which provides token related information in debug mode. This will capture user login token details for debugging purposes. Also cleaned up code formatting for readability.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 15:28:45 +02:00
b66a5aff6d feat(interfaces): add HttpStatusCode enum
Added a new file `HttpStatusCode.ts` in the `interfaces` scope. This file defines an enum which represents all the possible HTTP Status Codes, enhancing readability and maintainability in the code base by replacing hard-coded numbers with meaningful names.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 14:37:46 +02:00
3d3da77df0 feat(services): add getByEmailService to user.service.ts
A new method `getByEmailService` has been added to `user.service.ts`. This method retrieves a user from the database by their email. The tracing log adjustment has also been performed, where logging only happens when it's in debug mode. If there are multiple users with the same email, this method will return the first user.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 14:25:10 +02:00
abaeafea8a feat(services-controllers): implement debug mode and refactor error handling
- Implement debug mode for logging in services and controllers.
- Improve the precision of memory usage information in the app by rounding.
- Refactor error handling in services, notably in user service register function for better error checking.
- Include a condition in the AdminGuard validator to check if the token is valid.
- Fix issue in auth controller that was misidentifying user registration error as a successful registration.
- Refactor log display in Mysql service error throwing for better readability.
- Refactor memory usage information display in app for better readability.

These changes aim to improve the clarity of logs and accuracy of error reporting for a better debugging experience. The implementation of debug mode gives more control over the information displayed in the development phase. Several fixes in error handling also contribute to a more robust system.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 14:18:28 +02:00
cb41c68f77 feat(utils): add debug state utility
This commit includes the creation of a new utility file for checking if the app is running in debugging mode. It exports `isDebugMode()` function that checks the `DEBUG` environment variable's value.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 14:11:48 +02:00
e98729c9e5 style(app): Remove newline from memory usage logging
This commit modifies the logging of memory and heap usage in the app.ts file. The redundant newline code `\n` at the end of the log message has been removed to enhance readability and neatness of the logs.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 13:31:56 +02:00
cd09a0c61b build(others): update TypeScript compiler options in tsconfig.json
This commit updates the TypeScript compilerOptions in tsconfig.json. The changes include higher module resolution targets, adjustments in various compiler constraints, and the modification of paths for baseUrl. This will help improve the code's compile-time and run-time behavior.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 13:12:54 +02:00
1723a8588a feat(controllers): update registerUser function in auth controller
This commit includes changes to improve the `registerUser` function in auth.controller.ts.
- Switched registration from displayName to email and added email validation
- Adjusted types and logging for better consistency and readability

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 12:23:20 +02:00
3fe6453b0c style(utils): improve code readability in email validator
Improved code readability by formatting the regular expression in the `isEmail` function in `utils/validators/email.ts` and also ensured a newline at the end of the file.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 12:23:05 +02:00
98477c5f27 feat(interfaces): update user and request interfaces
- Renamed `is_mail_verified` to `is_email_verified` in `IDbUser`
- Replaced `username` with `email` in `IReqLogin`
- Commented out `dob` in `IReqRegister`
- Added new field `_questionMarksFields` in `IDbFactorize`

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 12:22:43 +02:00
3472c59ac2 feat(services): refactor MysqlHandler to improve factorize method
Update the MysqlHandler class in the 'mysql.service.ts':
- Enhance the `factorize` method to handle 'id' in a special manner and create a string of '?' for prepared SQL queries.
- Refactor the `add` method to utilize the updated `factorize` method for constructing SQL queries.
- Update the return types of `getById` and `getByEmail` methods to return an array of IDbUser instead of a single IDbUser instance.
- Rename a private attribute 'Logger' to 'logger'.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 12:22:17 +02:00
3b6726113d feat(services): enhance user service functions
- Modified the logic in `getUserByEmail` to handle multiple user instances.
- Altered the dob field to save as current date in inserted user records.
- Richened `getByEmailService` function to check email existence in the database.
- Adjusted `is_mail_verified` flag to `is_email_verified` within user insertion function.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 12:21:37 +02:00
d78b0aec4c feat(utils): add email validator function
A new function `isEmail` has been added to the utils folder that validates if a given string input is a valid email address. This should help in reducing erroneous entries and improve data validation.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-05-02 09:59:51 +02:00
ae6b25fbd6 style(services): improve code readability in user.service file
The user.service has been reformatted for better readability. Changes mainly include adding new lines and spaces to make the code more structured. No change in logic involved, purely cosmetic.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:52:23 +02:00
d7f9cb0b37 feat(services): enhance readability and update factorize function for various elements in mysql.service
- The readability within mysql.service.ts file is improved by formatting multi-line functions.
- The factorize function for 'users', 'brands', 'vehicles', and 'categories' has been updated to enhance code quality.
- Import statements were restructured for better readability.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:51:55 +02:00
cb1c2ee87c feat(interfaces): add newline at end of IDbFactorize file
A newline has been added at the end of the `IDbFactorize.ts` file to conform to coding style conventions.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:51:13 +02:00
23ce32cb6f feat(services): refactor update methods in mysql service
This commit:
- Standardizes the data handling in all update methods, using `handler.factorize()`
- Moves gdpr date validation to the top of 'update' method to reject it earliest
- Removes the unused `_values` and `_template` variables.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:50:40 +02:00
f6d18fc58d feat(services): simplify SQL query keys generation in mysql service
Remove the condition to check 'id' while generating SQL query keys in the `mysql.service.ts`. Now, a map function is used directly resulting in cleaner and leaner code.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:39:43 +02:00
5163d79056 test(services): add DbHandler usage in user.service.ts
This commit includes:
- The usage of `DbHandler.factorize` with sample values
- Testing log trace to monitor the result and fields of factored user data

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:36:59 +02:00
ea3b7aa68b feat(services): prevent id field from being factorized in mysql service
In the `mysql.service.ts`, the `factorize` function has been updated to exclude the `id` field. This change ensures the 'id' field is not injected into the result to avoid any potential issues. Furthermore, The update operation in the same file has been refactored to use the updated `factorize` function, hence enhancing code reusability.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:35:51 +02:00
bc12f94e41 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>
2024-04-30 16:19:12 +02:00
df28d3aa52 feat(services): add factorize method and refactor update method in mysql.service
- The `factorize` method was added to `mysql.service.ts` to convert input data into a database query.
- The `update` method was refactored; unnecessary data fields were removed and a check for the `gdpr` field was added. A corresponding interface, `IUserUpdate`, was also imported at the beginning of the file.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:15:19 +02:00
3d5ea6ac30 feat(interfaces): add IUserUpdate interface
This commit introduces a new interface named IUserUpdate. This interface consists of optional properties like id, username, firstname, lastname, dob and gdpr. These properties are used for validating user's data during update operation.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:14:47 +02:00
2fb6cd6e83 feat(interfaces): add IDbFactorize interfaces
Added IDbFactorizeOutput and IDbFactorizeInput interfaces to handle inputs and outputs of the SQL query factorization function. Both interfaces contain various properties for customizing the SQL query and its error handling behavior.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 16:14:29 +02:00
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
0635c512cc feat(services): add memory usage info, update user, enhance log and register function
- Added log for loaded services
- Implemented feature to update user in MySQL service
- Added memory usage information in the server start log
- Enhanced registration function in user service to provide more detailed response and log information
- Minor code formatting improvements

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 14:08:00 +02:00
3232e5fac1 feat(services): improve UserService with enhanced error handling and data validation
This commit includes updates to `user.service.ts` that enhance error handling and data validation. The `getUserFromUsername` function has been renamed to `getUserByEmail` reflecting the new identifier being used to retrieve users. Both `getUserByEmail` and `getUserFromIdService` have been revised to include error handling and logging capability. The `register` function now incorporates stronger validation checks and proper GDPR verification. It is also updated to return JWT token on successful registration.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 12:38:26 +02:00
9225337e95 feat(interfaces): update IReqRegister interface
The `IReqRegister` interface was modified. It no longer includes the `displayName` field, but `dob`, `email`, and `password` fields were added. The 'password' property was moved to the end of the interface.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 12:37:24 +02:00
f1272ca2c8 refactor(interfaces): remove unnecessary quotes in ErrorType enum
Removed unnecessary quotes from the keys of the `ErrorType` enum in the `ISError.ts` file, making the code more readable and conforming to TypeScript's best practices.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 12:35:27 +02:00
4fd6c11326 feat(interfaces): add ISError interface and ErrorType enum
A new interface `ISError` has been added to represent error objects. Also, an enumeration `ErrorType` has been included to cover all possible types of errors that may occur in the application. This improves the structuring of error handling in the project.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 11:57:22 +02:00
355cb0ec90 refactor(services): refactor method arguments and promises handling in mysql.service.ts
- Compact multiple lines codes into single lines for readability and simplicity.
- Replace some Promise responses from unknown to specific IDbStatusResult for a more predictable result.
- In `insert`, `update`, `getById` and `delete` methods for `brands`, `categories`, `models`, `vehicles` and `users`, modify the way promises are handled and return values.
- This makes the code more readable and easier to understand.

Issue: #17
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 11:42:05 +02:00
19d265a0e6 refactor(services): simplify function calls and logging statements in user.service.ts
This commit simplifies multi-line function calls and logging statements in `user.service.ts` to be just single-line. This change enhances the readability and maintainability of the code by reducing unnecessary lines and making the function calls and logging statements more straightforward.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 11:05:34 +02:00
a6593cb76f feat(all): refactor code for readability and simplicity
The controllers, services, routes, and others have been updated to reduce code complexity and improve readability. Changes include removing unnecessary lines, replacing long function signatures with simpler versions, and streamlining condition checks and logger statements.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 11:03:28 +02:00
28671146d1 feat(others): Add BiomeSettings in .idea/biome.xml
A new file `.idea/biome.xml` is added with `BiomeSettings` component for the project. It also includes MANUAL configuration with enabling formatOnSave option.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 10:56:39 +02:00
56bfd8cd0d type: style
scope: services, interfaces

subject: Apply code formatting

- Correct indentation and formatting to match code style standards in multiple 'interfaces' and 'services' files.
- Also ensure lines at the end of the files.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 10:55:37 +02:00
cda313866f feat(services): update return type in mysql.service
The return type for the update function in mysql.service is updated from Promise<unknown> to Promise<IDbStatusResult>. Also, the function resolves with the status result of the update instead of just being successful.

Issue: #17
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 10:01:14 +02:00
91b88ea592 chore: Bump package version to 1.1.0
The package.json file has been updated, specifically the version of the project has been changed from 1.0.0 to 1.1.0.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 09:59:24 +02:00
44b5745f3b feat(services): update insert function in mysql service
The `insert` function in mysql service has been updated. The function now includes more accurate commenting, and a check for `id` validity. It now returns a Promise with `IDbStatusResult` instead of `unknown`. The changes improve function clarity and error handling.

Issue: #17
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-30 09:59:06 +02:00
a9cf48b04a docs(services): update MySqlService in mysql.service.ts
Added a comment to test all users in MySqlService. Also, removed a redundant comment in Vehicle's MySqlService. All changes are seen in mysql.service.ts.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 15:00:33 +02:00
16a8e892f9 feat(others): Update database collations
The collations in the database have been updated, changing from `armscii8` to new ones such as `big5`, `dec8`, `cp850`, and others. This should help to smooth internationalization and localization processes involving different charsets.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 15:00:04 +02:00
89ba2cb6d1 feat(others): add new SQL dump file
The `db.sql` file was newly created which contains the structure for several tables namely `brands`, `categories`, `models`, `rent`, `users`, and `vehicles`. The tables contain relationships and constraints among them and is a MariaDB dump prepared for a Linux system.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 14:58:44 +02:00
47bb7aacdf refactor(services): add IDbVehicle import in mysql.service.ts
This commit includes changes to the import lines in 'mysql.service.ts'. Import of 'IDbVehicle' from '@interfaces/database/IDbVehicle' has been moved to maintain alphabetical order of the imports.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 13:40:46 +02:00
88e89f0551 feat(database): update database schema and sql dialect settings
- Added new procedures `CheckVehicleAvailability` and `IsVehicleAvailable` to the database schema.
- Introduced an `isAvailable` column to the `vehicles` table.
- Updated sql dialect settings to include a new console file for the database.
- Reflected changes also include id adjustments and table modifications.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 13:39:28 +02:00
9a6d7a73b2 feat(services): update getAvailable method in mysql.service
- Transform `getAvailable` method into a promise that retrieves available vehicles from the database.
- This method now takes the MySQL handler object to execute the query and returns a promise that resolves to an array of available vehicles.
- It also handles error while executing the query.

Issue: #20
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 13:38:15 +02:00
89d9fc47b2 feat(database): update database configuration
Update the database configuration like updating the LastIntrospectionLocalTimestamp and refactoring indexes and keys. This includes adding two new indexes `users_pk` and `users_pk_2`, and corresponding keys. Moreover, the columns' ID have been updated. These changes are done for better database performance and clear structuring.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 13:09:54 +02:00
37cfaf4bbd feat(services): implement getById and getAll methods in mysql service
Implemented getById and getAll methods in the mysql service. The getById method retrieves a vehicle by its ID, and the getAll method fetches all vehicles from the database. These improvements facilitate more efficient data retrieval and enhance overall service functionality.

Issue: #20
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 12:23:13 +02:00
8138231112 feat(data-grip): Add new data source and schema metadata files
Two new files have been added: `brief_05.6nelCw.meta` for schema metadata, and `1f2800b5-8649-4a80-a9ec-b7b2a24623b4.xml` for defining the data source. These configurations support the interaction with our database in different environments.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 12:12:50 +02:00
edf2f6880c feat(services): enhance insert and update methods in mysql.service.ts
- Enhanced `insert` and `update` methods in `mysql.service.ts`
- Added detailed documentation and type checks for `insert` function.
- Implemented body of `update` method, included parameter validation and SQL query building.

Issue: #20
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 12:05:34 +02:00
cd4b8479d2 fix(scripts): correct 'bun:dev' script in package.json
The previous `bun:dev` script was missing the key command `bun run`. This has been corrected for proper execution of the script in the development mode.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 11:44:58 +02:00
d05c7efadc feat(package.json): change main entry point and add development script
This commit modifies the `package.json` file in two key ways. The main file is now `src/app.ts` instead of `dist/app.js`. Additionally, a new script `bun:dev` has been added to aid in development, which watches the main file for changes.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 11:35:44 +02:00
8fe0fa57d8 feat(services): add Vehicle methods to mysql service
This commit introduces several new Vehicle methods to the mysql service (insert, update, getBySlug, getAll, getAvailable, getDue). These methods are scaffolded but not fully implemented, with `insert` being the most complete at this stage.

Issue: #20
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 11:31:50 +02:00
2796b514eb feat(routes): add BrandController methods to catalog routes
The commit introduces BrandController methods to the catalog router. Specifically, it implements create, getAll, getBySlug, update, and delete functionalities for brand routes. This update enhances brand management within the catalog.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 11:30:59 +02:00
0053c0ce19 feat(interfaces): add IDbVehicle interface
Add a new interface called `IDbVehicle` in the interfaces module. The `IDbVehicle` interface includes properties such as `id`, `plate_number`, `model_id`, `odometer`, and `health_state` for defining a vehicle object in our database.

Issue: #20
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 11:30:13 +02:00
041e77efcd feat(controllers): include request param to getAll functions
Changes include the inclusion of the request parameter (`_req`) in the `getAllBrand` and `getAllCategory` functions and some code format adjustment in `category.controller.ts`. This addition allows more flexibility in handling the request if needed in the future. The `total` field has also been added to the category output to conveniently provide category count.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 11:29:29 +02:00
915b205b6e chore(others): add mariadb to .gitignore
The .gitignore file was updated to ignore the `mariadb` file, preventing it from being tracked by version control. This ensures database related elements are ignored for smoother version control operations.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 09:54:20 +02:00
4ff1aa852d feat(app): add handling for app port from environment variable
This commit imports the `process` module from `node:process` and changes the app to listen on a port specified by an environment variable (APP_PORT). It also adds an error handler to stop the server and log the error message if the server fails to start.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 09:54:04 +02:00
c024770b4a feat(services): handle MySQL connection errors with process exit
Updated `mysql.service.ts` to handle MySQL connection errors by terminating the process instead of throwing a generic error. This change provides a more direct response to database connection failures.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 09:53:18 +02:00
37ec62405e feat(routes): update catalog routes with ModelController methods
A ModelController has been imported and its methods applied to the catalogue routes in the system. The changes include methods for creating, getting all, getting by slug, updating, and deleting models. Prior routes have been updated and enhanced with these additional controller methods for improved performance.

Issue: #28
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 09:52:43 +02:00
57151ec777 feat(controllers): add new TODO in model.controller
- The code update adds a new `TODO` comment in the `model.controller.ts` file.
- The new `TODO` is about getting a model with available vehicles.

Issue: #28
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 09:51:59 +02:00
0f8fd9a3e8 feat: Add Docker compose file for database setup
Add a `docker-compose.yml` file to ease local development. The file specifies configuration for a MariaDB container that will now serve as our database server. Environment variables and ports are also configured in the file.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-29 09:39:15 +02:00
80dff138cc chore(others): add dist to .gitignore
The update now includes `dist` directory in the `.gitignore` file to avoid pushing compiled files to the repository.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 16:22:30 +02:00
0887fe213f feat(services): update JWT methods in jwt.service.ts
Update the methods related to JWT in `jwt.service.ts`. Import and utilize `jwtVerify` and `SignJWT` from the "jose" package, replacing their previous counterparts. This refactors the `JwtVerifyService` and `JwtSignService` functions for better JWT handling.

Issue: #30
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 16:22:14 +02:00
3231f916f8 feat(validators): add check for non-existent userId
- The `UserGuard` now checks whether the `userId` extracted from the token exists.
- If the `userId` does not exist, an error is logged and a response with the 'Unauthorized' status is returned.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 15:44:01 +02:00
a74731a49e feat(controllers): add ModelController with CRUD methods
This commit adds a new controller, `ModelController`, to the controllers directory. This controller includes create, read, update and delete (CRUD) methods for handling 'model' related operations. Each method incorporates appropriate error handling and logging.

Issue: #28
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 14:09:29 +02:00
9bdcdef88f feat(routes): apply routers and rename files
- Routers `AuthRouter`, `CatalogRouter`, and `RentRouter` are used in the application in `src/app.ts` with error handling.
- The router files under `src/routes/auth`, `src/routes/catalog`, and `src/routes/rent` are renamed to `authRouter.ts`, `catalogRouter.ts`, `rentRouter.ts` respectively.
- The default exports in these router files have been updated to reflect their new names.
- The imports in `src/routes/index.ts` are updated according to the renamed files.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 12:21:13 +02:00
5d53cd28f8 refactor(services): update methods in ModelService
Removed `getByIdModel`, `getByCategoryModel`, and `getModelsByBrand` methods. These changes are to refine the methods available in ModelService for better usage and understanding.

Issue: #27
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 12:13:28 +02:00
8711b3530a feat(services): add fetch functions for models
Added two new functions, `getBySlugModel` and `getAllModels`, to the `ModelService` in `model.service.ts`. `getBySlugModel` fetches a model based on its slug while `getAllModels` fetches all models from the database.

Issue: #27
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 12:12:03 +02:00
13d72ad529 feat(services): update logging in updateModel and add deleteModel
The `updateModel` function's logging has been updated to use `slug_name` instead of `id`. Additionally, a new function `deleteModel` has been added to remove models from the database based on their slug. This function checks the existence of a model before attempting deletion and logs the process.

Issue: #27
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 12:06:19 +02:00
f23aabccd4 feat(services): add updateModel function and refine createModel function in model.service.ts
This commit mainly includes adding `updateModel` function, which updates a model in the database and refines `createModel` function in `model.service.ts`. It provides more clear explanations before the function and also specifies the return data type.

Issue: #27
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 11:58:04 +02:00
c70bcef352 feat(services): add model service
This commit adds a new service `ModelService` to handle model operations such as create, update, and delete. Some features like getBySlug, getAll, getById, getByCategory, and getByBrand are also introduced.

Issue: #27
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 11:53:42 +02:00
30bd5a0dbe feat(services): update database operations return type in mysql.service
This commit includes significant changes in `mysql.service.ts` in the `services` scope.
- Imported `IDbStatusResult` type.
- Updated the return type of all operations (insert, update, delete) on both `Brand` and `Model`. These operations now return `Promise<IDbStatusResult>` instead of `Promise<unknown>` or `Promise<number>`.
- Also, adjusted the functions documentations to reflect these changes, providing more clarity about the returned result from database operations.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 11:34:15 +02:00
61c546459a feat(interfaces): add IDbStatusResult interface
A new interface, `IDbStatusResult`, has been added to the 'interfaces' scope. This interface includes details about database operations such as field count, affected rows, insert id, info, server status, warning status, and changed rows.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 11:18:41 +02:00
adaf4e30db feat(services): update MySQL services with method adjustments
- Modified 'getById' reject message for accuracy.
- Adjusted 'getBySlug' method parameters for clarity and renamed the field used in the SQL statement.
- Added a new 'getById' method for retrieving models by ID.
- Updated the 'insert' method to correctly insert data into the `models` table.
- Refactored 'getBySlug' method for categories, renaming the parameter and making error messages more precise.

Issue: #7
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 11:09:26 +02:00
2f4ad31edf docs(controllers): add TODO comment for future feature in brand controller
In the brand controller file, a comment was added indicating a future enhancement to implement the functionality that fetches all models of a specific brand.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 10:29:07 +02:00
c25b204c67 feat(controllers): add BrandController with CRUD operations
This commit introduces a new `BrandController` in the controllers folder, offering key Create, Read, Update, and Delete (CRUD) operations. This includes `createBrand`, `updateBrand`, `getBySlugBrand`, `getAllBrand`, and `deleteBrand` methods, providing better control and operation of brands within the application.

Issue: #14
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 10:26:38 +02:00
0a6321deb0 feat(services): mark blob validation as TODO in brand service
A TODO comment has been added to the `brand.service.ts` file indicating that blob validation needs to be implemented in the future, for both the 'create' and 'update' functions in the brand service.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 10:00:55 +02:00
a811a2ece2 docs(services): add tasks to MySQL and Category services
An additional task is marked in the MySQL service to check if models are linked before taking further actions. Similarly, in the Category service, two tasks are added to verify the existence of a category and elements linked to it before proceeding with deletion.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 09:57:44 +02:00
85adbbcdb3 docs(services): add new tasks in brand.service.ts
A couple of new tasks have been added into the `brand.service.ts` file.
The tasks aim to fetch models and stats of a certain brand.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 09:53:27 +02:00
5c0d266002 feat(services): add deleteBrand function to brand.service.ts
The `brand.service.ts` file is updated to include a new function `deleteBrand`, which allows a brand to be deleted from the database by its ID. Appropriate error handling and activity logging have been incorporated to ensure smooth operation.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 09:52:09 +02:00
6c626e0b18 feat(services): add getByIdBrand function in brand service
A new function, `getByIdBrand`, has been added to the brand service. This function retrieves a brand from the database based on the provided brand ID, Checks are performed on the brand ID before the retrieval attempts.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 09:41:37 +02:00
aecaf83d85 feat(services): add getBySlugBrand function in brand.service
Adds a new function `getBySlugBrand` in `brand.service.ts`. This function retrieves a brand by its slug, providing a more specific search option. It implements error logging for missing slug or brand not found scenarios, and successful retrieval of the brand.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 09:40:24 +02:00
33d44ee4b6 feat(services): add getAllBrand function in BrandService
The BrandService in the services module now includes a new functionality - the getAllBrand function. This function retrieves all brands from the database. Loggers have been added to check successful retrieval and error handling.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 09:35:55 +02:00
90cd80e540 feat(services): add updateBrand function to BrandService
A new function `updateBrand` has been introduced to `BrandService`. This function handles updating a brand in the database, including checks for missing `id`, brand existence by `slug_name`, and logging for successful or failed updates.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-26 09:30:03 +02:00
f3bddc7170 feat(services): change IDbBrand import to explicit type import
Change the import of `IDbBrand` in `brand.service.ts` to explicitly denote it as a type. This clarifies that `IDbBrand` is only used for its type information, improving readability and understanding of code usage.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:57:39 +02:00
789d62ea62 feat(services): refactor register and login functions in UserService
- Removed extraneous comments and updated function parameters to use interface types in `register` and `login` methods.
- Renamed `RegisterService` and `LoginService` to `register` and `login` respectively.
- This update enhances readability and maintains the consistency in code by leveraging TypeScript's type-checking feature.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:57:21 +02:00
debb30b896 refactor(services): remove duplicate comments in category.service.ts
Duplicate comments were found and removed from `category.service.ts`. This refactoring step will make the code cleaner and less confusing.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:57:10 +02:00
6e429f4f27 feat(services): add new BrandService with createBrand function
This commit introduces the new file `brand.service.ts` under services. Specifically, it implements the `createBrand` method which handles the creation of a new brand instance in the database if there's no existing brand with the same slug name. The function returns a promise containing the operation result. A UUID is also generated as `brandId`, and several logs will be recorded in different situations.

Issue: #13
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:56:50 +02:00
b1dacb750a feat(services): add delete function in mysql service
A new delete function has been added to the `mysql.service.ts` file under services. This function deletes a brand from the database using the brandId. It throws an error if the brandId is undefined or invalid.

Issue: #5
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:40:42 +02:00
0ed130f7b8 feat(services): update MySQL service to fetch brand data
- Corrected the SQL query in fetching brand data based on ID.
- Added a new function, `getBySlug`, to fetch brand data using the brand slug. This ensures a flexible data access method thus enhancing the service's usability.

Issue: #5
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:38:41 +02:00
50c1ff797f feat(services): add getById function to mysql.service
The code now includes a `getById` function in `mysql.service.ts`, which fetches a category by its id. This function will reject with an error message if brandId is not defined or if its length is not 36. The query is executed using a MysqlHandler instance.

Issue: #5
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:36:12 +02:00
2640ebbc70 feat(controllers): add detailed comment to getBySlugCategory function
The `getBySlugCategory` function in `category.controller.ts` now has a detailed comment. The comment includes a description of the function, its parameters, and its return value.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:31:33 +02:00
a992868eb6 feat(services): add 'getAll' function to mysql.service.ts
A new function named `getAll` has been added to `mysql.service.ts`. This function is designed to retrieve all records from the `brands` table. It returns a promise that resolves to an array of `IDbBrand` objects representing the retrieved records.

Issue: #5
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:29:39 +02:00
f3fc63502d feat: Replace express-xss-sanitizer with helmet
The security middleware `express-xss-sanitizer` has been replaced with `helmet` for enhanced security measures. `helmet` provides better protection against potential security vulnerabilities.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:26:52 +02:00
44b04459fb feat(services): add detailed comments and specify return types in mysql.service
In the `mysql.service`, this commit adds detailed doc-block comments for `insert` and `update` functions under `Brand` and `Category`. Additionally, the return types of the `update` functions under `Brand` and `Category` are now specifically defined as `Promise<number>`, providing clarity on the expected returns.

Issue: #5
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:26:29 +02:00
64aa814d2c feat(services): add update method to mysql service
An `update` method is added to the `mysql.service.ts` for handling database updates. This new function checks for validity of the `id` before constructing and executing an SQL update statement. Errors are also caught and handled.

Issue: #5
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:24:02 +02:00
016f7fa9d4 feat(services): add brand insert method in mysql service
- Adjusted import syntax for IDbCategory interface
- Imported IDbBrand from database interfaces
- Added a new `insert` method in Brand object in MysqlHandler to insert brand data into the database. The method resolves with execution of SQL command and rejects on error or invalid ID.

Issue: #5
Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:19:27 +02:00
01241dff4b fix(services): correct database table name in mysql.service.ts
Update the hardcoded table name in the raw SQL query within mysql.service.ts. We changed `categorys` to `categories` to align with the actual database schema.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:07:29 +02:00
dda3eea9e1 feat(interfaces): add IReqRegister interface
This interface defines the register request structure, including `username`, `displayName`, `firstName`, `lastName`, `password`, and `gdpr` status. This will ensure consistency in the form of request data throughout the application.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:06:33 +02:00
a430044d95 feat(interfaces): add IReqLogin interface
The new IReqLogin interface defines the request shape for user login. It includes two string properties - username and password.

Signed-off-by: Mathis <yidhra@tuta.io>
2024-04-25 16:06:02 +02:00
59 changed files with 5540 additions and 1147 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
node_modules
pnpm-lock.yaml
.env
dist
mariadb

8
.idea/biome.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BiomeSettings">
<option name="configPath" value="$PROJECT_DIR$" />
<option name="configurationMode" value="MANUAL" />
<option name="formatOnSave" value="true" />
</component>
</project>

22
.idea/dataSources.local.xml generated Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="DB-241.14494.283">
<data-source name="@docker/brief05" uuid="1f2800b5-8649-4a80-a9ec-b7b2a24623b4">
<database-info product="MariaDB" version="10.3.39-MariaDB-1:10.3.39+maria~ubu2004" jdbc-version="4.2" driver-name="MariaDB Connector/J" driver-version="3.3.3" dbms="MARIADB" exact-version="10.3.39" exact-driver-version="3.3">
<extra-name-characters>#@</extra-name-characters>
<identifier-quote-string>`</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>user_brief05</user-name>
<schema-mapping>
<introspection-scope>
<node kind="schema">
<name qname="@" />
<name qname="brief_05" />
</node>
</introspection-scope>
</schema-mapping>
</data-source>
</component>
</project>

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="@docker/brief05" uuid="1f2800b5-8649-4a80-a9ec-b7b2a24623b4">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://localhost:3434/brief_05</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
#n:brief_05
!<md> [0, 0, null, null, -2147483648, -2147483648]

View File

@@ -3,7 +3,7 @@
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="114" name="TypeScript" />
<language minSize="174" name="TypeScript" />
</Languages>
</inspection_tool>
</profile>

7
.idea/sqldialects.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/1f2800b5-8649-4a80-a9ec-b7b2a24623b4/console.sql" dialect="MariaDB" />
<file url="PROJECT" dialect="MariaDB" />
</component>
</project>

View File

@@ -3,6 +3,15 @@
"organizeImports": {
"enabled": true
},
"files": {
"include": [
"./src/**/*.ts"
]
},
"vcs": {
"enabled": true,
"clientKind": "git"
},
"linter": {
"enabled": true,
"rules": {
@@ -17,8 +26,8 @@
}
},
"formatter": {
"indentStyle": "space",
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 15
"lineWidth": 80
}
}
}

155
db.sql Normal file
View File

@@ -0,0 +1,155 @@
-- MariaDB dump 10.19-11.3.2-MariaDB, for Linux (x86_64)
--
-- Host: 127.0.0.1 Database: brief_05
-- ------------------------------------------------------
-- Server version 10.3.39-MariaDB-1:10.3.39+maria~ubu2004
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `brands`
--
DROP TABLE IF EXISTS `brands`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `brands` (
`id` varchar(36) NOT NULL,
`slug_name` varchar(32) NOT NULL,
`display_name` varchar(32) NOT NULL DEFAULT `slug_name`,
`image_blob` blob DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug_name` (`slug_name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `categories`
--
DROP TABLE IF EXISTS `categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `categories` (
`id` varchar(36) NOT NULL,
`slug_name` varchar(32) NOT NULL,
`display_name` varchar(32) NOT NULL DEFAULT `slug_name`,
PRIMARY KEY (`id`),
UNIQUE KEY `categories_pk` (`slug_name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `models`
--
DROP TABLE IF EXISTS `models`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `models` (
`id` varchar(36) NOT NULL,
`slug_name` varchar(32) NOT NULL,
`display_name` varchar(32) NOT NULL DEFAULT `slug_name`,
`brand_id` varchar(36) DEFAULT NULL,
`category_id` varchar(36) DEFAULT NULL,
`image_blob` blob DEFAULT NULL,
`is_trending` tinyint(1) DEFAULT 0,
`base_price` float NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug_name` (`slug_name`),
KEY `models_brands_id_fk` (`brand_id`),
KEY `models_categories_id_fk` (`category_id`),
CONSTRAINT `models_brands_id_fk` FOREIGN KEY (`brand_id`) REFERENCES `brands` (`id`) ON UPDATE CASCADE,
CONSTRAINT `models_categories_id_fk` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `rents`
--
DROP TABLE IF EXISTS `rents`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `rents` (
`vehicle_id` varchar(36) NOT NULL,
`user_id` varchar(36) NOT NULL,
`active` tinyint(1) NOT NULL,
`iat` date NOT NULL,
`eat` date NOT NULL,
`need_survey` tinyint(1) DEFAULT NULL,
`km_at_start` int(11) DEFAULT NULL,
`id` varchar(36) NOT NULL,
PRIMARY KEY (`id`),
KEY `rent_vehicles_id_fk` (`vehicle_id`),
KEY `rent_users_id_fk` (`user_id`),
CONSTRAINT `rent_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE,
CONSTRAINT `rent_vehicles_id_fk` FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
`id` varchar(36) NOT NULL,
`username` varchar(12) NOT NULL,
`firstname` varchar(16) DEFAULT NULL,
`lastname` varchar(16) DEFAULT NULL,
`dob` date DEFAULT NULL,
`email` varchar(32) NOT NULL,
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
`gdpr` date NOT NULL,
`hash` varchar(97) NOT NULL,
`is_email_verified` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `users_pk` (`id`),
UNIQUE KEY `users_pk_2` (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `vehicles`
--
DROP TABLE IF EXISTS `vehicles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `vehicles` (
`id` varchar(36) NOT NULL,
`plate_number` varchar(8) DEFAULT NULL,
`model_id` varchar(36) NOT NULL,
`odometer` int(11) NOT NULL DEFAULT 0,
`health_state` float NOT NULL,
`isAvailable` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `vehicles_models_id_fk` (`model_id`),
CONSTRAINT `vehicles_models_id_fk` FOREIGN KEY (`model_id`) REFERENCES `models` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2024-05-03 14:56:07

18
docker-compose.yml Normal file
View File

@@ -0,0 +1,18 @@
version: '3.3'
services:
#serveur de base de donnees
database:
image: 'mariadb:10.3'
container_name: database
restart: always
environment:
MYSQL_USER: 'user_brief05'
MYSQL_PASSWORD: '1234567890'
MYSQL_DATABASE: 'brief_05'
MYSQL_ROOT_PASSWORD: '0987654321'
ports:
- '3434:3306'
volumes:
- ${PWD}/mariadb/:/var/lib/mysql/

View File

@@ -1,18 +1,22 @@
{
"name": "brief-05-back",
"version": "1.0.0",
"version": "1.1.0",
"description": "",
"main": "dist/app.js",
"main": "src/app.ts",
"keywords": [],
"author": "Mathis HERRIOT",
"license": "MIT",
"scripts": {
"bun:dev": "bun run --watch src/app.ts",
"bun:check": "bunx biome check --skip-errors --apply src"
},
"dependencies": {
"@node-rs/argon2": "^1.8.3",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.19.2",
"express-validator": "^7.0.1",
"express-xss-sanitizer": "^1.2.0",
"helmet": "^7.1.0",
"jose": "^5.2.4",
"morgan": "^1.10.0",
"mysql2": "^3.9.7",

BIN
schema-db.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

View File

@@ -1,17 +1,22 @@
import express, { type Express } from 'express';
import cors from 'cors';
import compression from 'compression';
import {Logger} from "tslog";
import * as process from "node:process";
import AuthRouter from "@routes/auth/authRouter";
import CatalogRouter from "@routes/catalog/catalogRouter";
import RentRouter from "@routes/rent/rentRouter";
import compression from "compression";
import cors from "cors";
import express, { type Express } from "express";
import helmet from "helmet";
import { Logger } from "tslog";
const logger = new Logger({ name: "App" });
const logger = new Logger({
name: "App",
});
const app: Express = express();
// enable cors
app.use(cors());
app.options('*', cors());
app.options("*", cors());
// enable xss sanitizer
app.use(
@@ -19,18 +24,41 @@ app.use(
xXssProtection: true,
}),
);
app.use(helmet.xXssProtection())
app.use(helmet.xXssProtection());
// parse json request body
app.use(express.json());
// parse urlencoded request body
app.use(express.urlencoded({ extended: true }));
app.use(
express.urlencoded({
extended: true,
}),
);
// gzip compression
app.use(compression())
app.use(compression());
//app.use('/auth', AuthRoutes)
try {
app.use("/auth", AuthRouter);
app.use("/catalog", CatalogRouter);
app.use("/rent", RentRouter);
logger.info("Routers loaded !");
} catch (err) {
logger.error(err);
throw null;
}
//app.listen(3333)
logger.info('Server is running !')
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`,
);
} catch (error) {
logger.error(`Server failed to start: ${error}`);
process.exit(1);
}

View File

@@ -1,83 +1,96 @@
import JwtService from "@services/jwt.service";
import type {IReqEditUserData} from "@interfaces/IReqEditUserData";
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 type {Request, Response} from "express";
import {Logger} from "tslog";
import { isDebugMode } from "@utils/debugState";
import { isEmail } from "@utils/validators/email";
import type { Request, Response } from "express";
import { Logger } from "tslog";
const logger = new Logger({
name: "AuthController",
});
const logger = new Logger({ name: "AuthController" });
//FIX Better return object interface
/**
* Registers a user with the given request data.
* Registers a user.
*
* @param {Request} req - The request object containing user data.
* @param {Response} res - The response object to send the registration result.
* @param {Request} req - The request object.
* @param {Response} res - The response object.
*
* @return {Promise} A promise that resolves to the registration result.
* It can have the following properties:
* - error: "gdprNotApproved" if GDPR is not approved
* - error: "exist" if the user already exists
* - Otherwise, the registered user data
* @return {Promise} - A promise that resolves with the registration result or rejects with an error.
*/
async function registerUser(req: Request, res: Response): Promise<unknown> {
const body = req.body;
async function registerUser(req: Request, res: Response): Promise<Response> {
const body: IReqRegister = req.body;
if (!body) {
logger.warn(`Invalid input data (${req.ip})`);
return res
.type('application/json')
.status(400)
.json({ error: 'Invalid input data' });
return res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "Invalid input data",
});
}
if (!body.password || !body.username || !body.firstName || !body.lastName || !body.displayName) {
if (
!body.password ||
!body.username ||
!body.firstName ||
!body.lastName ||
!body.email
) {
logger.warn(`Field(s) missing (${req.ip})`);
return res
.type('application/json')
.status(400)
.json({ error: 'Field(s) missing' });
return res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "Field(s) missing",
});
}
let gdpr = false
if (body.gdpr === true) {gdpr = true}
const sanitizeData= {
username: `${body.username}`,
displayName: `${body.displayName}`,
gdpr: gdpr,
password: `${body.password}`,
firstName: `${body.firstName}`,
lastName: `${body.lastName}`,
if (!isEmail(body.email)) {
logger.warn(`Invalid email format (${req.ip})`);
return res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "Invalid email format",
});
}
let gdpr = false;
if (body.gdpr === true) {
gdpr = true;
}
const sanitizeData: IReqRegister = {
username: `${body.username}`,
email: `${body.email.toLowerCase()}`,
gdpr: gdpr,
password: `${body.password}`,
firstName: `${body.firstName}`,
lastName: `${body.lastName}`,
};
const RegisterServiceResult = await UserService.register(sanitizeData)
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({
error: RegisterServiceResult.error,
message: "GDPR not accepted."
});
return res.status(HttpStatusCode.BadRequest).json({
error: RegisterServiceResult.error,
message: "GDPR not accepted.",
});
}
if (RegisterServiceResult.error === "exist") {
logger.warn(`The user already exists (${req.ip})`);
return res
.type('application/json')
.status(400)
.json({
error: RegisterServiceResult.error,
message: "The user already exists."
});
if (
typeof RegisterServiceResult !== "string" &&
RegisterServiceResult.error === 5
) {
logger.warn(`The user already exists (${sanitizeData.email})`);
return res.type("application/json").status(HttpStatusCode.Conflict).json({
error: RegisterServiceResult.error,
message: "The user already exists.",
});
}
// SUCCESS
logger.info(`User registered successfully (${req.ip})`);
//logger.info(`User registered successfully (${sanitizeData.username})`);
return res
.type('application/json')
.status(201)
.json(RegisterServiceResult);
.type("application/json")
.status(HttpStatusCode.Ok)
.json({ token: RegisterServiceResult });
}
/**
@@ -89,198 +102,201 @@ async function registerUser(req: Request, res: Response): Promise<unknown> {
* @return {Promise<void>} A promise that resolves when the user is logged in or rejects with an error.
*/
async function loginUser(req: Request, res: Response): Promise<void> {
const body = req.body;
if (!body) {
res
.type('application/json')
.status(400)
.json({ error: 'Invalid input data' });
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({ error: 'Field(s) missing' });
res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "Field(s) missing",
});
}
const loginData = {
username: `${body.username}`,
password: `${body.password}`
email: `${body.email}`,
password: `${body.password}`,
};
console.log(body)
console.log(body);
const LoginServiceResult = await UserService.login(loginData);
console.log(LoginServiceResult)
console.log(LoginServiceResult);
if (LoginServiceResult.error === "userNotFound") {
console.log('POOL')
res
.type('application/json')
.status(404)
.json({
error: LoginServiceResult.error,
message: "User not found."
});
if (
typeof LoginServiceResult !== "string" &&
LoginServiceResult.error === 3
) {
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({
error: LoginServiceResult.error,
message: "Invalid password."
});
if (
typeof LoginServiceResult !== "string" &&
LoginServiceResult.error === 5
) {
res.type("application/json").status(HttpStatusCode.NotAcceptable).json({
error: LoginServiceResult.error,
message: "Invalid password.",
});
}
res
.type('application/json')
.status(200)
.json(LoginServiceResult);
res.type("application/json").status(200).json(LoginServiceResult);
}
//ToTest
async function getAllUsers(req: Request, res: Response) {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(' ')[1];
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)
const sourceUser = await UserService.getFromId(payload.sub);
if (!sourceUser) {
return res
.type('application/json')
.status(404)
.json({ error: 'You dont exist anymore' });
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({ error: 'Unauthorized' });
if ("id" in sourceUser && !sourceUser.is_admin) {
return res.type("application/json").status(HttpStatusCode.Forbidden).json({
error: "Unauthorized",
});
}
const AllUserResponse = await UserService.getAll()
if (!AllUserResponse.users) {
const AllUserResponse = await UserService.getAll();
if (typeof AllUserResponse === "object") {
return res
.type('application/json')
.status(500)
.json({ error: 'Internal server error' });
.type("application/json")
.status(HttpStatusCode.InternalServerError)
.json({
error: "Internal server error",
});
}
return res
.type('application/json')
.status(200)
.type("application/json")
.status(HttpStatusCode.Found)
.json(AllUserResponse);
}
//ToTest
async function getUser(req: Request, res: Response) {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(' ')[1];
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' });
.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({ error: 'Unauthorized' });
return res
.type("application/json")
.status(HttpStatusCode.Unauthorized)
.json({
error: "Unauthorized",
});
}
const sourceUser = await UserService.getFromId(payload.sub)
const sourceUser = await UserService.getFromId(payload.sub);
if (!sourceUser) {
return res
.type('application/json')
.status(404)
.json({ error: 'You dont exist anymore' });
return res.type("application/json").status(HttpStatusCode.ImATeapot).json({
error: "You dont exist anymore",
});
}
if (!sourceUser.is_admin) {
if ("username" in sourceUser && !sourceUser.is_admin) {
return res
.type('application/json')
.status(403)
.json({ error: 'Unauthorized' });
.type("application/json")
.status(HttpStatusCode.Unauthorized)
.json({
error: "Unauthorized",
});
}
const userId = req.params["id"];
if (!userId) {
logger.warn(`User ID not provided (${req.ip})`);
return res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "User ID not provided",
});
}
const dbUser = await UserService.getFromId(userId);
if (!dbUser) {
logger.warn(`User not found (${req.ip})`);
return res
.type('application/json')
.status(404)
.json({ error: 'User not found' });
return res.type("application/json").status(HttpStatusCode.NotFound).json({
error: "User not found",
});
}
// @ts-ignore
delete dbUser.passwordHash
delete dbUser.passwordHash;
// @ts-ignore
delete dbUser._id
return res
.type('application/json')
.status(200)
.json(dbUser);
delete dbUser._id;
return res.type("application/json").status(HttpStatusCode.Found).json(dbUser);
}
//FEAT - Implement re-auth by current password in case of password change
//ToTest
async function editUser(req: Request, res: Response) {
const body: IReqEditUserData | null = req.body;
if (!body) {
return res
.type('application/json')
.status(400)
.json({ error: 'Field(s) missing' });
return res.type("application/json").status(400).json({
error: "Field(s) missing",
});
}
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(' ')[1];
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(401).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({ error: 'Unauthorized' });
return res.type("application/json").status(401).json({
error: "Unauthorized",
});
}
const sourceUser = await UserService.getFromId(payload.sub)
const sourceUser = await UserService.getFromId(payload.sub);
//@ts-ignore
const targetUserId = req.params.id || payload.sub
console.log(targetUserId)
const targetUserId = req.params.id || payload.sub;
console.log(targetUserId);
if (!sourceUser) {
logger.warn(`Unauthorized access attempt (${req.ip})`);
return res
.type('application/json')
.status(404)
.json({ error: 'You dont exist anymore' });
return res.type("application/json").status(404).json({
error: "You dont exist anymore",
});
}
if (sourceUser.is_admin || sourceUser.id === payload.sub) {
if (
("id" in sourceUser && sourceUser.is_admin) ||
("id" in sourceUser && sourceUser.id === payload.sub)
) {
if (sourceUser.is_admin) {
logger.info(`EDIT :> Source user is an admin (${sourceUser.firstname} ${sourceUser.lastname})`)
logger.info(
`EDIT :> Source user is an admin (${sourceUser.firstname} ${sourceUser.lastname})`,
);
} else {
logger.info(`EDIT :> Source user modify itself (${sourceUser.firstname} ${sourceUser.lastname})`)
logger.info(
`EDIT :> Source user modify itself (${sourceUser.firstname} ${sourceUser.lastname})`,
);
}
//TODO Interface
const modifiedData = {
}
const modifiedData = {};
//@ts-ignore
if (body.firstName) modifiedData.firstName = `${body.firstName}`;
//@ts-ignore
@@ -291,175 +307,169 @@ async function editUser(req: Request, res: Response) {
//if (body.password) modifiedData.password = `${body.password}`;
//Call service
const EditUserServiceResult = await UserService.edit(`${targetUserId}`, modifiedData);
if (EditUserServiceResult.error === 'userNotFound') {
const EditUserServiceResult = await UserService.edit(
`${targetUserId}`,
modifiedData,
);
if (
typeof EditUserServiceResult !== "boolean" &&
EditUserServiceResult.error === 3
) {
logger.warn(`User not found (${req.ip})`);
return res
.type('application/json')
.status(404)
.json({ error: 'User not found' });
return res.type("application/json").status(404).json({
error: "User not found",
});
}
if (EditUserServiceResult.error !== 'none') {
if (
typeof EditUserServiceResult !== "boolean" &&
EditUserServiceResult.error
) {
logger.error(`Error occurred during user edit (${req.ip})`);
return res
.type('application/json')
.status(500)
.json({ error: 'Internal server error' });
return res.type("application/json").status(500).json({
error: "Internal server error",
});
}
return res
.type('application/json')
.status(200)
.json(EditUserServiceResult);
return res.type("application/json").status(200).json(EditUserServiceResult);
}
//Not itself or
logger.warn(`Unauthorized access attempt, not self or admin (${req.ip})`);
return res
.type('application/json')
.status(403)
.json({ error: 'Unauthorized' });
return res.type("application/json").status(403).json({
error: "Unauthorized",
});
}
//ToTest
async function deleteUser(req: Request, res: Response): Promise<Response> {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(' ')[1];
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(401).json({
error: "Unauthorized",
});
}
const payload = await JwtService.verify(bearerToken);
if (!payload) {
if (!payload || !payload.sub) {
logger.warn(`Invalid token (${req.ip})`);
return res
.type('application/json')
.status(401)
.json({ error: 'Invalid token' });
return res.type("application/json").status(401).json({
error: "Invalid token",
});
}
const sourceUser = await UserService.getFromId(payload?.sub)
const targetUserId = req.params["id"]
const sourceUser = await UserService.getFromId(payload?.sub);
const targetUserId = req.params["id"];
if (!sourceUser) {
logger.warn(`Unauthorized access attempt (${req.ip})`);
return res
.type('application/json')
.status(404)
.json({ error: 'You dont exist anymore' });
return res.type("application/json").status(404).json({
error: "You dont exist anymore",
});
}
if (sourceUser.is_admin || sourceUser.id === payload.sub) {
const deleteUserServiceResult = await UserService.delete(`${targetUserId}`);
if (
("id" in sourceUser && sourceUser.is_admin) ||
("id" in sourceUser && sourceUser.id === payload.sub)
) {
const deleteUserServiceResult = await UserService.delete(`${targetUserId}`);
if (!deleteUserServiceResult) {
logger.error(`Error occurred during user delete (${req.ip})`);
return res
.type('application/json')
.status(500)
.json({ error: 'Internal server error' });
return res.type("application/json").status(500).json({
error: "Internal server error",
});
}
return res
.type('application/json')
.status(200)
.json({ message: 'User deleted successfully' });
return res.type("application/json").status(200).json({
message: "User deleted successfully",
});
}
return res
.type('application/json')
.status(403)
.json({ error: 'Unauthorized' });
return res.type("application/json").status(403).json({
error: "Unauthorized",
});
}
//ToTest
async function deleteSelf(req: Request, res: Response) {
const authHeader = req.headers.authorization;
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' });
}
const payload = await JwtService.verify(bearerToken);
if (!payload) {
logger.warn(`Unauthorized access attempt (${req.ip})`);
return res
.type('application/json')
.status(401)
.json({ error: 'Unauthorized' });
}
const sourceUser = await UserService.getFromId(payload.sub)
if (!sourceUser) {
return res
.type('application/json')
.status(404)
.json({ error: 'You dont exist anymore' });
}
if (sourceUser.id !== req.params["id"]) {
return res
.type('application/json')
.status(403)
.json({ error: 'Unauthorized' });
}
const deleteResult = await UserService.delete(sourceUser.id);
if (!deleteResult) {
logger.error(`Failed to delete user (${req.ip})`);
return res
.type('application/json')
.status(500)
.json({ error: 'Failed to delete user' });
}
return res
.type('application/json')
.status(200)
.json({ message: 'User deleted successfully' });
}
async function getSelf(req: Request, res: Response) {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(' ')[1];
const bearerToken = authHeader?.split(" ")[1];
if (!bearerToken) {
return res
.type('application/json')
.status(401)
.json({ error: 'Unauthorized' });
logger.warn(`Bearer token not provided (${req.ip})`);
return res.type("application/json").status(401).json({
error: "Unauthorized",
});
}
const payload = await JwtService.verify(bearerToken);
if (!payload) {
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) {
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
if (!payload || !payload.sub) {
logger.warn(`Unauthorized access attempt (${req.ip})`);
return res.type("application/json").status(401).json({
error: "Unauthorized",
});
}
const sourceUser = await UserService.getFromId(payload.sub);
if (!sourceUser) {
return res.type("application/json").status(404).json({
error: "You dont exist anymore",
});
}
if ("id" in sourceUser && sourceUser.id !== req.params["id"]) {
return res.type("application/json").status(403).json({
error: "Unauthorized",
});
}
if ("id" in sourceUser) {
const deleteResult = await UserService.delete(sourceUser.id);
if (!deleteResult) {
logger.error(`Failed to delete user (${req.ip})`);
return res.type("application/json").status(500).json({
error: "Failed to delete user",
});
}
}
return res.type("application/json").status(200).json({
message: "User deleted successfully",
});
}
//ToTest
async function getSelf(req: Request, res: Response) {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(" ")[1];
if (!bearerToken) {
return res.type("application/json").status(401).json({
error: "Unauthorized",
});
}
const payload = await JwtService.verify(bearerToken);
if (!payload || !payload.sub) {
logger.warn(`Unauthorized access attempt (${req.ip})`);
return res.type("application/json").status(401).json({
error: "Unauthorized",
});
}
const GetUserResult = await UserService.getFromId(payload.sub);
if ("id" in GetUserResult) {
return res.type("application/json").status(200).json({
id: GetUserResult.id,
username: GetUserResult.username,
firstName: GetUserResult.firstname,
lastName: GetUserResult.lastname,
isAdmin: GetUserResult.is_admin,
});
}
return res.type("application/json").status(404).json({
error: "User not found",
});
}
if (isDebugMode()) logger.debug("\nController loaded.");
const AuthController = {
register: registerUser,
login: loginUser,
getAllUsers,
getUser,
editUser,
deleteUser,
login: loginUser,
getAllUsers,
getUser,
editUser,
deleteUser,
deleteSelf,
getSelf
}
getSelf,
};
export default AuthController;
export default AuthController;

View File

@@ -0,0 +1,185 @@
import type { Request, Response } from "express";
import { Logger } from "tslog";
import type IDbBrand from "@interfaces/database/IDbBrand";
import BrandService from "@services/brand.service";
import { isDebugMode } from "@utils/debugState";
//import {body} from "express-validator";
const logger = new Logger({
name: "BrandController",
});
/**
* Creates a new brand.
*
* @param req - The request object containing the brand information.
* @param res - The response object used to send the response.
*
* @returns A Promise that resolves to the response object with a status code and a JSON message indicating the success or failure of brand creation.
*/
async function createBrand(req: Request, res: Response): Promise<Response> {
const body: IDbBrand = req.body;
const doesExist = await BrandService.getBySlug(`${body.slug_name}`);
if (doesExist) {
logger.error("Brand already exists");
return res.status(400).json({
error: "Brand already exists",
});
}
const createResult = await BrandService.create({
slug_name: `${body.slug_name}`,
display_name: `${body.display_name}`,
image_blob: `${body.image_blob}`,
});
if (!createResult) {
logger.error("Failed to create brand");
return res.status(500).json({
error: "Failed to create brand",
});
}
logger.info(`Brand created successfully ! (${body.slug_name})`);
return res.status(201).json({
message: "Brand created successfully",
});
}
/**
* Update a brand in the database.
*
* @param {Request} req - The HTTP request object.
* @param {Response} res - The HTTP response object.
* @return {Promise<Response>} A promise that resolves with the HTTP response.
*/
async function updateBrand(req: Request, res: Response): Promise<Response> {
const body: IDbBrand = req.body;
const brandSlug = req.params["brandSlug"];
if (!brandSlug) {
logger.error("Brand slug is missing");
return res.status(400).json({
error: "Brand slug is missing",
});
}
const doesExist = await BrandService.getBySlug(brandSlug);
if (!doesExist) {
logger.error("Brand not found");
return res.status(404).json({
error: "Brand not found",
});
}
const updateResult = await BrandService.update({
slug_name: `${body.slug_name}`,
display_name: `${body.display_name}`,
image_blob: `${body.image_blob}`,
});
if (!updateResult) {
logger.error("Failed to update brand");
return res.status(500).json({
error: "Failed to update brand",
});
}
logger.info(`Brand updated successfully ! (${brandSlug})`);
return res.status(200).json({
message: "Brand updated successfully",
});
}
/**
* Retrieves a brand by its slug.
*
* @param {Request} req - The request object containing the brand slug in the parameters.
* @param {Response} res - The response object to send the result.
* @returns {Promise<Response>} - A promise that resolves to the response with the retrieved brand.
*/
async function getBySlugBrand(req: Request, res: Response): Promise<Response> {
const brandSlug = req.params["brandSlug"];
if (!brandSlug) {
logger.error("Brand slug is missing");
return res.status(400).json({
error: "Brand slug is missing",
});
}
const brand = await BrandService.getBySlug(brandSlug);
if (!brand) {
logger.error("Brand not found");
return res.status(404).json({
error: "Brand not found",
});
}
logger.info(`Brand retrieved successfully ! (${brandSlug})`);
return res.status(200).json(brand);
}
/**
* Retrieves all brands.
*
* @param {Request} _req - The request object.
* @param {Response} res - The response object.
* @returns {Promise<Response>} - A promise with the response object.
*/
async function getAllBrand(_req: Request, res: Response): Promise<Response> {
const brands = await BrandService.getAll();
if (!brands) {
logger.error("Failed to retrieve brands");
return res.status(500).json({
error: "Failed to retrieve brands",
});
}
logger.info("Brands retrieved successfully !");
return res.status(200).json({
uat: Date.now(),
brands: brands,
total: brands.length,
});
}
/**
* Deletes a brand.
*
* @async
* @param {Request} req - The request object.
* @param {Response} res - The response object.
* @returns {Promise<Response>} - The response object indicating the success or failure of the delete operation.
*/
async function deleteBrand(req: Request, res: Response): Promise<Response> {
const brandSlug = req.params["brandSlug"];
if (!brandSlug) {
logger.error("Brand slug is missing");
return res.status(400).json({
error: "Brand slug is missing",
});
}
//TODO verify if models linked to brand
const doesExist = await BrandService.getBySlug(brandSlug);
if (!doesExist) {
logger.error("Brand not found");
return res.status(404).json({
error: "Brand not found",
});
}
const deleteResult = await BrandService.delete(brandSlug);
if (!deleteResult) {
logger.error("Failed to delete brand");
return res.status(500).json({
error: "Failed to delete brand",
});
}
logger.info(`Brand deleted successfully ! (${brandSlug})`);
return res.status(200).json({
message: "Brand deleted successfully",
});
}
//TODO get models of the brand
if (isDebugMode()) logger.debug("\nController loaded.");
const BrandController = {
create: createBrand,
update: updateBrand,
getBySlug: getBySlugBrand,
getAll: getAllBrand,
delete: deleteBrand,
};
export default BrandController;

View File

@@ -1,12 +1,13 @@
import type {Request, Response} from "express";
import {Logger} from "tslog";
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 {validationResult} from "express-validator";
const logger = new Logger({ name: "CategoryController" });
const logger = new Logger({
name: "CategoryController",
});
/**
* Creates a new category.
@@ -16,22 +17,28 @@ const logger = new Logger({ name: "CategoryController" });
* @returns {Promise<Response>} The response object indicating the outcome of the category creation.
*/
async function createCategory(req: Request, res: Response): Promise<Response> {
const body: IDbCategory = req.body
const doesExist = await CategoryService.getBySlug(`${body.slug_name}`)
const body: IDbCategory = req.body;
const doesExist = await CategoryService.getBySlug(`${body.slug_name}`);
if (doesExist) {
logger.error("Category already exists");
return res.status(400).json({ error: "Category already exists" });
}
logger.error("Category already exists");
return res.status(400).json({
error: "Category already exists",
});
}
const createResult = await CategoryService.create({
display_name: `${body.display_name}`,
slug_name: `${body.slug_name}`
})
slug_name: `${body.slug_name}`,
});
if (!createResult) {
logger.error("Failed to create category");
return res.status(500).json({ error: "Failed to create category" });
return res.status(500).json({
error: "Failed to create category",
});
}
logger.info(`Category created successfully ! (${body.slug_name})`)
return res.status(201).json({ message: "Category created successfully" });
logger.info(`Category created successfully ! (${body.slug_name})`);
return res.status(201).json({
message: "Category created successfully",
});
}
/**
@@ -42,29 +49,37 @@ async function createCategory(req: Request, res: Response): Promise<Response> {
*
* @return {Promise<Response>} - A promise that will be resolved with the result of the update operation.
*/
async function updateCategory(req: Request, res:Response): Promise<Response> {
async function updateCategory(req: Request, res: Response): Promise<Response> {
const body: IDbCategory = req.body;
const categoryId = req.params["categorySlug"];
const categoryId = req.params["categorySlug"];
if (!categoryId) {
logger.error("Category slug is missing");
return res.status(400).json({ error: "Category slug is missing" });
return res.status(400).json({
error: "Category slug is missing",
});
}
const doesExist = await CategoryService.getById(`${categoryId}`)
if (!doesExist || !doesExist.id) {
logger.error("Category not found");
return res.status(404).json({ error: "Category not found" });
}
const updateResult = await CategoryService.update({
const doesExist = await CategoryService.getById(`${categoryId}`);
if (!doesExist || !doesExist.id) {
logger.error("Category not found");
return res.status(404).json({
error: "Category not found",
});
}
const updateResult = await CategoryService.update({
id: doesExist.id,
slug_name: `${body.slug_name}`,
display_name: `${body.display_name}`
})
if (!updateResult) {
logger.error("Failed to update category");
return res.status(500).json({ error: "Failed to update category" });
}
logger.info(`Category updated successfully! (${categoryId})`);
return res.status(200).json({ message: "Category updated successfully" });
display_name: `${body.display_name}`,
});
if (!updateResult) {
logger.error("Failed to update category");
return res.status(500).json({
error: "Failed to update category",
});
}
logger.info(`Category updated successfully! (${categoryId})`);
return res.status(200).json({
message: "Category updated successfully",
});
}
/**
@@ -76,73 +91,101 @@ async function updateCategory(req: Request, res:Response): Promise<Response> {
*/
async function deleteCategory(req: Request, res: Response): Promise<Response> {
const categorySlug = req.params["categorySlug"];
if (!categorySlug) {
logger.error("Category slug is missing");
return res.status(400).json({ error: "Category slug is missing" });
}
const doesExist = await CategoryService.getBySlug(`${categorySlug}`);
if (!doesExist || !doesExist.id) {
logger.error("Category not found");
return res.status(404).json({ error: "Category not found" });
}
const deleteResult = await CategoryService.delete(`${doesExist.id}`);
if (!deleteResult) {
logger.error("Failed to delete category");
return res.status(500).json({ error: "Failed to delete category" });
}
logger.info(`Category deleted successfully! (${categorySlug})`);
return res.status(200).json({ message: "Category deleted successfully" });
if (!categorySlug) {
logger.error("Category slug is missing");
return res.status(400).json({
error: "Category slug is missing",
});
}
const doesExist = await CategoryService.getBySlug(`${categorySlug}`);
if (!doesExist || !doesExist.id) {
logger.error("Category not found");
return res.status(404).json({
error: "Category not found",
});
}
const deleteResult = await CategoryService.delete(`${doesExist.id}`);
if (!deleteResult) {
logger.error("Failed to delete category");
return res.status(500).json({
error: "Failed to delete category",
});
}
logger.info(`Category deleted successfully! (${categorySlug})`);
return res.status(200).json({
message: "Category deleted successfully",
});
}
/**
* Retrieves all categories.
*
* @param _req
* @param {Response} res - The response object.
* @return {Promise<Response>} - A promise that resolves to the response object.
*/
async function getAllCategory(res: Response): Promise<Response> {
async function getAllCategory(_req: Request, res: Response): Promise<Response> {
const categories = await CategoryService.getAll();
if (!categories) {
logger.error("Failed to get categories");
return res.status(500).json({ error: "Failed to get categories" });
}
logger.info("Categories retrieved successfully");
//ToTest categories output type
return res.status(200).json({
if (!categories) {
logger.error("Failed to get categories");
return res.status(500).json({
error: "Failed to get categories",
});
}
logger.info("Categories retrieved successfully");
return res.status(200).json({
iat: Date.now(),
categories: categories.map((category: IDbCategory) => ({
id: category.id,
display_name: category.display_name,
slug_name: category.slug_name
}))
id: category.id,
display_name: category.display_name,
slug_name: category.slug_name,
})),
total: categories.length,
});
}
async function getBySlugCategory(req: Request, res:Response) {
/**
* Get category by slug
*
* @param {Request} req - The request object containing category slug
* @param {Response} res - The response object to send back the category
*
* @return {Promise<Response>} - The response with category data or error message
*/
async function getBySlugCategory(
req: Request,
res: Response,
): Promise<Response> {
const categorySlug = req.params["categorySlug"];
if (!categorySlug) {
logger.error("Category slug is missing");
return res.status(400).json({ error: "Category slug is missing" });
}
const category = await CategoryService.getBySlug(`${categorySlug}`);
if (!category || !category.id) {
logger.error("Category not found");
return res.status(404).json({ error: "Category not found" });
}
logger.info(`Category retrieved successfully! (${categorySlug})`);
return res.status(200).json({
id: category.id,
display_name: category.display_name,
slug_name: category.slug_name
});
if (!categorySlug) {
logger.error("Category slug is missing");
return res.status(400).json({
error: "Category slug is missing",
});
}
const category = await CategoryService.getBySlug(`${categorySlug}`);
if (!category || !category.id) {
logger.error("Category not found");
return res.status(404).json({
error: "Category not found",
});
}
logger.info(`Category retrieved successfully! (${categorySlug})`);
return res.status(200).json({
id: category.id,
display_name: category.display_name,
slug_name: category.slug_name,
});
}
if (isDebugMode()) logger.debug("\nController loaded.");
const CategoryController = {
create: createCategory,
update: updateCategory,
delete: deleteCategory,
getAll: getAllCategory,
getBySlug: getBySlugCategory
}
getBySlug: getBySlugCategory,
};
export default CategoryController;
export default CategoryController;

View File

@@ -0,0 +1,145 @@
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 {validationResult} from "express-validator";
const logger = new Logger({
name: "ModelController",
});
async function createModel(req: Request, res: Response): Promise<Response> {
const body: IDbModel = req.body;
const doesExist = await CategoryService.getBySlug(`${body.slug_name}`);
if (doesExist) {
logger.error("Category already exists");
return res.status(400).json({
error: "Category already exists",
});
}
const createResult = await ModelService.create({
display_name: `${body.display_name}`,
slug_name: `${body.slug_name}`,
category_id: `${body.category_id}`,
base_price: Number.parseFloat(`${body.base_price}`),
brand_id: `${body.brand_id}`,
image_blob: `${body.image_blob}`,
is_trending: !!body.is_trending,
});
if (!createResult) {
logger.error("Failed to create category");
return res.status(500).json({
error: "Failed to create category",
});
}
logger.info(`Category created successfully ! (${body.slug_name})`);
return res.status(201).json({
message: "Category created successfully",
});
}
async function updateModel(req: Request, res: Response): Promise<Response> {
const body: IDbModel = req.body;
const doesExist = await ModelService.getBySlug(`${req.params["modelSlug"]}`);
if (!doesExist) {
logger.error("Model does not exist");
return res.status(404).json({
error: "Model does not exist",
});
}
const updateResult = await ModelService.update({
id: `${body.id}`,
display_name: `${body.display_name}`,
slug_name: `${body.slug_name}`,
category_id: `${body.category_id}`,
base_price: Number.parseFloat(`${body.base_price}`),
brand_id: `${body.brand_id}`,
image_blob: `${body.image_blob}`,
is_trending: !!body.is_trending,
});
if (!updateResult) {
logger.error("Failed to update model");
return res.status(500).json({
error: "Failed to update model",
});
}
logger.info(`Model updated successfully! (${body.slug_name})`);
return res.status(200).json({
message: "Model updated successfully",
});
}
async function getAllModel(res: Response): Promise<Response> {
const models = await ModelService.getAll();
if (!models) {
logger.error("Failed to get all models");
return res.status(500).json({
error: "Failed to get all models",
});
}
return res.status(200).json({
uat: Date.now(),
models: models,
total: models.length,
});
}
async function getModelBySlug(req: Request, res: Response): Promise<Response> {
const slug = req.params["modelSlug"];
if (!slug) {
logger.error("Invalid slug");
return res.status(400).json({
error: "Invalid slug",
});
}
const model = await ModelService.getBySlug(slug);
if (!model) {
logger.error("Model not found");
return res.status(404).json({
error: "Model not found",
});
}
return res.status(200).json({
model,
});
}
async function deleteModel(req: Request, res: Response): Promise<Response> {
const modelSlug = req.params["modelSlug"];
if (!modelSlug) {
logger.error("Invalid model slug");
return res.status(400).json({
error: "Invalid model slug",
});
}
//TODO Check if vehicle related to model
const deleteResult = await ModelService.delete(modelSlug);
if (!deleteResult) {
logger.error("Failed to delete model");
return res.status(500).json({
error: "Failed to delete model",
});
}
logger.info(`Model deleted successfully! (SLUG: ${modelSlug})`);
return res.status(200).json({
message: "Model deleted successfully",
});
}
//TODO get all vehicles of an model by slug
//TODO get model with vehicle available.
if (isDebugMode()) logger.debug("\nController loaded.");
const ModelController = {
create: createModel,
update: updateModel,
getAll: getAllModel,
getBySlug: getModelBySlug,
delete: deleteModel,
};
export default ModelController;

View File

@@ -0,0 +1,150 @@
import { isDebugMode } from "@utils/debugState";
import type { Request, Response } from "express";
import { Logger } from "tslog";
import RentService from "@services/rent.service";
import {HttpStatusCode} from "@interfaces/requests/HttpStatusCode";
import type IDbRent from "@interfaces/database/IDbRent";
import JwtService from "@services/jwt.service";
import UserService from "@services/user.service";
const logger = new Logger({
name: "RentController",
});
async function createRent(req: Request, res: Response): Promise<Response> {
try {
const rentData: IDbRent = req.body;
if (!rentData.active || !rentData.need_survey || !rentData.eat || !rentData.iat || !rentData.user_id || !rentData.vehicle_id || !rentData.km_at_start || !rentData.active) {
logger.error("Invalid rent data");
return res.status(HttpStatusCode.BadRequest).json({
error: "Invalid rent data",
});
}
const rent = await RentService.create(rentData);
logger.info(`\n\n> Rent created successfully! (ID: ${rentData.vehicle_id})\n`);
return res.status(201).json({
message: "Rent created successfully",
rent,
});
} catch (error) {
logger.error(`\n\n> Failed to create rent !\n${error}\n`);
return res.status(500).json({
error: "Failed to create rent",
});
}
}
async function updateRent(req: Request, res: Response): Promise<Response> {
const body: IDbRent = req.body;
if (!body.vehicle_id || !body.user_id || !body.active || !body.need_survey || !body.eat || !body.iat || !body.km_at_start) {
logger.error("Invalid rent data");
return res.status(HttpStatusCode.BadRequest).json({
error: "Invalid rent data",
});
}
const rentId = req.params["rentId"];
if (!rentId || rentId.length !== 36) {
logger.error("Invalid rent ID");
return res.status(HttpStatusCode.BadRequest).json({
error: "Invalid rent ID",
});
}
const result = await RentService.update({
id: rentId,
vehicle_id: ``,
user_id: ``,
active: !!body.active,
need_survey: !!body.need_survey,
eat: body.eat,
iat: body.iat,
km_at_start: body.km_at_start,
})
if (!result) {
logger.error(`Failed to update rent with ID: ${rentId}`);
return res.status(HttpStatusCode.InternalServerError).json({
error: `Failed to update rent with ID: ${rentId}`,
});
}
logger.info(`Rent with ID: ${rentId} updated successfully`);
return res.status(HttpStatusCode.Ok).json({
message: `Rent with ID: ${rentId} updated successfully`,
});
}
async function getAllAssigned(res: Response): Promise<Response> {
const rents = await RentService.getAll();
if (rents.length === 0) {
return res.status(HttpStatusCode.NotFound).json({
error: "No assigned rents found",
});
}
return res.status(HttpStatusCode.Ok).json({
iat: Date.now(),
rents: rents,
total: rents.length
});
}
async function getAssignedToUser(req: Request, res: Response): Promise<Response> {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(" ")[1];
if (!bearerToken) {
logger.warn(`Bearer token not provided (${req.ip})`);
return res
.type("application/json")
.status(HttpStatusCode.Unauthorized)
.json({
error: "Unauthorized",
});
}
const payload = await JwtService.verify(bearerToken);
if (!payload || !payload.sub) {
logger.warn(`Unauthorized access attempt (${req.ip})`);
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(HttpStatusCode.ImATeapot).json({
error: "You dont exist anymore",
});
}
const userId: string = payload.sub;
if (!userId || userId.length !== 36) {
logger.error("Invalid user ID");
return res.status(HttpStatusCode.BadRequest).json({
error: "Invalid user ID",
});
}
let targetId = userId
if ("is_admin" in sourceUser && sourceUser.is_admin) {
targetId = req.body.targetId || userId
}
const rents = await RentService.getUserRent(targetId);
if (!rents) {
return res.status(HttpStatusCode.NotFound).json({
error: "No assigned rents found for the user",
});
}
return res.status(HttpStatusCode.Ok).json({
iat: Date.now(),
rents: rents,
total: rents.length,
});
}
if (isDebugMode()) logger.debug("\nController loaded.");
const RentController = {
create: createRent,
update: updateRent,
getAll: getAllAssigned,
getAssignedToUser,
};
export default RentController;

View File

@@ -0,0 +1,174 @@
import type { IDbVehicle } from "@interfaces/database/IDbVehicle";
import VehicleService from "@services/vehicle.service";
import { isDebugMode } from "@utils/debugState";
import type { Request, Response } from "express";
import { Logger } from "tslog";
//import {validationResult} from "express-validator";
const logger = new Logger({
name: "VehicleController",
});
/**
* Creates a new vehicle in the database.
*
* @param {Request} req - The request object containing the vehicle details in the body.
* @param {Response} res - The response object used to send the result of the operation.
* @returns {Promise<Response>} The response with the result of the operation.
*/
async function createVehicle(req: Request, res: Response): Promise<Response> {
const body: IDbVehicle = req.body;
const createResult = await VehicleService.create({
plate_number: `${body.plate_number}`,
model_id: `${body.plate_number}`,
odometer: Number.parseInt(`${body.odometer}`),
health_state: Number.parseInt(`${body.health_state}`),
});
if (!createResult) {
logger.error("Failed to create vehicle");
return res.status(500).json({
error: "Failed to create vehicle",
});
}
logger.info(`Vehicle created successfully ! (${body.plate_number})`);
return res.status(201).json({
message: "Vehicle created successfully",
});
}
/**
* Updates a vehicle in the database.
*
* @param {Request} req - The request object containing the vehicle data in the request body and the vehicle ID in the request parameters.
* @param {Response} res - The response object used to send the result of the update operation.
*
* @return {Promise<Response>} A promise that resolves to the response object with a status and a JSON body indicating the result of the update operation.
*/
async function updateVehicle(req: Request, res: Response): Promise<Response> {
const body: IDbVehicle = req.body;
const vehicleId = req.params["vehicleId"];
if (!vehicleId || vehicleId.length !== 36) {
if (isDebugMode()) logger.error("Vehicle ID is missing");
return res.status(400).json({
error: "Vehicle ID is missing or not valid",
});
}
const updateResult = await VehicleService.update({
plate_number: `${body.plate_number}`,
model_id: `${body.plate_number}`,
odometer: Number.parseInt(`${body.odometer}`),
health_state: Number.parseInt(`${body.health_state}`),
});
if (!updateResult) {
logger.error("Failed to update vehicle");
return res.status(500).json({
error: "Failed to update vehicle",
});
}
logger.info(`Vehicle updated successfully ! (${body.plate_number})`);
return res.status(200).json({
message: "Vehicle updated successfully",
});
}
/**
* Retrieves all vehicles from the vehicle service.
*
* @param {Response} res - The response object from the client.
* @returns {Promise<Response>} A promise that resolves to a response object containing the result of the operation. The result is a JSON object containing all vehicles.
*/
async function getAllVehicle(res: Response): Promise<Response> {
const getAllVehicleResult = await VehicleService.getAll();
if (!getAllVehicleResult) {
logger.error("Failed to get all vehicles");
return res.status(500).json({
error: "Failed to get all vehicles",
});
}
return res.status(200).json(getAllVehicleResult);
}
/**
* Retrieves the available vehicles from the VehicleService
*
* @param {Response} res - The Response object to send the result to
* @returns {Promise<Response<any, Record<string, any>>>} - A promise that resolves to a Response object containing the available vehicles or an error message
*/
async function getAvailableVehicle(res: Response): Promise<Response<any, Record<string, any>>> {
const getAvailableVehicleResult = await VehicleService.getAvailable();
if (!getAvailableVehicleResult) {
logger.error("Failed to get available vehicles");
return res.status(500).json({
error: "Failed to get available vehicles",
});
}
return res.status(200).json(getAvailableVehicleResult);
}
/**
* Retrieves a vehicle by its ID.
*
* @param {Request} req - The request object containing the vehicle ID.
* @param {Response} res - The response object used to send the result.
*
* @return {Promise<Response>} A promise that resolves to the result of the retrieval operation.
*/
async function getVehicleById(req: Request, res: Response): Promise<Response> {
const vehicleId = req.params["vehicleId"];
if (!vehicleId || vehicleId.length !== 36) {
if (isDebugMode()) logger.error("Vehicle ID is missing or not valid");
return res.status(400).json({
error: "Vehicle ID is missing or not valid",
});
}
const getVehicleResult = await VehicleService.getById(vehicleId);
if (!getVehicleResult) {
logger.error(`Failed to get vehicle by ID: ${vehicleId}`);
return res.status(500).json({
error: `Failed to get vehicle by ID: ${vehicleId}`,
});
}
return res.status(200).json(getVehicleResult);
}
/**
* Deletes a vehicle.
*
* @param {Object} req - The request object.
* @param {Object} res - The response object.
*
* @return {Promise<Response>} A promise that resolves to the response JSON object.
*/
async function deleteVehicle(req: Request, res: Response): Promise<Response> {
const vehicleId = req.params["vehicleId"];
if (!vehicleId || vehicleId.length !== 36) {
if (isDebugMode()) logger.error("Vehicle ID is missing or not valid");
return res.status(400).json({
error: "Vehicle ID is missing or not valid",
});
}
const deleteResult = await VehicleService.delete(vehicleId);
if (!deleteResult) {
logger.error(`Failed to delete vehicle with ID: ${vehicleId}`);
return res.status(500).json({
error: `Failed to delete vehicle with ID: ${vehicleId}`,
});
}
logger.info(`Vehicle deleted successfully ! (ID: ${vehicleId})`);
return res.status(200).json({
message: "Vehicle deleted successfully",
});
}
if (isDebugMode()) logger.debug("\nController loaded.");
const VehicleController = {
create: createVehicle,
update: updateVehicle,
getAll: getAllVehicle,
delete: deleteVehicle,
getById: getVehicleById,
getAvailable: getAvailableVehicle,
};
export default VehicleController;

View File

@@ -3,4 +3,4 @@ export interface IReqEditUserData {
lastName?: string;
displayName?: string;
password?: string;
}
}

View File

@@ -12,7 +12,7 @@ interface DbUserData {
isDisabled: boolean;
resetPasswordToken?: string;
resetPasswordExpires?: Date;
resetPasswordExpires?: Date;
dob: Date;
gdpr: Date;
@@ -20,4 +20,4 @@ interface DbUserData {
uat: Date;
}
export default DbUserData
export default DbUserData;

View File

@@ -1,8 +1,8 @@
export interface IDbBrand {
id?: string;
slug_name: string;
display_name: string;
image_blob: BinaryType;
id?: string;
slug_name: string;
display_name: string;
image_blob: BinaryType;
}
export default IDbBrand;
export default IDbBrand;

View File

@@ -1,7 +1,7 @@
export interface IDbCategory {
id?: string;
slug_name: string;
display_name: string
id?: string;
slug_name: string;
display_name: string;
}
export default IDbCategory;
export default IDbCategory;

View File

@@ -0,0 +1,54 @@
/**
* 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 list of ? for the "VALUE" section
*/
_questionMarksFields: 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,12 +1,12 @@
export interface IDbModel {
id?: string;
slug_name: string;
display_name: string;
brand_id: string;
category_id: string;
image_blob: BinaryType;
is_trending: boolean;
base_price: number;
id?: string;
slug_name: string;
display_name: string;
brand_id: string;
category_id: string;
image_blob: BinaryType;
is_trending: boolean;
base_price: number;
}
export default IDbModel;
export default IDbModel;

View File

@@ -1,11 +1,12 @@
export interface IDbRent {
vehicle_id: string;
user_id: string;
active: boolean;
iat: Date;
eat: Date;
need_survey: boolean;
km_at_start: number
id?: string;
vehicle_id: string;
user_id: string;
active: boolean;
iat: Date;
eat: Date;
need_survey: boolean;
km_at_start: number;
}
export default IDbRent;
export default IDbRent;

View File

@@ -0,0 +1,9 @@
export interface IDbStatusResult {
fieldCount: number;
affectedRows: number;
insertId: number;
info: string;
serverStatus: number;
warningStatus: number;
changedRows: number;
}

View File

@@ -1,12 +1,12 @@
export interface IDbUser {
id?: string;
username: string;
firstname: string;
lastname: string;
dob: Date;
email: string;
is_mail_verified: boolean;
is_admin: boolean;
gdpr: Date;
hash: string
}
id?: string;
username: string;
firstname: string;
lastname: string;
dob: Date;
email: string;
is_email_verified: boolean;
is_admin: boolean;
gdpr: Date;
hash: string;
}

View File

@@ -0,0 +1,8 @@
export interface IDbVehicle {
id?: string;
plate_number: string;
model_id: string;
odometer: number;
health_state: number;
isAvailable?: boolean;
}

View File

@@ -1 +1 @@
export * from './UserData'
export * from "./UserData";

View File

@@ -0,0 +1,269 @@
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,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HttpVersionNotSupported = 505,
VariantAlsoNegotiates = 506,
InsufficientStorage = 507,
LoopDetected = 508,
NotExtended = 510,
NetworkAuthenticationRequired = 511,
}

View File

@@ -0,0 +1,4 @@
export interface IReqLogin {
email: string;
password: string;
}

View File

@@ -0,0 +1,9 @@
export interface IReqRegister {
username: string;
firstName: string;
lastName: string;
/*dob: Date;*/
email: string;
gdpr?: boolean;
password: string;
}

View File

@@ -0,0 +1,25 @@
/**
* Represents an error object.
*
* @interface ISError
*/
export interface ISError {
error: ErrorType;
message: string;
result?: unknown;
}
/**
* Represents the types of errors that can occur in the application.
*
* @enum {number}
*/
export enum ErrorType {
InvalidData = 0,
DatabaseError = 1,
ServiceError = 2,
NotFound = 3,
PasswordInvalid = 4,
UnAuthorized = 5,
UnexpectedError = 6,
}

View File

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

View File

@@ -0,0 +1,30 @@
import AuthController from "@controllers/auth.controller";
import AdminGuard from "@validators/AdminGuard";
import UserGuard from "@validators/UserGuard";
import express, { type Router } from "express";
const AuthRouter: Router = express.Router();
AuthRouter.route("/login").post(AuthController.login);
AuthRouter.route("/register").post(AuthController.register);
// PATCH
//TODO - To test
AuthRouter.route("/me").patch(UserGuard, AuthController.editUser);
// GET
AuthRouter.route("/me").get(UserGuard, AuthController.getSelf);
// DELETE
AuthRouter.route("/me").delete(UserGuard, AuthController.deleteSelf);
// GET
AuthRouter.route("/all").get(AdminGuard, AuthController.getAllUsers);
// GET
AuthRouter.route("/user/:targetId")
.get(AdminGuard, AuthController.getUser)
.patch(AdminGuard, AuthController.editUser)
.delete(AdminGuard, AuthController.deleteUser);
export default AuthRouter;

View File

@@ -1,38 +0,0 @@
import express, {type Router} from "express";
import UserGuard from "@validators/UserGuard";
import AdminGuard from "@validators/AdminGuard";
import AuthController from "@controllers/auth.controller";
const router: Router = express.Router();
router.route('/login').post(AuthController.login)
router.route('/register').post(AuthController.register)
// PATCH
//TODO - To test
router.route('/me')
.patch(UserGuard, AuthController.editUser)
// GET
router.route('/me')
.get(UserGuard, AuthController.getSelf)
// DELETE
router.route('/me')
.delete(UserGuard, AuthController.deleteSelf)
// GET
router.route('/all')
.get(AdminGuard, AuthController.getAllUsers)
// GET
router.route('/user/:targetId')
.get(AdminGuard, AuthController.getUser)
.patch(AdminGuard, AuthController.editUser)
.delete(AdminGuard, AuthController.deleteUser)
export default router

View File

@@ -0,0 +1,41 @@
import BrandController from "@controllers/brand.controller";
import CategoryController from "@controllers/category.controller";
import ModelController from "@controllers/model.controller";
import AdminGuard from "@validators/AdminGuard";
import UserGuard from "@validators/UserGuard";
import express, { type Router } from "express";
const CatalogRouter: Router = express.Router();
//-- MODELS >>
CatalogRouter.route("/model/new").get(AdminGuard, ModelController.create);
CatalogRouter.route("/model/all").get(ModelController.getAll);
CatalogRouter.route("/model/:modelSlug")
.get(UserGuard, ModelController.getBySlug)
.patch(AdminGuard, ModelController.update)
.delete(AdminGuard, ModelController.delete);
//-- CATEGORY >>
CatalogRouter.route("/category/new").get(AdminGuard, CategoryController.create);
CatalogRouter.route("/category/all").get(CategoryController.getAll);
CatalogRouter.route("/category/:categorySlug")
.get(UserGuard, CategoryController.getBySlug)
.patch(AdminGuard, CategoryController.update)
.delete(AdminGuard, CategoryController.delete);
//-- BRAND >>
CatalogRouter.route("/brand/new").post(AdminGuard, BrandController.create);
CatalogRouter.route("/brand/all").get(BrandController.getAll);
CatalogRouter.route("/brand/:brandSlug")
.get(UserGuard, BrandController.getBySlug)
.patch(AdminGuard, BrandController.update)
.delete(AdminGuard, BrandController.delete);
export default CatalogRouter;

View File

@@ -1,40 +0,0 @@
import express, {type Router} from "express";
import AdminGuard from "@validators/AdminGuard";
import UserGuard from "@validators/UserGuard";
import CategoryController from "@controllers/category.controller";
const router: Router = express.Router();
//-- MODELS >>
router.route('/model/new').get(AdminGuard)
router.route('/model/all').get()
router.route('/model/:modelSlug')
.get(UserGuard)
.patch(AdminGuard)
.delete(AdminGuard)
//-- CATEGORY >>
router.route('/category/new').get(AdminGuard, CategoryController.create)
router.route('/category/all').get(CategoryController.getAll)
router.route('/category/:categorySlug')
.get(UserGuard, CategoryController.getBySlug)
.patch(AdminGuard, CategoryController.update)
.delete(AdminGuard, CategoryController.delete)
//-- BRAND >>
router.route('/brand/new').post(AdminGuard)
router.route('/brand/all').get()
router.route('/brand/:brandSlug')
.get(UserGuard)
.patch(AdminGuard)
.delete(AdminGuard)

View File

@@ -1,3 +1,3 @@
export * from './auth/router'
export * from './catalog/router'
export * from './rent/router'
export * from "src/routes/auth/authRouter";
export * from "src/routes/catalog/catalogRouter";
export * from "src/routes/rent/rentRouter";

View File

@@ -0,0 +1,29 @@
import AdminGuard from "@validators/AdminGuard";
import UserGuard from "@validators/UserGuard";
import express, { type Router } from "express";
import VehicleController from "@controllers/vehicle.controller";
import RentController from "@controllers/rent.controller";
const RentRouter: Router = express.Router();
// Get rent affected to the user
RentRouter.route("/affected").get(UserGuard, RentController.getAssignedToUser);
// Get all vehicle in rent (admin only)
RentRouter.route("/affected/all").get(AdminGuard, RentController.getAll);
// Add a new vehicle (admin only)
RentRouter.route("/veh/new").post(AdminGuard, VehicleController.create);
// Get all vehicles
RentRouter.route("/veh/all").get(VehicleController.getAll);
// Rent a specific vehicle
RentRouter.route("/veh/rent/:vehicleId").post(UserGuard);
RentRouter.route("/veh/:vehicleId")
.get(UserGuard, VehicleController.getById)
.patch(AdminGuard, VehicleController.update)
.delete(AdminGuard, VehicleController.delete);
export default RentRouter;

View File

@@ -1,32 +0,0 @@
import express, {type Router} from "express";
import AdminGuard from "@validators/AdminGuard";
import UserGuard from "@validators/UserGuard";
const router: Router = express.Router();
// Get rent affected to the user
router.route('/affected')
.get(UserGuard)
// Get all vehicle in rent (admin only)
router.route('/affected/all')
.get(AdminGuard)
// Add a new vehicle (admin only)
router.route('/veh/new')
.post(AdminGuard)
// Get all vehicles
router.route('/veh/all')
.get()
// Rent a specific vehicle
router.route('/veh/rent/:vehicleId')
.post(UserGuard)
router.route('/veh/:vehicleId')
.get(UserGuard)
.patch(AdminGuard)
.delete(AdminGuard)

View File

@@ -0,0 +1,183 @@
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";
const DbHandler = new MysqlService.Handler("BrandService");
const logger = new Logger({
name: "BrandService",
});
//SEC todo Blob validation
/**
* Creates a brand in the database with the given data.
*
* @param {IDbBrand} data - The data of the brand to be created.
* @return {Promise<unknown>} A promise that resolves to the result of the operation.
*/
async function createBrand(data: IDbBrand): Promise<unknown> {
const doesExist = await MysqlService.Brand.getBySlug(
DbHandler,
data.slug_name,
);
if (doesExist) {
logger.error(`Brand already exists (${data.slug_name})`);
return {
error: "exist",
};
}
const brandId = uuidv4();
const createdBrand = await MysqlService.Brand.insert(DbHandler, {
id: brandId,
slug_name: `${data.slug_name}`,
display_name: `${data.display_name}`,
image_blob: data.image_blob,
});
if (createdBrand) {
logger.info(`Brand created successfully (${data.slug_name})`);
return {
success: true,
brand: createdBrand,
};
}
logger.error(`Failed to create brand (${data.slug_name})`);
return {
error: "failed",
};
}
//SEC todo Blob validation
/**
* Updates a brand in the database.
*
* @param {IDbBrand} data - The brand data to update.
* @returns {Promise<boolean>} - Indicates whether the update was successful or not.
*/
async function updateBrand(data: IDbBrand): Promise<boolean> {
if (!data.id) {
logger.error("Brand ID is missing");
return false;
}
const doesExist = await MysqlService.Brand.getBySlug(
DbHandler,
data.slug_name,
);
if (doesExist && doesExist.id !== data.id) {
logger.error(`Brand already exists (${data.slug_name})`);
return false;
}
const updatedBrand = await MysqlService.Brand.update(DbHandler, {
id: data.id,
slug_name: `${data.slug_name}`,
display_name: `${data.display_name}`,
image_blob: data.image_blob,
});
if (updatedBrand) {
logger.info(`Brand updated successfully (${data.slug_name})`);
return true;
}
logger.error(`Failed to update brand (${data.slug_name})`);
return false;
}
/**
* Retrieves all brands from the database.
* @returns {Promise<Array<IDbBrand>|false>} - An array of IDbBrand objects if successful, false otherwise.
*/
async function getAllBrand(): Promise<Array<IDbBrand> | false> {
const brands = await MysqlService.Brand.getAll(DbHandler);
if (!brands) {
logger.error("Failed to retrieve brands");
return false;
}
logger.info(`Retrieved all brands successfully (${brands.length})`);
return brands;
}
/**
* Retrieves a brand by its slug.
*
* @param {string} brandSlug - The slug of the brand.
* @returns {Promise<IDbBrand|false>} - A promise that resolves to the retrieved brand object or false if the brand is not found.
*/
async function getBySlugBrand(brandSlug: string): Promise<IDbBrand | false> {
if (!brandSlug) {
logger.error("Brand slug is missing");
return false;
}
const brand = await MysqlService.Brand.getBySlug(DbHandler, brandSlug);
if (!brand) {
logger.error(`Brand not found (${brandSlug})`);
return false;
}
logger.info(`Retrieved brand by slug successfully (${brandSlug})`);
return brand;
}
/**
* Retrieves a brand from the database based on the provided brand ID.
*
* @param {string} brandId - The ID of the brand to retrieve.
*
* @returns {Promise<IDbBrand | false>} A promise that resolves to the retrieved brand object, or false if the brand is not found or the ID is invalid.
*/
async function getByIdBrand(brandId: string): Promise<IDbBrand | false> {
if (!brandId) {
logger.error("Brand ID is missing");
return false;
}
if (brandId.length !== 36) {
logger.error("Invalid brand ID");
return false;
}
const brand = await MysqlService.Brand.getById(DbHandler, brandId);
if (!brand) {
logger.error(`Brand not found (${brandId})`);
return false;
}
logger.info(`Retrieved brand by ID successfully (${brandId})`);
return brand;
}
//TODO get models of the brand
//TODO get stats of the brand
/**
* Deletes a brand from the database.
*
* @param {string} brandId - The ID of the brand to delete.
* @return {Promise<boolean>} - A promise that resolves to true if the brand was deleted successfully, or false otherwise.
*/
async function deleteBrand(brandId: string): Promise<boolean> {
if (!brandId) {
logger.error("Brand ID is missing");
return false;
}
if (brandId.length !== 36) {
logger.error("Invalid brand ID");
return false;
}
//TODO verify if as models linked
const deletedBrand = await MysqlService.Brand.delete(DbHandler, brandId);
if (!deletedBrand) {
logger.error(`Failed to delete brand (${brandId})`);
return false;
}
logger.info(`Brand deleted successfully (${brandId})`);
return true;
}
if (isDebugMode()) logger.debug("\nService loaded.");
const BrandService = {
create: createBrand,
update: updateBrand,
getAll: getAllBrand,
getBySlug: getBySlugBrand,
getById: getByIdBrand,
delete: deleteBrand,
};
export default BrandService;

View File

@@ -1,14 +1,13 @@
//FEAT Create new category
//FEAT Create new category
import type { IDbCategory } from "@interfaces/database/IDbCategory";
import MysqlService from "@services/mysql.service";
import {Logger} from "tslog";
import { v4 as uuidv4 } from 'uuid';
const DbHandler = new MysqlService.Handler('CategoryService')
const logger = new Logger({name: 'CategoryService'})
import { isDebugMode } from "@utils/debugState";
import { Logger } from "tslog";
import { v4 as uuidv4 } from "uuid";
const DbHandler = new MysqlService.Handler("CategoryService");
const logger = new Logger({
name: "CategoryService",
});
/**
* Creates a new category with the given data.
@@ -18,18 +17,18 @@ const logger = new Logger({name: 'CategoryService'})
* If an error occurs, the promise will reject with the error.
*/
async function createCategory(data: IDbCategory): Promise<boolean> {
logger.info(`Creating a new category... (${data.display_name})`)
logger.info(`Creating a new category... (${data.display_name})`);
try {
await MysqlService.Category.insert(DbHandler, {
id: uuidv4(),
display_name: data.display_name,
slug_name: data.slug_name
})
slug_name: data.slug_name,
});
//TODO Return the new id
return true;
} catch (error) {
logger.error(`Error creating category: ${error}`);
return false;
return false;
}
}
@@ -45,19 +44,19 @@ async function createCategory(data: IDbCategory): Promise<boolean> {
*/
async function updateCategory(data: IDbCategory) {
if (!data.id) {
logger.error("Category id is missing.")
return false
logger.error("Category id is missing.");
return false;
}
try {
await MysqlService.Category.update(DbHandler, {
id: data.id,
slug_name: data.slug_name,
display_name: data.display_name
display_name: data.display_name,
});
//TODO Return id
return true;
} catch (err) {
logger.error(err)
logger.error(err);
return false;
}
}
@@ -69,15 +68,14 @@ async function updateCategory(data: IDbCategory) {
*/
async function getAll(): Promise<Promise<Array<IDbCategory>> | null> {
try {
logger.info("Getting all categories...");
return await MysqlService.Category.getAll(DbHandler);
} catch (error) {
logger.error(`Error getting all categories: ${error}`);
return null;
}
logger.info("Getting all categories...");
return await MysqlService.Category.getAll(DbHandler);
} catch (error) {
logger.error(`Error getting all categories: ${error}`);
return null;
}
}
/**
* Gets a category by its slug
*
@@ -86,12 +84,12 @@ async function getAll(): Promise<Promise<Array<IDbCategory>> | null> {
*/
async function getBySlug(slug: string): Promise<IDbCategory | null> {
try {
logger.info(`Getting category by slug... (${slug})`);
return await MysqlService.Category.getBySlug(DbHandler, slug);
} catch (error) {
logger.error(`Error getting category by slug: ${error}`);
return null;
}
logger.info(`Getting category by slug... (${slug})`);
return await MysqlService.Category.getBySlug(DbHandler, slug);
} catch (error) {
logger.error(`Error getting category by slug: ${error}`);
return null;
}
}
/**
@@ -100,42 +98,45 @@ async function getBySlug(slug: string): Promise<IDbCategory | null> {
* @param {string} id - The id of the category to retrieve.
* @returns {Promise<IDbCategory | null>} - A Promise that resolves with the retrieved category object or null if not found.
*/
async function getById(id: string):Promise<IDbCategory | null> {
async function getById(id: string): Promise<IDbCategory | null> {
try {
logger.info(`Getting category by id... (${id})`);
return await MysqlService.Category.getById(DbHandler, id);
} catch (error) {
logger.error(`Error getting category by id: ${error}`);
return null;
}
logger.info(`Getting category by id... (${id})`);
return await MysqlService.Category.getById(DbHandler, id);
} catch (error) {
logger.error(`Error getting category by id: ${error}`);
return null;
}
}
//FEAT Get all models in category (slug)
/**
* Deletes a category with the given ID from the database.
*
* @param {string} id - The ID of the category to delete.
* @return {Promise} - A Promise that resolves to the deleted category if successful, or null if an error occurs.
*/
async function deleteCategory(id:string): Promise<unknown> {
async function deleteCategory(id: string): Promise<unknown> {
//TODO Verify if exist
//TODO Verify if element linked to category
try {
logger.info(`Deleting category... (${id})`);
return await MysqlService.Category.delete(DbHandler, id);
} catch (error) {
logger.error(`Error deleting category: ${error}`);
return null;
}
logger.info(`Deleting category... (${id})`);
return await MysqlService.Category.delete(DbHandler, id);
} catch (error) {
logger.error(`Error deleting category: ${error}`);
return null;
}
}
if (isDebugMode()) logger.debug("\nService loaded.");
const CategoryService = {
create: createCategory,
delete: deleteCategory,
update: updateCategory,
getAll,
getBySlug,
getById
}
getById,
};
export default CategoryService;
export default CategoryService;

View File

@@ -2,23 +2,23 @@ import Argon2id from "@node-rs/argon2";
//ToTest
export async function getHashFromPassword(password: string) {
return await Argon2id.hash(password,{
return await Argon2id.hash(password, {
secret: Buffer.from(`${process.env["HASH_SECRET"]}`),
algorithm: 2
})
algorithm: 2,
});
}
//ToTest
export async function comparePassword(password: string, hash: string) {
return await Argon2id.verify(hash, password, {
secret: Buffer.from(`${process.env["HASH_SECRET"]}`),
algorithm: 2
algorithm: 2,
});
}
const CredentialService = {
compare: comparePassword,
hash: getHashFromPassword,
}
};
export default CredentialService;
export default CredentialService;

View File

@@ -1,2 +1,2 @@
export * from './jwt.service';
export * as MySqlService from './mysql.service'
export * from "./jwt.service";
export * as MySqlService from "./mysql.service";

View File

@@ -1,7 +1,15 @@
import Jose, {type JWTHeaderParameters, type JWTPayload} from "jose";
import {Logger} from "tslog";
import { isDebugMode } from "@utils/debugState";
import {
type JWTHeaderParameters,
type JWTPayload,
SignJWT,
jwtVerify,
} from "jose";
import { Logger } from "tslog";
const logger = new Logger({ name: "JwtService" });
const logger = new Logger({
name: "JwtService",
});
/**
* Verify a JWT token.
@@ -11,22 +19,22 @@ const logger = new Logger({ name: "JwtService" });
* @returns {Promise<null | JWTPayload>}
* - The payload of the verified JWT token or null if verification fails.
*/
async function JwtVerifyService(jwt: string | Uint8Array): Promise<null | JWTPayload> {
try {
const result = await Jose.jwtVerify(
jwt,
new TextEncoder()
.encode(`${process.env["JWT_SECRET"]}`),
{
})
return result.payload;
} catch (error) {
logger.error(error)
return null
}
async function JwtVerifyService(
jwt: string | Uint8Array,
): Promise<null | JWTPayload> {
try {
const result = await jwtVerify(
jwt,
new TextEncoder().encode(`${process.env["JWT_SECRET"]}`),
{},
);
return result.payload;
} catch (error) {
logger.error(error);
return null;
}
}
/**
* Asynchronously signs a JWT token using the provided payload, header, expiration time, and audience.
*
@@ -42,19 +50,26 @@ async function JwtVerifyService(jwt: string | Uint8Array): Promise<null | JWTPay
* @returns {Promise<string>}
* - A promise that resolves with the signed JWT token.
*/
async function JwtSignService(payload: JWTPayload, pHeader: JWTHeaderParameters, expTime: string | number | Date, audience: string | string[]): Promise<string> {
return await new Jose.SignJWT(payload)
.setProtectedHeader(pHeader)
.setIssuedAt(new Date())
.setIssuer(`${process.env["JWT_SECRET"]} - Mathis HERRIOT`)
.setAudience(audience)
.setExpirationTime(expTime)
.sign(new TextEncoder().encode(`${process.env["JWT_SECRET"]}`))
async function JwtSignService(
payload: JWTPayload,
pHeader: JWTHeaderParameters,
expTime: string | number | Date,
audience: string | string[],
): Promise<string> {
return await new SignJWT(payload)
.setProtectedHeader(pHeader)
.setIssuedAt(new Date())
.setIssuer(`${process.env["JWT_SECRET"]} - Mathis HERRIOT`)
.setAudience(audience)
.setExpirationTime(expTime)
.sign(new TextEncoder().encode(`${process.env["JWT_SECRET"]}`));
}
if (isDebugMode()) logger.debug("\nService loaded.");
const JwtService = {
verify: JwtVerifyService,
sign: JwtSignService
}
verify: JwtVerifyService,
sign: JwtSignService,
};
export default JwtService
export default JwtService;

View File

@@ -0,0 +1,157 @@
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";
const DbHandler = new MysqlService.Handler("ModelService");
const logger = new Logger({
name: "ModelService",
});
//SEC TODO validate blob
/**
* Creates a new model with the provided data.
*
* @param {IDbModel} data - The data for the new model.
*
* @return {Promise<boolean>} - Indicates whether the model was created successfully.
*/
async function createModel(data: IDbModel): Promise<boolean> {
logger.info(`Creating a new model... (${data.display_name})`);
//TODO Validate IDbModel data
try {
await MysqlService.Model.insert(DbHandler, {
id: uuidv4(),
display_name: data.display_name,
slug_name: data.slug_name,
image_blob: data.image_blob,
brand_id: data.brand_id,
category_id: data.category_id,
base_price: data.base_price,
is_trending: data.is_trending,
});
//TODO Return the new id
logger.info("Success !");
return true;
} catch (error) {
logger.error(`Error creating category: ${error}`);
return false;
}
}
/**
* Updates a model in the database.
*
* @param {IDbModel} data - The model data to update.
*
* @return {Promise<boolean>} - A promise that resolves to a boolean indicating whether the update was successful or not.
*/
async function updateModel(data: IDbModel): Promise<boolean> {
logger.info(`Updating model... (${data.slug_name})`);
try {
await MysqlService.Model.update(DbHandler, {
display_name: data.display_name,
slug_name: data.slug_name,
image_blob: data.image_blob,
brand_id: data.brand_id,
category_id: data.category_id,
base_price: data.base_price,
is_trending: data.is_trending,
});
logger.info("Update Successful !");
return true;
} catch (error) {
logger.error(`Error updating model: ${error}`);
return false;
}
}
/**
* Deletes a model from the database.
*
* @param {string} modelSlug - The slug of the model to be deleted.
* @return {Promise<boolean>} - A promise that resolves to true if the deletion is successful, else false.
*/
async function deleteModel(modelSlug: string): Promise<boolean> {
if (!modelSlug) {
logger.error("Model slug is missing");
return false;
}
logger.info(`Deleting model with ID: ${modelSlug}`);
const doesExist = await MysqlService.Model.getBySlug(DbHandler, modelSlug);
if (!doesExist[0]) {
logger.warn(`Model with slug ${modelSlug} not found`);
return false;
}
const target = doesExist[0]
if (!target.id) return false;
try {
await MysqlService.Model.delete(DbHandler, target.id);
logger.info("Deletion Successful !");
return true;
} catch (error) {
logger.error(`Error deleting model: ${error}`);
return false;
}
}
/**
* Fetches a model by slug from the database.
*
* @param {string} modelSlug - The slug of the model to be fetched.
* @return {Promise<IDbModel | null>} - A promise that resolves to the model if found, else null.
*/
async function getBySlugModel(modelSlug: string): Promise<IDbModel | null> {
logger.info(`Fetching model with slug: ${modelSlug}`);
try {
const model = await MysqlService.Model.getBySlug(DbHandler, modelSlug);
if (!model[0]) {
logger.warn(`Model with slug ${modelSlug} not found`);
return null;
}
return model[0];
} catch (error) {
logger.error(`Error fetching model by slug: ${error}`);
return null;
}
}
/**
* Fetches all models from the database.
*
* @return {Promise<IDbModel[] | null>} - A promise that resolves to an array of all models if found, else null.
*/
async function getAllModels(): Promise<IDbModel[] | null> {
logger.info("Fetching all models from the database");
try {
const models = await MysqlService.Model.getAll(DbHandler);
if (!models || models.length === 0) {
logger.warn("No models found on the database");
return null;
}
logger.info(`Found ${models.length} model(s)`);
return models;
} catch (error) {
logger.error(`Error fetching all models: ${error}`);
return null;
}
}
if (isDebugMode()) logger.debug("\nService loaded.");
/**
* ModelService is responsible for managing models.
* @namespace
*/
const ModelService = {
create: createModel,
update: updateModel,
delete: deleteModel,
getBySlug: getBySlugModel,
getAll: getAllModels,
//getByCategory: getByCategoryModel,
//getByBrand: getModelsByBrand,
};
export default ModelService;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
//import { ErrorType, type ISError } from "@interfaces/services/ISError";
import MySqlService from "@services/mysql.service";
import { isDebugMode } from "@utils/debugState";
import { Logger } from "tslog";
import { v4 } from "uuid";
import IDbRent from "@interfaces/database/IDbRent";
const logger = new Logger({
name: "RentService",
});
const DbHandler = new MySqlService.Handler("RentService");
async function createRentService(data: IDbRent): Promise<boolean> {
if (isDebugMode()) logger.debug(`\n\n> Creating a new rent...\n`);
const wantedVehicleId = data.id;
const targetUserId = data.user_id;
if (!targetUserId || !wantedVehicleId) {
logger.error(`Missing targetUserId or targetVehicleId`);
return false;
}
const userIfExist = await MySqlService.User.getById(DbHandler, targetUserId);
if (!userIfExist[0]) {
logger.error(`User does not exist`);
return false;
}
const targetUser = userIfExist[0]
if (!targetUser.id) return false;
const vehicleIfExist = await MySqlService.Vehicle.getById(DbHandler, wantedVehicleId);
if (!vehicleIfExist[0] || !vehicleIfExist[0].id) {
logger.error(`Vehicle does not exist`);
return false;
}
const vehicleId = vehicleIfExist[0].id
if (!vehicleIfExist[0].isAvailable) {
logger.error(`Vehicle is not available`);
return false;
}
try {
const result = await MySqlService.Rent.insert(DbHandler, {
id: v4(),
vehicle_id: vehicleId,
user_id: targetUser.id,
active: data.active,
iat: new Date(Date.parse(`${data.iat}`)),
eat: new Date(Date.parse(`${data.eat}`)),
need_survey: data.need_survey,
km_at_start: Number.parseInt(`${data.km_at_start}`)
});
if (result.affectedRows !== 0) {
logger.info("\n\n> Success !");
return true;
}
return false;
} catch (error) {
logger.error(`\n\n> Error creating category: \n${error}\n`);
return false;
}
}
async function updateRentService(data: IDbRent) {
if (isDebugMode()) logger.debug(`\n\n> Updating a rent...\n`);
const wantedVehicleId = data.id;
const targetUserId = data.user_id;
if (!targetUserId || !wantedVehicleId || !data.id) {
logger.error(`Missing targetUserId or targetVehicleId`);
return false;
}
const rentIfExist = await MySqlService.Rent.getById(DbHandler, data.id);
if (!rentIfExist[0]) {
logger.error(`Rent does not exist`);
return false;
}
const rentId = rentIfExist[0].id;
if (!rentId) {
logger.error(`RentId does not exist`);
return false;
}
const userIfExist = await MySqlService.User.getById(DbHandler, targetUserId);
if (!userIfExist[0]) {
logger.error(`User does not exist`);
return false;
}
const targetUser = userIfExist[0]
if (!targetUser.id) return false;
const vehicleIfExist = await MySqlService.Vehicle.getById(DbHandler, wantedVehicleId);
if (!vehicleIfExist[0] || !vehicleIfExist[0].id) {
logger.error(`Vehicle does not exist`);
return false;
}
const vehicleId = vehicleIfExist[0].id
try {
const result = await MySqlService.Rent.update(DbHandler, {
id: rentId,
vehicle_id: vehicleId,
user_id: targetUser.id,
active: data.active,
iat: new Date(Date.parse(`${data.iat}`)),
eat: new Date(Date.parse(`${data.eat}`)),
need_survey: data.need_survey,
km_at_start: Number.parseInt(`${data.km_at_start}`)
});
if (result.affectedRows !== 0) {
logger.info("\n\n> Success !");
return true;
}
return false;
} catch (error) {
logger.error(`\n\n> Error updating category: \n${error}\n`);
return false;
}
}
async function getAllAssignedRentService() {
const result = await MySqlService.Rent.getAllAssigned(DbHandler);
if (result.length > 0) {
return result;
}
logger.warn(`No assigned rents found`);
return [];
}
async function getRentByIdService(rentId: string) {
if (!rentId || rentId.length !== 36) {
logger.warn(`Id missing or not conform`)
return false;
}
const rent = await MySqlService.Rent.getById(DbHandler, rentId);
if (rent.length > 0) {
return rent[0];
}
logger.warn(`Rent not found`);
return null;
}
async function deleteRentService(rentId: string) {
const rentIfExist = await MySqlService.Rent.getById(DbHandler, rentId);
if (!rentIfExist[0]) {
logger.error(`Rent does not exist`);
return false;
}
const target = rentIfExist[0]
if (!target.id) return false;
try {
const result = await MySqlService.Rent.delete(DbHandler, target.id);
if (result.affectedRows !== 0) {
logger.info("\n\n> Success !");
return true;
}
return false;
} catch (error) {
logger.error(`\n\n> Error deleting rent: \n${error}\n`);
return false
}
}
async function getUserRentService(userId: string) {
if (!userId) {
logger.warn(`Missing userId`);
return false;
}
const rents = await MySqlService.Rent.getAssignedToUser(DbHandler, userId);
if (rents.length > 0) {
return rents;
}
logger.warn(`No rents found for user`);
return false;
}
if (isDebugMode()) logger.debug("\nService loaded.");
const VehicleService = {
create: createRentService,
update: updateRentService,
getAll: getAllAssignedRentService,
getById: getRentByIdService,
delete: deleteRentService,
getUserRent: getUserRentService,
};
export default VehicleService;

View File

@@ -1,204 +1,322 @@
import {Logger} from "tslog";
import Argon2id from "@node-rs/argon2";
import MySqlService from "@services/mysql.service";
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 type { IUserUpdate } from "@interfaces/services/IUserUpdate";
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 { isDebugMode } from "@utils/debugState";
import { Logger } from "tslog";
import { v4 } from "uuid";
const logger = new Logger({
name: "UserService",
});
const logger = new Logger({ name: "UserService" });
const DbHandler = new MySqlService.Handler('UserService')
const DbHandler = new MySqlService.Handler("UserService");
/**
* Retrieves a user object from the database based on the given username.
* Retrieves a user from the database by the given email address.
*
* @param {string} username - The username of the user to retrieve.
* @returns {Promise<Object | null>} - The user object if found, or null if not found.
* @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 getUserFromUsername(username: string): Promise<object | null> {
const dbUser = await MySqlService.User.getByUsername(DbHandler, username)
if (dbUser === undefined) return null;
return dbUser;
}
async function getUserFromIdService(id: string | undefined) {
const dbUser = await MySqlService.User.getById(DbHandler, id);
if (dbUser === undefined) return null;
return dbUser;
}
/**
* Registers a new user by creating a UserService object, generating a JWT token, and inserting the user into the database.
*
* @param {Object} sanitizedData - The sanitized user data.
* @param {string} sanitizedData.username - The username of the new user.
* @param {string} sanitizedData.displayName - The display namcoe of the new user.
* @param {string} sanitizedData.firstName
* @param {string} sanitizedData.lastName
* @param {string} sanitizedData.password - The password of the new user.
* @param {boolean} sanitizedData.gdpr - Indicates whether the new user has accepted GDPR.
*
* @returns {Object} - An object containing the registered user's data and JWT token.
* @returns {string} error - The error name, if any. "none" if registration was successful.
* @returns {string|null} jwt - The JWT token for the registered user. Null if registration was not successful.
* @returns {Object|null} user - The registered user's data. Null if registration was not successful.
* @returns {string|null} user.id - The ID of the registered user. Null if registration was not successful.
* @returns {string|null} user.username - The username of the registered user. Null if registration was not successful.
* @returns {string|null} user.displayName - The display name of the registered user. Null if registration was not successful.
*/
async function RegisterService(sanitizedData) {
if (sanitizedData.password.length < 6) {
logger.info(`REGISTER :> Invalid password (${sanitizedData.username})`)
return { error: "invalidPassword" };
}
const passwordHash = await CredentialService.hash(sanitizedData.password)
// Does the new user has accepted GDPR ?
if (sanitizedData.gdpr !== true) {
logger.info(`REGISTER :> GDPR not validated (${sanitizedData.username})`)
return { error: "gdprNotApproved" }
}
// Check if exist and return
const dbUserIfExist = await getUserFromUsername(sanitizedData.username)
if (dbUserIfExist) {
logger.info(`REGISTER :> User exist (${dbUserIfExist.username})\n ID:${dbUserIfExist.id}`)
return { error: "exist" }
}
const currentDate = new Date();
// New UserService (class)
const NewUser = new User(sanitizedData.username, sanitizedData.displayName, passwordHash, currentDate);
NewUser.setFirstName(sanitizedData.firstName);
NewUser.setLastName(sanitizedData.lastName);
// JWT
const alg = 'HS512'
const token = await JwtService.sign({
sub: NewUser.id
}, alg,
'1d',
'user')
const userData = {
error: "none",
jwt: token,
user: {
id: NewUser.id,
username: NewUser.username,
displayName: NewUser.displayName,
firstName: NewUser.firstName,
lastName: NewUser.lastName
}};
logger.info(userData)
await Db.collection("users").insertOne(NewUser);
logger.info(`REGISTER :> Inserted new user (${NewUser.username})`)
return userData
}
/**
* Performs the login process by verifying the provided credentials.
* @param {Object} sanitizedData - The sanitized user login data.
* @param {string} sanitizedData.username - The username provided by the user.
* @param {string} sanitizedData.password - The password provided by the user.
* @returns {Object} - The login result object.
* @returns {string} result.error - The error code if there is an error during the login process.
* @returns {string} result.jwt - The JSON Web Token (JWT) generated upon successful login.
* @returns {Object} result.user - The user information.
* @returns {number} result.user.id - The ID of the user.
* @returns {string} result.user.username - The username of the user.
* @returns {string} result.user.displayName - The display name of the user.
*/
async function LoginService(sanitizedData: { username: string; password: string; }) {
//const passwordHash = await getHashFromPassword(sanitizedData.password);
const dbUser = await MysqlService.User.getByUsername(DbHandler, sanitizedData.username);
if (!dbUser) {
console.log(`LoginService :> User does not exist (${sanitizedData.username})`);
return { error: "userNotFound" };
}
if (sanitizedData.password.length < 6) {
console.log('X')
console.log(`LoginService :> Invalid password (${sanitizedData.username})`);
return { error: "invalidPassword" };
}
const isPasswordValid = await CredentialService.compare(sanitizedData.password, dbUser.hash)
if (!isPasswordValid) {
console.log(isPasswordValid)
console.log(`LoginService :> Invalid password (${sanitizedData.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,
async function getUserByEmail(targetEmail: string): Promise<IDbUser | ISError> {
try {
const dbUser = await MySqlService.User.getByEmail(DbHandler, targetEmail);
if (dbUser.length === 0) {
logger.info(`\n\n> User not found (${targetEmail})\n`);
return {
error: ErrorType.NotFound,
message: "The user was not fund.",
};
}
};
userData.jwt = await JwtService.sign({sub: dbUser.id}, {alg: 'HS512'}, '7d', 'user')
console.log("USERDATA :>");
console.log(userData);
return userData;
if (dbUser.length === 1 && dbUser[0]) return dbUser[0];
return {
error: ErrorType.ServiceError,
message: "To many user found, suspicious.",
};
} 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(`\n\n> Invalid ID (${id})\n`);
return {
error: ErrorType.InvalidData,
message: "Invalid ID length.",
};
}
const dbUser = await MySqlService.User.getById(DbHandler, id);
if (dbUser.length === 0) {
logger.info(`User not found (${id})`);
return {
error: ErrorType.NotFound,
message: "The user was not found.",
};
}
const firstUser = dbUser[0];
if (firstUser) return firstUser;
return {
error: ErrorType.ServiceError,
message: "No user found.",
};
} catch (err) {
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
/*
DbHandler.factorize({
values: {
id: '011010101',
username: 'avnyr',
age: 42,
is_admin: true
},
actionName: "Testing"
}).then((result)=>{
logger.trace(`\n\n> ${result._valuesArray.join(', ')}\n\n> ${result.totalFields}\n\n> ${result._keysTemplate}\n`)
})*/
/**
* 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> {
try {
if (inputData.password.length < 6) {
return {
error: ErrorType.InvalidData,
message: "Password must be at least 6 characters long.",
};
}
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 ("username" in dbUserIfExist) {
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();
logger.info(`\n\n> Trying to insert a new user... (${currentId})\n`);
const NewUser = await MySqlService.User.insert(DbHandler, {
id: currentId,
email: inputData.email,
username: inputData.username,
firstname: inputData.firstName,
lastname: inputData.lastName,
dob: new Date(),
hash: passwordHash,
gdpr: currentDate,
is_admin: false,
is_email_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
return await JwtService.sign(
{
sub: currentId,
},
{
alg: "HS512",
},
"1d",
"user",
);
} catch (err) {
logger.error(`\n\n${err}\n`);
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
/**
* 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<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.",
};
}
// Generate the JSDoc definition for those html status code
const token = await JwtService.sign(
{
sub: dbUser.id,
p: [
{
isAdmin: dbUser.is_admin,
gdpr: dbUser.gdpr,
},
],
},
{
alg: "HS512",
},
"1d",
"user",
);
if (isDebugMode())
logger.trace(
`\n\n> New token for login of "${dbUser.username}".\n${token}`,
);
return token;
} catch (err) {
logger.error(`\n\n${err}\n`);
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
/**
* Retrieves a user from the database by email.
*
* @param {string} email - The email associated with the user.
* @return {Promise<IDbUser | false>} - A Promise that resolves to the user (if found) or false (if not found).
*/
async function getByEmailService(email: string): Promise<IDbUser | false> {
const dbUser = await MySqlService.User.getByEmail(DbHandler, email);
if (dbUser.length === 0) {
if (isDebugMode()) logger.trace(`\n\n> User not found in DB (${email})\n`);
return false;
}
if (isDebugMode()) logger.trace(dbUser);
if (dbUser.length > 1 && dbUser[0]) return dbUser[0];
return false;
}
//TOTest
/**
* 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.
* @returns {Promise<Array<IDbUser> | 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: <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
async function getAllUsersService(): Promise<Array<IDbUser> | 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
async function editUserService(
targetId: string,
inputData: IUserUpdate,
): Promise<ISError | boolean> {
if (!targetId || targetId.length !== 36) {
logger.info(`\n\n> Invalid ID (${targetId})\n`);
return {
error: ErrorType.InvalidData,
message: "Invalid ID length.",
};
}
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" };
const dbUser = await MySqlService.User.getById(DbHandler, targetId);
if (!dbUser[0] || !dbUser[0].id) {
return {
error: ErrorType.NotFound,
message: "User not found.",
};
}
logger.info(`EDIT :> User updated (${targetId})`);
return { error: "none" };
const result = await MySqlService.User.update(DbHandler, inputData);
if (result.affectedRows === 0) {
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
return true;
}
/**
@@ -207,24 +325,27 @@ async function editUserService(targetId, sanitizedData) {
* @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}`)
async function deleteUserService(targetId: string): Promise<boolean> {
logger.info(`Deleting user ${targetId}`);
try {
await Db.collection("users").deleteOne({id: targetId});
return true
const DeleteResult = await MySqlService.User.delete(DbHandler, targetId);
return DeleteResult.affectedRows !== 0;
} catch (e) {
logger.warn(e)
return false
logger.warn(e);
return false;
}
}
if (isDebugMode()) logger.debug("\nService loaded.");
const UserService = {
register: RegisterService,
login: LoginService,
register: register,
login: login,
getAll: getAllUsersService,
getFromId: getUserFromIdService,
getByEmail: getByEmailService,
edit: editUserService,
delete: deleteUserService
}
delete: deleteUserService,
};
export default UserService;
export default UserService;

View File

@@ -0,0 +1,125 @@
import type { IDbVehicle } from "@interfaces/database/IDbVehicle";
//import { ErrorType, type ISError } from "@interfaces/services/ISError";
import MySqlService from "@services/mysql.service";
import { isDebugMode } from "@utils/debugState";
import { Logger } from "tslog";
import { v4 } from "uuid";
const logger = new Logger({
name: "VehicleService",
});
const DbHandler = new MySqlService.Handler("VehicleService");
async function createVehicleService(data: IDbVehicle) {
if (isDebugMode()) logger.debug(`\n\n> Creating a new vehicle...\n`);
try {
const result = await MySqlService.Vehicle.insert(DbHandler, {
id: v4(),
plate_number: data.plate_number,
model_id: data.model_id,
odometer: data.odometer | 0,
health_state: data.health_state,
});
if (result.affectedRows !== 0) {
logger.info("\n\n> Success !");
return true;
}
return false;
} catch (error) {
logger.error(`\n\n> Error creating category: \n${error}\n`);
return false;
}
}
async function updateVehicleService(data: IDbVehicle) {
if (isDebugMode()) logger.debug(`\n\n> Updating vehicle...\n`);
try {
if (!data.id) {
return false;
}
const result = await MySqlService.Vehicle.update(DbHandler, {
id: data.id,
plate_number: data.plate_number,
model_id: data.model_id,
odometer: data.odometer | 0,
health_state: data.health_state,
});
if (result.affectedRows !== 0) {
logger.info("\n\n> Success !");
return true;
}
return false;
} catch (error) {
logger.error(`\n\n> Error updating vehicle: \n${error}\n`);
return false;
}
}
async function getAllVehiclesService() {
try {
const result = await MySqlService.Vehicle.getAll(DbHandler);
return {
iat: Date.now(),
vehicles: result,
total: result.length,
};
} catch (error) {
logger.error(`\n\n> Error getting vehicles: \n${error}\n`);
return false;
}
}
async function getVehicleByIdService(vehicleId: string) {
try {
const result = await MySqlService.Vehicle.getById(DbHandler, vehicleId);
return {
iat: Date.now(),
vehicle: result,
};
} catch (error) {
logger.error(`\n\n> Error getting vehicle by id: \n${error}\n`);
return false;
}
}
async function getAvailableVehicleService() {
try {
const result = await MySqlService.Vehicle.getAvailable(DbHandler);
return {
iat: Date.now(),
vehicles: result,
total: result.length,
};
} catch (error) {
logger.error(`\n\n> Error getting available vehicles: \n${error}\n`);
return false;
}
}
async function deleteVehicleService(vehicleId: string) {
try {
const result = await MySqlService.Vehicle.delete(DbHandler, vehicleId);
if (result.affectedRows !== 0) {
logger.info("\n\n> Success !");
return true;
}
return false;
} catch (error) {
logger.error(`\n\n> Error deleting vehicle: \n${error}\n`);
return false;
}
}
if (isDebugMode()) logger.debug("\nService loaded.");
const VehicleService = {
create: createVehicleService,
update: updateVehicleService,
getAll: getAllVehiclesService,
getById: getVehicleByIdService,
delete: deleteVehicleService,
getAvailable: getAvailableVehicleService,
};
export default VehicleService;

8
src/utils/debugState.ts Normal file
View File

@@ -0,0 +1,8 @@
import process from "node:process";
export function isDebugMode() {
if (process.env["DEBUG"] === "true") {
return true;
}
return false;
}

View File

@@ -0,0 +1,5 @@
export function isEmail(email: string) {
const re =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}

View File

@@ -1,39 +1,52 @@
import JwtService from "@services/jwt.service";
import type {NextFunction, Request, Response} from "express";
import MySqlService from "@services/mysql.service";
import MysqlService from "@services/mysql.service";
import {Logger} from "tslog";
import type { NextFunction, Request, Response } from "express";
import { Logger } from "tslog";
const DbHandler = new MySqlService.Handler('AdminGuard')
const logger = new Logger({name: 'AdminGuard'})
const DbHandler = new MySqlService.Handler("AdminGuard");
const logger = new Logger({
name: "AdminGuard",
});
const UNAUTHORIZED = 401;
const FORBIDDEN = 403;
const UNAUTH_MESSAGE = 'Missing Authorization Header';
const INVALID_TOKEN_MESSAGE = 'Invalid or expired token.';
const PERMISSON_NOT_VALID = 'You are missing the required permission.'
const UNAUTH_MESSAGE = "Missing Authorization Header";
const INVALID_TOKEN_MESSAGE = "Invalid or expired token.";
const PERMISSON_NOT_VALID = "You are missing the required permission.";
async function AdminGuard(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader) {
logger.warn(`Invalid header (${req.ip})`)
return res.status(UNAUTHORIZED).json({message: UNAUTH_MESSAGE});
logger.warn(`Invalid header (${req.ip})`);
return res.status(UNAUTHORIZED).json({
message: UNAUTH_MESSAGE,
});
}
const bearerToken = authHeader.split(' ')[1];
const bearerToken = authHeader.split(" ")[1];
if (!bearerToken) return res.status(FORBIDDEN).json({message: INVALID_TOKEN_MESSAGE});
if (!bearerToken)
return res.status(FORBIDDEN).json({
message: INVALID_TOKEN_MESSAGE,
});
const token = await JwtService.verify(bearerToken);
if (token) {
if (token && token.sub) {
// @ts-ignore
const isSourceAdmin = await MysqlService.User.getAdminStateForId(DbHandler, token.sub)
const isSourceAdmin = await MysqlService.User.getAdminStateForId(
DbHandler,
token.sub,
);
if (isSourceAdmin === true) next();
return res.status(FORBIDDEN).json({message: PERMISSON_NOT_VALID});
return res.status(FORBIDDEN).json({
message: PERMISSON_NOT_VALID,
});
}
return res.status(FORBIDDEN).json({message: INVALID_TOKEN_MESSAGE});
return res.status(FORBIDDEN).json({
message: INVALID_TOKEN_MESSAGE,
});
}
export default AdminGuard
export default AdminGuard;

View File

@@ -1,40 +1,57 @@
import JwtService from "@services/jwt.service";
import type {NextFunction, Request, Response} from "express";
import MySqlService from "@services/mysql.service";
import {Logger} from "tslog";
import type { NextFunction, Request, Response } from "express";
import { Logger } from "tslog";
const DbHandler = new MySqlService.Handler('UserGuard')
const logger = new Logger({name: 'UserGuard'})
const DbHandler = new MySqlService.Handler("UserGuard");
const logger = new Logger({
name: "UserGuard",
});
const UNAUTHORIZED = 401;
const FORBIDDEN = 403;
const UNAUTH_MESSAGE = 'Missing Authorization Header';
const INVALID_TOKEN_MESSAGE = 'Invalid or expired token.';
const USER_NOT_EXIST = 'You dont exist anymore'
const UNAUTH_MESSAGE = "Missing Authorization Header";
const INVALID_TOKEN_MESSAGE = "Invalid or expired token.";
const USER_NOT_EXIST = "You dont exist anymore";
async function UserGuard(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(UNAUTHORIZED).json({message: UNAUTH_MESSAGE});
return res.status(UNAUTHORIZED).json({
message: UNAUTH_MESSAGE,
});
}
const bearerToken = authHeader.split(' ')[1];
const bearerToken = authHeader.split(" ")[1];
if (!bearerToken) return res.status(FORBIDDEN).json({message: INVALID_TOKEN_MESSAGE});
if (!bearerToken)
return res.status(FORBIDDEN).json({
message: INVALID_TOKEN_MESSAGE,
});
const token = await JwtService.verify(bearerToken);
if (token) {
// @ts-ignore
const userId = token.sub;
const user= await MySqlService.User.getById(DbHandler, userId);
if (user) {
logger.info(`An user do a request. (${user?.username})`)
next()
if (!userId) {
logger.error(USER_NOT_EXIST);
return res.status(UNAUTHORIZED).json({
message: USER_NOT_EXIST,
});
}
return res.status(UNAUTHORIZED).json({message: USER_NOT_EXIST});
const user = await MySqlService.User.getById(DbHandler, userId);
if (user) {
logger.info(`An user do a request. (${user?.username})`);
next();
}
return res.status(UNAUTHORIZED).json({
message: USER_NOT_EXIST,
});
}
return res.status(FORBIDDEN).json({message: INVALID_TOKEN_MESSAGE});
return res.status(FORBIDDEN).json({
message: INVALID_TOKEN_MESSAGE,
});
}
export default UserGuard
export default UserGuard;

View File

@@ -1,10 +1,22 @@
{
"compilerOptions": {
"target": "es2017",
"module": "es6",
"rootDir": "./src",
"moduleResolution": "node",
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"noImplicitAny": true,
"strictBindCallApply": true,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": true,
"paths": {
"@services/*": [
"src/services/*"
@@ -26,19 +38,11 @@
]
},
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"removeComments": false,
"noEmitOnError": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
@@ -47,12 +51,10 @@
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"allowUnusedLabels": true,
"allowUnreachableCode": true,
"skipLibCheck": true
"allowUnreachableCode": true
},
}