Compare commits

...

62 Commits

Author SHA1 Message Date
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
32 changed files with 2064 additions and 514 deletions

View File

@@ -1036,7 +1036,7 @@ brief\\_05|schema||user_brief05||UPDATE|G</Grants>
</schema>
<schema id="325" parent="1" name="brief_05">
<Current>1</Current>
<LastIntrospectionLocalTimestamp>2024-04-29.12:54:42</LastIntrospectionLocalTimestamp>
<LastIntrospectionLocalTimestamp>2024-05-03.07:39:06</LastIntrospectionLocalTimestamp>
<CollationName>latin1_swedish_ci</CollationName>
</schema>
<user id="326" parent="1" name="user_brief05"/>
@@ -1063,7 +1063,7 @@ brief\\_05|schema||user_brief05||UPDATE|G</Grants>
<Engine>InnoDB</Engine>
<CollationName>latin1_swedish_ci</CollationName>
</table>
<table id="332" parent="325" name="rent">
<table id="332" parent="325" name="rents">
<Engine>InnoDB</Engine>
<CollationName>latin1_swedish_ci</CollationName>
</table>
@@ -1270,7 +1270,7 @@ brief\\_05|schema||user_brief05||UPDATE|G</Grants>
<Position>7</Position>
</column>
<column id="376" parent="332" name="id">
<DasType>tinyint(1)|0s</DasType>
<DasType>varchar(36)|0s</DasType>
<NotNull>1</NotNull>
<Position>8</Position>
</column>

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>

6
db.sql
View File

@@ -74,13 +74,13 @@ CREATE TABLE `models` (
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `rent`
-- Table structure for table `rents`
--
DROP TABLE IF EXISTS `rent`;
DROP TABLE IF EXISTS 'rents';
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `rent` (
CREATE TABLE `rents` (
`vehicle_id` varchar(36) NOT NULL,
`user_id` varchar(36) NOT NULL,
`active` tinyint(1) NOT NULL,

View File

@@ -51,7 +51,13 @@ try {
try {
app.listen(process.env["APP_PORT"]);
logger.info("Server is running !");
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,7 +1,11 @@
import JwtService from "@services/jwt.service";
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 { isDebugMode } from "@utils/debugState";
import { isEmail } from "@utils/validators/email";
import type { Request, Response } from "express";
import { Logger } from "tslog";
@@ -9,24 +13,19 @@ 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({
return res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "Invalid input data",
});
}
@@ -35,21 +34,28 @@ async function registerUser(req: Request, res: Response): Promise<unknown> {
!body.username ||
!body.firstName ||
!body.lastName ||
!body.displayName
!body.email
) {
logger.warn(`Field(s) missing (${req.ip})`);
return res.type("application/json").status(400).json({
return res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "Field(s) missing",
});
}
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 = {
const sanitizeData: IReqRegister = {
username: `${body.username}`,
displayName: `${body.displayName}`,
email: `${body.email.toLowerCase()}`,
gdpr: gdpr,
password: `${body.password}`,
firstName: `${body.firstName}`,
@@ -58,24 +64,33 @@ async function registerUser(req: Request, res: Response): Promise<unknown> {
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({
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({
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})`);
return res.type("application/json").status(201).json(RegisterServiceResult);
//logger.info(`User registered successfully (${sanitizeData.username})`);
return res
.type("application/json")
.status(HttpStatusCode.Ok)
.json({ token: RegisterServiceResult });
}
/**
@@ -89,34 +104,39 @@ async function registerUser(req: Request, res: Response): Promise<unknown> {
async function loginUser(req: Request, res: Response): Promise<void> {
const body = req.body;
if (!body) {
res.type("application/json").status(400).json({
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({
res.type("application/json").status(HttpStatusCode.BadRequest).json({
error: "Field(s) missing",
});
}
const loginData = {
username: `${body.username}`,
email: `${body.email}`,
password: `${body.password}`,
};
console.log(body);
const LoginServiceResult = await UserService.login(loginData);
console.log(LoginServiceResult);
if (LoginServiceResult.error === "userNotFound") {
console.log("POOL");
res.type("application/json").status(404).json({
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({
if (
typeof LoginServiceResult !== "string" &&
LoginServiceResult.error === 5
) {
res.type("application/json").status(HttpStatusCode.NotAcceptable).json({
error: LoginServiceResult.error,
message: "Invalid password.",
});
@@ -124,74 +144,97 @@ async function loginUser(req: Request, res: Response): Promise<void> {
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];
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);
if (!sourceUser) {
return res.type("application/json").status(404).json({
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({
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) {
return res.type("application/json").status(500).json({
error: "Internal server error",
});
if (typeof AllUserResponse === "object") {
return res
.type("application/json")
.status(HttpStatusCode.InternalServerError)
.json({
error: "Internal server error",
});
}
return res.type("application/json").status(200).json(AllUserResponse);
return res
.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];
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.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);
if (!sourceUser) {
return res.type("application/json").status(404).json({
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 ("username" in sourceUser && !sourceUser.is_admin) {
return res
.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({
return res.type("application/json").status(HttpStatusCode.NotFound).json({
error: "User not found",
});
}
@@ -199,10 +242,11 @@ async function getUser(req: Request, res: Response) {
delete dbUser.passwordHash;
// @ts-ignore
delete dbUser._id;
return res.type("application/json").status(200).json(dbUser);
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) {
@@ -219,7 +263,7 @@ async function editUser(req: Request, res: Response) {
});
}
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",
@@ -237,7 +281,10 @@ async function editUser(req: Request, res: Response) {
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})`,
@@ -264,13 +311,19 @@ async function editUser(req: Request, res: Response) {
`${targetUserId}`,
modifiedData,
);
if (EditUserServiceResult.error === "userNotFound") {
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",
});
}
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",
@@ -285,6 +338,7 @@ async function editUser(req: Request, res: Response) {
});
}
//ToTest
async function deleteUser(req: Request, res: Response): Promise<Response> {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(" ")[1];
@@ -296,7 +350,7 @@ async function deleteUser(req: Request, res: Response): Promise<Response> {
}
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",
@@ -310,7 +364,10 @@ async function deleteUser(req: Request, res: Response): Promise<Response> {
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)
) {
const deleteUserServiceResult = await UserService.delete(`${targetUserId}`);
if (!deleteUserServiceResult) {
logger.error(`Error occurred during user delete (${req.ip})`);
@@ -328,6 +385,7 @@ async function deleteUser(req: Request, res: Response): Promise<Response> {
});
}
//ToTest
async function deleteSelf(req: Request, res: Response) {
const authHeader = req.headers.authorization;
const bearerToken = authHeader?.split(" ")[1];
@@ -338,7 +396,7 @@ async function deleteSelf(req: Request, res: Response) {
});
}
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",
@@ -350,23 +408,27 @@ async function deleteSelf(req: Request, res: Response) {
error: "You dont exist anymore",
});
}
if (sourceUser.id !== req.params["id"]) {
if ("id" in sourceUser && 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",
});
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];
@@ -376,27 +438,29 @@ async function getSelf(req: Request, res: Response) {
});
}
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",
});
}
const dbUser = await UserService.getFromId(payload.sub);
if (!dbUser) {
return res.type("application/json").status(404).json({
error: "User not found",
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(200).json({
id: dbUser.id,
username: dbUser.username,
firstName: dbUser.firstname,
lastName: dbUser.firstname,
isAdmin: dbUser.firstname,
return res.type("application/json").status(404).json({
error: "User not found",
});
}
if (isDebugMode()) logger.debug("\nController loaded.");
const AuthController = {
register: registerUser,
login: loginUser,

View File

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

View File

@@ -1,5 +1,6 @@
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";
@@ -177,6 +178,8 @@ async function getBySlugCategory(
});
}
if (isDebugMode()) logger.debug("\nController loaded.");
const CategoryController = {
create: createCategory,
update: updateCategory,

View File

@@ -1,6 +1,7 @@
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";
@@ -41,7 +42,7 @@ async function createModel(req: Request, res: Response): Promise<Response> {
async function updateModel(req: Request, res: Response): Promise<Response> {
const body: IDbModel = req.body;
const doesExist = await ModelService.getBySlug(`${body.slug_name}`);
const doesExist = await ModelService.getBySlug(`${req.params["modelSlug"]}`);
if (!doesExist) {
logger.error("Model does not exist");
return res.status(404).json({
@@ -131,6 +132,8 @@ async function deleteModel(req: Request, res: Response): Promise<Response> {
//TODO get model with vehicle available.
if (isDebugMode()) logger.debug("\nController loaded.");
const ModelController = {
create: createModel,
update: updateModel,

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

@@ -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,4 +1,5 @@
export interface IDbRent {
id?: string;
vehicle_id: string;
user_id: string;
active: boolean;

View File

@@ -5,7 +5,7 @@ export interface IDbUser {
lastname: string;
dob: Date;
email: string;
is_mail_verified: boolean;
is_email_verified: boolean;
is_admin: boolean;
gdpr: Date;
hash: string;

View File

@@ -4,4 +4,5 @@ export interface IDbVehicle {
model_id: string;
odometer: number;
health_state: number;
isAvailable?: boolean;
}

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

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

View File

@@ -1,8 +1,9 @@
export interface IReqRegister {
username: string;
displayName: string;
firstName: string;
lastName: string;
password: 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

@@ -1,27 +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);
RentRouter.route("/affected").get(UserGuard, RentController.getAssignedToUser);
// Get all vehicle in rent (admin only)
RentRouter.route("/affected/all").get(AdminGuard);
RentRouter.route("/affected/all").get(AdminGuard, RentController.getAll);
// Add a new vehicle (admin only)
RentRouter.route("/veh/new").post(AdminGuard);
RentRouter.route("/veh/new").post(AdminGuard, VehicleController.create);
// Get all vehicles
RentRouter.route("/veh/all").get();
RentRouter.route("/veh/all").get(VehicleController.getAll);
// Rent a specific vehicle
RentRouter.route("/veh/rent/:vehicleId").post(UserGuard);
RentRouter.route("/veh/:vehicleId")
.get(UserGuard)
.patch(AdminGuard)
.delete(AdminGuard);
.get(UserGuard, VehicleController.getById)
.patch(AdminGuard, VehicleController.update)
.delete(AdminGuard, VehicleController.delete);
export default RentRouter;

View File

@@ -1,5 +1,6 @@
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";
@@ -168,6 +169,8 @@ async function deleteBrand(brandId: string): Promise<boolean> {
return true;
}
if (isDebugMode()) logger.debug("\nService loaded.");
const BrandService = {
create: createBrand,
update: updateBrand,

View File

@@ -1,5 +1,6 @@
import type { IDbCategory } from "@interfaces/database/IDbCategory";
import MysqlService from "@services/mysql.service";
import { isDebugMode } from "@utils/debugState";
import { Logger } from "tslog";
import { v4 as uuidv4 } from "uuid";
@@ -127,6 +128,8 @@ async function deleteCategory(id: string): Promise<unknown> {
}
}
if (isDebugMode()) logger.debug("\nService loaded.");
const CategoryService = {
create: createCategory,
delete: deleteCategory,

View File

@@ -1,3 +1,4 @@
import { isDebugMode } from "@utils/debugState";
import {
type JWTHeaderParameters,
type JWTPayload,
@@ -64,6 +65,8 @@ async function JwtSignService(
.sign(new TextEncoder().encode(`${process.env["JWT_SECRET"]}`));
}
if (isDebugMode()) logger.debug("\nService loaded.");
const JwtService = {
verify: JwtVerifyService,
sign: JwtSignService,

View File

@@ -1,5 +1,6 @@
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";
@@ -79,12 +80,14 @@ async function deleteModel(modelSlug: string): Promise<boolean> {
}
logger.info(`Deleting model with ID: ${modelSlug}`);
const doesExist = await MysqlService.Model.getBySlug(DbHandler, modelSlug);
if (!doesExist || !doesExist.id) {
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, doesExist.id);
await MysqlService.Model.delete(DbHandler, target.id);
logger.info("Deletion Successful !");
return true;
} catch (error) {
@@ -103,11 +106,11 @@ 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) {
if (!model[0]) {
logger.warn(`Model with slug ${modelSlug} not found`);
return null;
}
return model;
return model[0];
} catch (error) {
logger.error(`Error fetching model by slug: ${error}`);
return null;
@@ -135,6 +138,8 @@ async function getAllModels(): Promise<IDbModel[] | null> {
}
}
if (isDebugMode()) logger.debug("\nService loaded.");
/**
* ModelService is responsible for managing models.
* @namespace

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,10 +1,14 @@
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",
@@ -13,209 +17,306 @@ const logger = new Logger({
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;
}
async function register(ReqData: IReqRegister) {
if (ReqData.password.length < 6) {
logger.info(`REGISTER :> Invalid password (${ReqData.username})`);
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.",
};
}
if (dbUser.length === 1 && dbUser[0]) return dbUser[0];
return {
error: "invalidPassword",
error: ErrorType.ServiceError,
message: "To many user found, suspicious.",
};
} catch (err) {
logger.error(err);
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
const passwordHash = await CredentialService.hash(ReqData.password);
}
// Does the new user has accepted GDPR ?
if (ReqData.gdpr !== true) {
logger.info(`REGISTER :> GDPR not validated (${ReqData.username})`);
/**
* 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: "gdprNotApproved",
error: ErrorType.ServiceError,
message: "No user found.",
};
} catch (err) {
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
// Check if exist and return
/*
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`)
})*/
const dbUserIfExist = await getUserFromUsername(ReqData.username);
if (dbUserIfExist) {
logger.info(
`REGISTER :> User exist (${dbUserIfExist.username})\n ID:${dbUserIfExist.id}`,
/**
* 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: "exist",
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
const currentDate = new Date();
// New UserService (class)
const NewUser = new User(
ReqData.username,
ReqData.displayName,
passwordHash,
currentDate,
);
NewUser.setFirstName(ReqData.firstName);
NewUser.setLastName(ReqData.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;
}
async function login(ReqData: IReqLogin) {
//const passwordHash = await getHashFromPassword(sanitizedData.password);
const dbUser = await MysqlService.User.getByUsername(
DbHandler,
ReqData.username,
);
if (!dbUser) {
console.log(`LoginService :> User does not exist (${ReqData.username})`);
/**
* 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: "userNotFound",
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
if (ReqData.password.length < 6) {
console.log("X");
console.log(`LoginService :> Invalid password (${ReqData.username})`);
return {
error: "invalidPassword",
};
}
const isPasswordValid = await CredentialService.compare(
ReqData.password,
dbUser.hash,
);
if (!isPasswordValid) {
console.log(isPasswordValid);
console.log(`LoginService :> Invalid password (${ReqData.username})`);
return {
error: "invalidPassword",
};
}
// biome-ignore lint/style/useConst: <explanation>
let userData = {
error: "none",
jwt: "",
user: {
id: dbUser.id,
username: dbUser.username,
displayName: dbUser.displayName,
},
};
userData.jwt = await JwtService.sign(
{
sub: dbUser.id,
},
{
alg: "HS512",
},
"7d",
"user",
);
console.log("USERDATA :>");
console.log(userData);
return userData;
}
/**
* 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,
};
}
/**
* Edits a user in the database.
*
* @param {string} targetId - The ID of the user to be edited.
* @param {object} sanitizedData - The sanitized data to update the user with.
* @returns {object} - An object indicating the result of the operation.
* If the user is not found, the error property will be a string "userNotFound".
* Otherwise, the error property will be a string "none".
*/
async function editUserService(targetId, sanitizedData) {
if (sanitizedData.password) {
const passwordHash = await getHashFromPassword(sanitizedData.password);
delete sanitizedData.password;
logger.info(`Changing password for user "${targetId}"`);
sanitizedData.passwordHash = passwordHash;
}
const updatedUserResult = await Db.collection("users").updateOne(
{
id: targetId,
},
{
$set: sanitizedData,
},
);
if (updatedUserResult.modifiedCount === 0) {
logger.info(`EDIT :> User not found (${targetId})`);
async function 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: "userNotFound",
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
logger.info(`EDIT :> User updated (${targetId})`);
return {
error: "none",
};
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 dbUser = await MySqlService.User.getById(DbHandler, targetId);
if (!dbUser[0] || !dbUser[0].id) {
return {
error: ErrorType.NotFound,
message: "User not found.",
};
}
const result = await MySqlService.User.update(DbHandler, inputData);
if (result.affectedRows === 0) {
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
return true;
}
/**
@@ -224,24 +325,25 @@ 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) {
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;
}
}
if (isDebugMode()) logger.debug("\nService loaded.");
const UserService = {
register: register,
login: login,
getAll: getAllUsersService,
getFromId: getUserFromIdService,
getByEmail: getByEmailService,
edit: editUserService,
delete: deleteUserService,
};

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

@@ -33,7 +33,7 @@ async function AdminGuard(req: Request, res: Response, next: NextFunction) {
const token = await JwtService.verify(bearerToken);
if (token) {
if (token && token.sub) {
// @ts-ignore
const isSourceAdmin = await MysqlService.User.getAdminStateForId(
DbHandler,

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
},
}