From f28976bbe831b2ad09812df25d289e902520d2f2 Mon Sep 17 00:00:00 2001 From: Mohamed Taman Date: Wed, 12 Apr 2023 11:24:37 +0200 Subject: [PATCH] Upgrade to Spring v 3.1.0-M2. --- README.md | 2 +- frontend/package-lock.json | 549 ++++-------------- frontend/package.json | 2 +- pom.xml | 73 +-- .../fa/api/controller/AuthController.java | 56 +- .../fa/api/controller/CityController.java | 121 ++-- .../fa/api/controller/CountryController.java | 30 +- .../api/controller/FileUploadController.java | 195 +++---- .../org/siriusxi/htec/fa/domain/Role.java | 2 +- .../org/siriusxi/htec/fa/domain/User.java | 16 +- .../fa/infra/algorithm/distance/Point.java | 4 +- .../htec/fa/infra/mapper/CommentMapper.java | 4 +- .../fa/infra/security/JwtTokenFilter.java | 8 +- .../fa/infra/security/SecurityConfig.java | 217 +++---- .../security/password/PasswordDigester.java | 92 --- .../security/password/PasswordGenerator.java | 56 -- .../security/password/PasswordManager.java | 17 + .../htec/fa/service/CityMgmtService.java | 20 +- .../htec/fa/service/TravelService.java | 15 +- .../siriusxi/htec/fa/service/UserService.java | 32 +- src/main/resources/static/index.html | 2 +- .../org/siriusxi/htec/fa/CsvToBeanTests.java | 27 +- 22 files changed, 500 insertions(+), 1040 deletions(-) delete mode 100644 src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordDigester.java delete mode 100644 src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordGenerator.java create mode 100644 src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordManager.java diff --git a/README.md b/README.md index de4321c..acaaff7 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# Flight Advisor Service ![GitHub release (latest by date)](https://img.shields.io/github/v/release/mohamed-taman/Flight-Advisor) [![Release Codename](https://img.shields.io/badge/codename-Advisor_3.0-yellow.svg)](https://github.com/mohamed-taman/Flight-Advisor/releases) [![BCH compliance](https://bettercodehub.com/edge/badge/mohamed-taman/Flight-Advisor?branch=main)](https://bettercodehub.com/) [![Twitter Follow](https://img.shields.io/twitter/follow/_tamanm?label=follow%20me&style=social)](https://twitter.com/_tamanm) Flight advisor Service is a set of APIs for primarily finding the cheapest flight from city A to city B based on price, and as a result it's returning all the trip information alongside the distance(s). ## System Functionality - This project is developing a layered monolith **Spring Boot** based project (with the latest version 3.0.0.M2), on Java 18, and with an embedded database mode. - This project is an OAuth2 based project, using JWT token to secure endpoints. So you need to register first to continue using the system. - The functionality is reached based on user role, and there are three roles in the system. - **Admin**: user is a predefined user (*admin@traveladvisor.com/Admin1234*). - Admin needs to log-in first through `/login` API to get their token to contact the system. - This user can upload airports and flight routes. - Admin manages cities by adding, updating, or deleting them. - Actually, the admin can do anything in the system. - **Client**: Clients should register first before using the system through `/register` public API. - After successful registration, they can then use the public `/login` API to get a token to contact the system successfully. - Client can use all read API calls. - Clients can add, manage their comments for a city, add, update, delete their comments, and see other comments. - User can get the cheapest flight by calling `/cities/travel` API and provide airport codes for [from the city] and [to the city]. - **Public**: it is not a role, but anonymous users use APIs under public. - Use `/login` API call to log-in to the system, supplying the username and password. Then you will get a valid JWT token. - Use `/register` API call to register as a client to use the system functionalities related to the client, otherwise for he will receive *Not Authorized*. ## Getting started ### Project Management 1. I have used GitHub projects to manage my tasks in the **Flight-Advisor** project. [Project Link](https://github.com/mohamed-taman/Flight-Advisor/projects/1). 2. All MVP tasks are assigned to the **Flight Advisor API MVP** Milestone. [Milestone Link](https://github.com/mohamed-taman/Flight-Advisor/milestone/1?closed=1). 3. I used pull requests to manage and close assigned tasks. [Tasks Link](https://github.com/mohamed-taman/Flight-Advisor/issues?q=is%3Aclosed). 4. Finally, I have added releases to manage small features sprints until the final release v1.0. [Releases Link](https://github.com/mohamed-taman/Flight-Advisor/releases). 5. Have a look at opened issues for future enhancements. [Opened Issues](https://github.com/mohamed-taman/Flight-Advisor/issues?q=is%3Aopen). ### System components Structure Let's explain first the system layers structure to understand its components: ``` Flight-Advisor --> Parent folder. |- docs --> Contains system images. |- data --> Contains Airports and routes files. |- frontend --> Contains the frontend UI project. |- src/main/java - org.siriusxi.htec.fa (package) |- FlightAdvisorApplication.java --> The main starting point of the application. |- api --> Contains All REST API controllers that receive requests from the client, to process that request, and finally, return appropriate responses. It contains all request and response DTOs. |- repository --> All the database entities CRUD management services. |- domain --> Domain contains all the database modeled entities. |- infra --> Contains all the configurations, exceptions, security management, support utilities, and dto<-->entities mappers, for the system support. |- service --> Contains all the system business login, receives calls from Controllers, call repository to retrieve and manage data, then process them to return back to the controllers. ``` Now, as we have learned about different system layers and components, so it is the time then to play, let's play. ## Playing With Flight Advisor Service First things first, you need to download the following pieces of software to have fun with the project: ### Required software The following are the initially required software pieces: 1. **Maven**: it can be downloaded from https://maven.apache.org/download.cgi#. 2. **Git**: it can be downloaded from https://git-scm.com/downloads. 3. **Java 18.0.0**: it can be downloaded from https://www.oracle.com/java/technologies/downloads/#java18. 4. **Node.js 17.8+**: Latest features, and it can be downloaded from https://nodejs. org/en/download/current/. 5. **Angular CLI 12.2+**: Install it with the following command: `npm install -g @angular/cli@latest` Follow the installation guide for each software, on provided website link and check your software versions from the command line to verify that they are all installed correctly. ### Cloning It Now it is the time to open **terminal** or **git bash** command line, and then clone the project under any of your favorite places with the following command: ```bash > git clone https://github.com/mohamed-taman/Flight-Advisor.git ``` ### Using an IDE I recommend that you work with your Java code using an IDE that supports the development of Spring Boot applications such as **Spring Tool Suite** or **IntelliJ IDEA Community | Ultimate Edition**. All you have to do is fire up your favorite IDE **->** open or import the parent folder `Flight-Advisor,` and everything will be ready for you. ### Building & Running The System To build and run Flight Advisor (FA) system components, run the following command: #### Building FA Components ##### FA backend ```bash 👻 [mtaman]:Flight-Advisor ~~ ./mvnw clean package ``` Now you should expect output like this: ```JavaScript [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ flight-advisor --- [INFO] Building jar: /Flight-Advisor/target/flight-advisor-3.0.jar [INFO] [INFO] --- spring-boot-maven-plugin:2.4.0:repackage (repackage) @ flight-advisor --- [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 14.620 s [INFO] Finished at: 2022-04-13T13:40:50+01:00 [INFO] ----------------------------------------------------------------------- ``` ##### FA frontend ```bash 👻 [mtaman]:Flight-Advisor ~~ cd frontend 👻 [mtaman]:frontend ~~ npm istall && ng build ``` Now you should expect output like this: ```JavaScript removed 1 package and audited 1521 packages in 6.658s 85 packages are looking for funding run `npm fund` for details found 6 vulnerabilities (3 low, 1 moderate, 1 high, 1 critical) run `npm audit fix` to fix them, or `npm audit` for details ✔ Browser application bundle generation complete. ✔ Copying assets complete. ✔ Index html generation complete. Initial Chunk Files | Names | Size vendor.js | vendor | 4.37 MB scripts.js | scripts | 153.22 kB polyfills.js | polyfills | 149.77 kB styles.css | styles | 142.28 kB main.js | main | 13.32 kB runtime.js | runtime | 6.15 kB | Initial Total | 4.83 MB Build at: 2022-04-13T18:30:00.555Z - Hash: ccf039ad034c30696fdb - Time: 8608ms ``` #### Running the System Now it's the time to run the system, and it's straightforward, just hit the following commands: ##### FA backend ```bash 👻 [mtaman]:Flight-Advisor ~~ java --enable-preview -jar ./target/*.jar \ 👻 [mtaman]:Flight-Advisor ~+ --spring.profiles.active=prod ``` Or ```bash 👻 [mtaman]:Flight-Advisor ~~ ./mvnw spring-boot:run \ 👻 [mtaman]:Flight-Advisor ~+ -Dspring-boot.run.jvmArguments="--enable-preview" \ 👻 [mtaman]:Flight-Advisor ~+ -Dspring-boot.run.arguments="--spring.profiles.active=prod" ``` **Flight Advisor backend service** will run, with embedded H2 **database** that will be created under `db` folder and then the `flightDB.mv.db` file, and you should expect an output like this: ```javascript 2022-04-13 13:56:16.587 INFO 2981 --- [ restartedMain] o.s.b.a.h2.H2ConsoleAutoConfiguration: H2 console available at '/db-console'. Database available at 'jdbc:h2:./db/flightDB' 2022-04-13 13:56:18.081 INFO 2981 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer: Tomcat started on port(s): 8090 (http) with context path 'api/v1/flight/service' 2022-04-13 13:56:18.581 INFO 2981 --- [ restartedMain] o.s.h.f.F.AppStartupRunner: Congratulations, Flight Advisor Application, is Up & Running :) ``` ##### FA frontend ```bash 👻 [mtaman]:frontend ~~ npm start ``` **Flight Advisor UI** will run, and you should expect an output like this: ```javascript ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** ✔ Compiled successfully. ``` ### Access Flight Advisor System APIs You can play and test `Flight Advisor` APIs throughout its **OpenAPI** interface. 1. Go to the landing page at the following URL [http://localhost:8090/api/v1/flight/service/] (http://localhost:8090/api/v1/flight/service/). 2. Follow the link on the page, and you should see the following: ![System APIs](docs/images/SystemAPI.png) 3. More beautifully with its UI at [http://localhost:4200/](http://localhost:4200/), and you expect this view for login: ![ClientLogin page](docs/images/FA-Login.png) And this view after login, to travel: ![System APIs](docs/images/FA-Travel.png) #### System Behaviour 1. First, if you want to upload airports or routes (in the data folder) using the **Files upload Management** section: 1. You need to log-in with the provided admin username/password to the `public/login` endpoint. 2. On a successful login, the response will contain an authorization token; copy it. 3. Click on the Authorize button and paste it in the only field out there, `value,` then click the `Authorize` button. 4. Now, all locks are closed, and you can use the secured APIs. 2. If you are a new client and want to access the system, you need first to register through the `/public/register` endpoint. Then follow previous point **1.1**. 3. When uploading the Airports file, countries and cities will be created automatically. 4. All search parameters are case-insensitive, and the system use like search by default. 5. To add a city, you need a country, so from the **country management** section, you can search for the country you want. 6. To manage comments, you need a city, so from the **city management** section, you can get all cities or search for a specific city. 7. You can search for all airports for a specific city to know their codes, so you can use travel service. 8. Use travel service to search for the cheapest flight from city to city, for example traveling from **CAI** (*Cairo International Airport, Egypt*) to **LAX** (*Los Angeles, USA*) the following results will be returned: ```JSON [ { "start": { "airport": "Cairo International Airport", "city": "Cairo", "country": "Egypt", "iata": "CAI" }, "through": [ { "airport": "Lester B. Pearson International Airport", "city": "Toronto", "country": "Canada", "iata": "YYZ" } ], "end": { "airport": "Los Angeles International Airport", "city": "Los Angeles", "country": "United States", "iata": "LAX" }, "price": { "total": 62.17, "currency": "US" }, "distance": { "total": 12722.2, "in": "KM" } } ] ``` ### Access Flight Advisor System Database You can access database through it online console from the following URL [http://localhost:8090/api/v1/flight/service/db-console/] (http://localhost:8090/api/v1/flight/service/db-console/) with the following properties: - Driver class: `org.h2.Driver` - JDBC URL: `jdbc:h2:./db/flightDB` - user: `sa` - password: `Admin1234` ![System DB](docs/images/SystemDB.png) Hit test, and it should show a green bar for successful settings. So hit the **Connect** button and explore all data. ### Stopping The System Just press the `CTRL+C` keys on the terminal. ### Closing The Story Finally, I hope you enjoyed the application and find it useful. If you would like to enhance, please open **PR**, and yet give it a 🌟. ## The End Happy Coding 😊 ## License Copyright (C) 2022 Mohamed Taman, Licensed under the **MIT License**. \ No newline at end of file +# Flight Advisor Service ![GitHub release (latest by date)](https://img.shields.io/github/v/release/mohamed-taman/Flight-Advisor) [![Release Codename](https://img.shields.io/badge/codename-Advisor_4.0-yellow.svg)](https://github.com/mohamed-taman/Flight-Advisor/releases) [![Twitter Follow](https://img.shields.io/twitter/follow/_tamanm?label=follow%20me&style=social)](https://twitter.com/_tamanm) Flight advisor Service is a set of APIs for primarily finding the cheapest flight from city A to city B based on price, and as a result it's returning all the trip information alongside the distance(s). ## System Functionality - This project is developing a layered monolith **Spring Boot** based project (with the latest version **3.1.0-M2**), on Java 20, and with an embedded database mode. - This project is an OAuth2 based project, using JWT token to secure endpoints. So you need to register first to continue using the system. - The functionality is reached based on user role, and there are three roles in the system. - **Admin**: user is a predefined user (*admin@traveladvisor.com/Admin1234*). - Admin needs to log-in first through `/login` API to get their token to contact the system. - This user can upload airports and flight routes. - Admin manages cities by adding, updating, or deleting them. - Actually, the admin can do anything in the system. - **Client**: Clients should register first before using the system through `/register` public API. - After successful registration, they can then use the public `/login` API to get a token to contact the system successfully. - Client can use all read API calls. - Clients can add, manage their comments for a city, add, update, delete their comments, and see other comments. - Client can get the cheapest flight by calling `/cities/travel` API and provide airport codes for [from the city] and [to the city]. - **Public**: it is not a role, but anonymous users use APIs under public. - Use `/login` API call to log-in to the system, supplying the username and password. Then you will get a valid JWT token. - Use `/register` API call to register as a client to use the system functionalities related to the client, otherwise for he will receive *Not Authorized*. ## Getting started ### Project Management 1. I have used GitHub projects to manage my tasks in the **Flight-Advisor** project. [Project Link](https://github.com/mohamed-taman/Flight-Advisor/projects/1). 2. All MVP tasks are assigned to the **Flight Advisor API MVP** Milestone. [Milestone Link](https://github.com/mohamed-taman/Flight-Advisor/milestone/1?closed=1). 3. I used pull requests to manage and close assigned tasks. [Tasks Link](https://github.com/mohamed-taman/Flight-Advisor/issues?q=is%3Aclosed). 4. Finally, I have added releases to manage small features sprints until the final release v1.0. [Releases Link](https://github.com/mohamed-taman/Flight-Advisor/releases). 5. Have a look at opened issues for future enhancements. [Opened Issues](https://github.com/mohamed-taman/Flight-Advisor/issues?q=is%3Aopen). ### System components Structure Let's explain first the system layers structure to understand its components: ``` Flight-Advisor --> Parent folder. |- docs --> Contains system images. |- data --> Contains Airports and routes files. |- frontend --> Contains the frontend UI project. |- src/main/java - org.siriusxi.htec.fa (package) |- FlightAdvisorApplication.java --> The main starting point of the application. |- api --> Contains All REST API controllers that receive requests from the client, to process that request, and finally, return appropriate responses. It contains all request and response DTOs. |- repository --> All the database entities CRUD management services. |- domain --> Domain contains all the database modeled entities. |- infra --> Contains all the configurations, exceptions, security management, support utilities, and dto<-->entities mappers, for the system support. |- service --> Contains all the system business login, receives calls from Controllers, call repository to retrieve and manage data, then process them to return back to the controllers. ``` Now, as we have learned about different system layers and components, so it is the time then to play, let's play. ## Playing With Flight Advisor Service First things first, you need to download the following pieces of software to have fun with the project: ### Required software The following are the initially required software pieces: 1. **Maven**: it can be downloaded from https://maven.apache.org/download.cgi#. 2. **Git**: it can be downloaded from https://git-scm.com/downloads. 3. **Java 18.0.0**: it can be downloaded from https://www.oracle.com/java/technologies/downloads/#java18. 4. **Node.js 17.8+**: Latest features, and it can be downloaded from https://nodejs. org/en/download/current/. 5. **Angular CLI 12.2+**: Install it with the following command: `npm install -g @angular/cli@latest` Follow the installation guide for each software, on provided website link and check your software versions from the command line to verify that they are all installed correctly. ### Cloning It Now it is the time to open **terminal** or **git bash** command line, and then clone the project under any of your favorite places with the following command: ```bash > git clone https://github.com/mohamed-taman/Flight-Advisor.git ``` ### Using an IDE I recommend that you work with your Java code using an IDE that supports the development of Spring Boot applications such as **Spring Tool Suite** or **IntelliJ IDEA Community | Ultimate Edition**. All you have to do is fire up your favorite IDE **->** open or import the parent folder `Flight-Advisor,` and everything will be ready for you. ### Building & Running The System To build and run Flight Advisor (FA) system components, run the following command: #### Building FA Components ##### FA backend ```bash 👻 [mtaman]:Flight-Advisor ~~ ./mvnw clean package ``` Now you should expect output like this: ```JavaScript [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ flight-advisor --- [INFO] Building jar: /Flight-Advisor/target/flight-advisor-3.0.jar [INFO] [INFO] --- spring-boot-maven-plugin:2.4.0:repackage (repackage) @ flight-advisor --- [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 14.620 s [INFO] Finished at: 2022-04-13T13:40:50+01:00 [INFO] ----------------------------------------------------------------------- ``` ##### FA frontend ```bash 👻 [mtaman]:Flight-Advisor ~~ cd frontend 👻 [mtaman]:frontend ~~ npm install && ng build ``` Now you should expect output like this: ```JavaScript removed 1 package and audited 1521 packages in 6.658s 85 packages are looking for funding run `npm fund` for details found 6 vulnerabilities (3 low, 1 moderate, 1 high, 1 critical) run `npm audit fix` to fix them, or `npm audit` for details ✔ Browser application bundle generation complete. ✔ Copying assets complete. ✔ Index html generation complete. Initial Chunk Files | Names | Size vendor.js | vendor | 4.37 MB scripts.js | scripts | 153.22 kB polyfills.js | polyfills | 149.77 kB styles.css | styles | 142.28 kB main.js | main | 13.32 kB runtime.js | runtime | 6.15 kB | Initial Total | 4.83 MB Build at: 2022-04-13T18:30:00.555Z - Hash: ccf039ad034c30696fdb - Time: 8608ms ``` #### Running the System Now it's the time to run the system, and it's straightforward, just hit the following commands: ##### FA backend ```bash 👻 [mtaman]:Flight-Advisor ~~ java --enable-preview -jar ./target/*.jar \ 👻 [mtaman]:Flight-Advisor ~+ --spring.profiles.active=prod ``` Or ```bash 👻 [mtaman]:Flight-Advisor ~~ ./mvnw spring-boot:run \ 👻 [mtaman]:Flight-Advisor ~+ -Dspring-boot.run.jvmArguments="--enable-preview" \ 👻 [mtaman]:Flight-Advisor ~+ -Dspring-boot.run.arguments="--spring.profiles.active=prod" ``` **Flight Advisor backend service** will run, with embedded H2 **database** that will be created under `db` folder and then the `flightDB.mv.db` file, and you should expect an output like this: ```javascript 2022-04-13 13:56:16.587 INFO 2981 --- [ restartedMain] o.s.b.a.h2.H2ConsoleAutoConfiguration: H2 console available at '/db-console'. Database available at 'jdbc:h2:./db/flightDB' 2022-04-13 13:56:18.081 INFO 2981 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer: Tomcat started on port(s): 8090 (http) with context path 'api/v1/flight/service' 2022-04-13 13:56:18.581 INFO 2981 --- [ restartedMain] o.s.h.f.F.AppStartupRunner: Congratulations, Flight Advisor Application, is Up & Running :) ``` ##### FA frontend ```bash 👻 [mtaman]:frontend ~~ npm start ``` **Flight Advisor UI** will run, and you should expect an output like this: ```javascript ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** ✔ Compiled successfully. ``` ### Access Flight Advisor System APIs You can play and test `Flight Advisor` APIs throughout its **OpenAPI** interface. 1. Go to the landing page at the following URL [http://localhost:8090/api/v1/flight/service/] (http://localhost:8090/api/v1/flight/service/). 2. Follow the link on the page, and you should see the following: ![System APIs](docs/images/SystemAPI.png) 3. More beautifully with its UI at [http://localhost:4200/](http://localhost:4200/), and you expect this view for login: ![ClientLogin page](docs/images/FA-Login.png) And this view after login, to travel: ![System APIs](docs/images/FA-Travel.png) #### System Behaviour 1. First, if you want to upload airports or routes (in the data folder) using the **Files upload Management** section: 1. You need to log-in with the provided admin username/password to the `public/login` endpoint. 2. On a successful login, the response will contain an authorization token; copy it. 3. Click on the Authorize button and paste it in the only field out there, `value,` then click the `Authorize` button. 4. Now, all locks are closed, and you can use the secured APIs. 2. If you are a new client and want to access the system, you need first to register through the `/public/register` endpoint. Then follow previous point **1.1**. 3. When uploading the Airports file, countries and cities will be created automatically. 4. All search parameters are case-insensitive, and the system use like search by default. 5. To add a city, you need a country, so from the **country management** section, you can search for the country you want. 6. To manage comments, you need a city, so from the **city management** section, you can get all cities or search for a specific city. 7. You can search for all airports for a specific city to know their codes, so you can use travel service. 8. Use travel service to search for the cheapest flight from city to city, for example traveling from **CAI** (*Cairo International Airport, Egypt*) to **LAX** (*Los Angeles, USA*) the following results will be returned: ```JSON [ { "start": { "airport": "Cairo International Airport", "city": "Cairo", "country": "Egypt", "iata": "CAI" }, "through": [ { "airport": "Lester B. Pearson International Airport", "city": "Toronto", "country": "Canada", "iata": "YYZ" } ], "end": { "airport": "Los Angeles International Airport", "city": "Los Angeles", "country": "United States", "iata": "LAX" }, "price": { "total": 62.17, "currency": "US" }, "distance": { "total": 12722.2, "in": "KM" } } ] ``` ### Access Flight Advisor System Database You can access database through it online console from the following URL [http://localhost:8090/api/v1/flight/service/db-console/] (http://localhost:8090/api/v1/flight/service/db-console/) with the following properties: - Driver class: `org.h2.Driver` - JDBC URL: `jdbc:h2:./db/flightDB` - user: `sa` - password: `Admin1234` ![System DB](docs/images/SystemDB.png) Hit test, and it should show a green bar for successful settings. So hit the **Connect** button and explore all data. ### Stopping The System Just press the `CTRL+C` keys on the terminal. ### Closing The Story Finally, I hope you enjoyed the application and find it useful. If you would like to enhance, please open **PR**, and yet give it a 🌟. ## The End Happy Coding 😊 ## License Copyright (C) 2023 Mohamed Taman, Licensed under the **MIT License**. \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a99f65d..005ef49 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,34 +18,34 @@ "@angular/platform-browser": "~12.2.3", "@angular/platform-browser-dynamic": "~12.2.3", "@angular/router": "~12.2.3", - "@popperjs/core": "^2.9.3", - "bootstrap": "^4.6.0", + "@popperjs/core": "^2.6.0", + "bootstrap": "^4.5.3", "build": "^0.1.4", - "jquery": "^3.6.0", - "js-yaml": "^4.1.0", - "ngx-bootstrap-icons": "^1.5.3", + "jquery": "^3.5.1", + "js-yaml": "^3.14.1", + "ngx-bootstrap-icons": "^1.3.1", "popper.js": "^1.16.1", - "rxjs": "~7.3.0", - "tslib": "^2.3.1", - "uglify-js": "^3.14.1", + "rxjs": "~6.6.3", + "tslib": "^2.0.3", + "uglify-js": "^3.12.2", "zone.js": "~0.11.4" }, "devDependencies": { "@angular-devkit/build-angular": "~12.2.3", "@angular/cli": "~12.2.3", "@angular/compiler-cli": "~12.2.3", - "@types/jasmine": "~3.8.2", - "@types/node": "^16.7.6", - "codelyzer": "^6.0.2", - "jasmine-core": "~3.9.0", - "jasmine-spec-reporter": "~7.0.0", + "@types/jasmine": "~3.6.2", + "@types/node": "^14.14.14", + "codelyzer": "^6.0.1", + "jasmine-core": "~3.6.0", + "jasmine-spec-reporter": "~6.0.0", "karma": "~6.3.4", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-jasmine": "~4.0.1", - "karma-jasmine-html-reporter": "^1.7.0", + "karma-jasmine-html-reporter": "^1.5.4", "protractor": "~7.0.0", - "ts-node": "~10.2.1", + "ts-node": "~9.1.1", "tslint": "~6.1.3", "typescript": "~4.3.5" } @@ -78,24 +78,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/architect/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/build-angular": { "version": "12.2.3", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-12.2.3.tgz", @@ -237,24 +219,6 @@ "node": ">= 6" } }, - "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/build-angular/node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -328,24 +292,6 @@ "webpack-dev-server": "^3.1.4" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/core": { "version": "12.2.3", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.3.tgz", @@ -387,24 +333,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/schematics": { "version": "12.2.3", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.3.tgz", @@ -421,24 +349,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular/animations": { "version": "12.2.3", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.3.tgz", @@ -2417,27 +2327,6 @@ "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", - "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -2717,30 +2606,6 @@ "node": ">=10.13.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, "node_modules/@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -2796,9 +2661,9 @@ } }, "node_modules/@types/jasmine": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.8.2.tgz", - "integrity": "sha512-u5h7dqzy2XpXTzhOzSNQUQpKGFvROF8ElNX9P/TJvsHnTg/JvsAseVsGWQAQQldqanYaM+5kwxW909BBFAUYsg==", + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.11.tgz", + "integrity": "sha512-S6pvzQDvMZHrkBz2Mcn/8Du7cpr76PlRJBAoHnSDNbulULsH5dp0Gns+WRyNX5LHejz/ljxK4/vIHK/caHt6SQ==", "dev": true }, "node_modules/@types/json-schema": { @@ -2814,9 +2679,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.6.tgz", - "integrity": "sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg==", + "version": "14.18.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.42.tgz", + "integrity": "sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==", "dev": true }, "node_modules/@types/parse-json": { @@ -3067,15 +2932,6 @@ "acorn": "^8" } }, - "node_modules/acorn-walk": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", - "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -3315,9 +3171,12 @@ "dev": true }, "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } }, "node_modules/aria-query": { "version": "3.0.0", @@ -4450,18 +4309,6 @@ "zone.js": "~0.10.2" } }, - "node_modules/codelyzer/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, "node_modules/codelyzer/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -6209,7 +6056,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -7920,6 +7766,15 @@ "node": ">=8" } }, + "node_modules/inquirer/node_modules/rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/inquirer/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8436,15 +8291,15 @@ } }, "node_modules/jasmine-core": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz", - "integrity": "sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", "dev": true }, "node_modules/jasmine-spec-reporter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", - "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz", + "integrity": "sha512-MvTOVoMxDZAftQYBApIlSfKnGMzi9cj351nXeqtnZTuXffPlbONN31+Es7F+Ke4okUeQ2xISukt4U1npfzLVrQ==", "dev": true, "dependencies": { "colors": "1.4.0" @@ -8514,11 +8369,12 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dependencies": { - "argparse": "^2.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -14178,17 +14034,20 @@ } }, "node_modules/rxjs": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.3.0.tgz", - "integrity": "sha512-p2yuGIg9S1epc3vrjKf6iVb3RCaAYjYskkO+jHIaV0IjOPlJop4UnodOoFb2xeNwlguqLYvGw1b1McillYb5Gw==", + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dependencies": { - "tslib": "~2.1.0" + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" } }, "node_modules/rxjs/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/safe-buffer": { "version": "5.1.2", @@ -15127,8 +14986,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/sshpk": { "version": "1.16.1", @@ -15796,47 +15654,29 @@ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "node_modules/ts-node": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", - "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", "dev": true, "dependencies": { - "@cspotcode/source-map-support": "0.6.1", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "source-map-support": "^0.5.17", "yn": "3.1.1" }, "bin": { "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=10.0.0" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } } }, "node_modules/tslib": { @@ -15875,28 +15715,6 @@ "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" } }, - "node_modules/tslint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/tslint/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/tslint/node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -17499,23 +17317,6 @@ "requires": { "@angular-devkit/core": "12.2.3", "rxjs": "6.6.7" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@angular-devkit/build-angular": { @@ -17614,23 +17415,6 @@ "debug": "4" } }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -17675,23 +17459,6 @@ "requires": { "@angular-devkit/architect": "0.1202.3", "rxjs": "6.6.7" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@angular-devkit/core": { @@ -17725,21 +17492,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true } } }, @@ -17752,23 +17504,6 @@ "@angular-devkit/core": "12.2.3", "ora": "5.4.1", "rxjs": "6.6.7" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@angular/animations": { @@ -19125,21 +18860,6 @@ "to-fast-properties": "^2.0.0" } }, - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", - "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", - "dev": true, - "requires": { - "@cspotcode/source-map-consumer": "0.8.0" - } - }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -19345,30 +19065,6 @@ "integrity": "sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow==", "dev": true }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, "@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -19424,9 +19120,9 @@ } }, "@types/jasmine": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.8.2.tgz", - "integrity": "sha512-u5h7dqzy2XpXTzhOzSNQUQpKGFvROF8ElNX9P/TJvsHnTg/JvsAseVsGWQAQQldqanYaM+5kwxW909BBFAUYsg==", + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.11.tgz", + "integrity": "sha512-S6pvzQDvMZHrkBz2Mcn/8Du7cpr76PlRJBAoHnSDNbulULsH5dp0Gns+WRyNX5LHejz/ljxK4/vIHK/caHt6SQ==", "dev": true }, "@types/json-schema": { @@ -19442,9 +19138,9 @@ "dev": true }, "@types/node": { - "version": "16.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.6.tgz", - "integrity": "sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg==", + "version": "14.18.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.42.tgz", + "integrity": "sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==", "dev": true }, "@types/parse-json": { @@ -19683,12 +19379,6 @@ "dev": true, "requires": {} }, - "acorn-walk": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", - "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==", - "dev": true - }, "adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -19868,9 +19558,12 @@ "dev": true }, "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } }, "aria-query": { "version": "3.0.0", @@ -20748,15 +20441,6 @@ "dev": true, "requires": {} }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -22126,8 +21810,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esrecurse": { "version": "4.3.0", @@ -23483,6 +23166,15 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23874,15 +23566,15 @@ } }, "jasmine-core": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz", - "integrity": "sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", "dev": true }, "jasmine-spec-reporter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", - "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz", + "integrity": "sha512-MvTOVoMxDZAftQYBApIlSfKnGMzi9cj351nXeqtnZTuXffPlbONN31+Es7F+Ke4okUeQ2xISukt4U1npfzLVrQ==", "dev": true, "requires": { "colors": "1.4.0" @@ -23933,11 +23625,12 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "requires": { - "argparse": "^2.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -28148,17 +27841,17 @@ } }, "rxjs": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.3.0.tgz", - "integrity": "sha512-p2yuGIg9S1epc3vrjKf6iVb3RCaAYjYskkO+jHIaV0IjOPlJop4UnodOoFb2xeNwlguqLYvGw1b1McillYb5Gw==", + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "requires": { - "tslib": "~2.1.0" + "tslib": "^1.9.0" }, "dependencies": { "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" } } }, @@ -28937,8 +28630,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "sshpk": { "version": "1.16.1", @@ -29435,22 +29127,16 @@ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "ts-node": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", - "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", "dev": true, "requires": { - "@cspotcode/source-map-support": "0.6.1", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "source-map-support": "^0.5.17", "yn": "3.1.1" } }, @@ -29480,25 +29166,6 @@ "tsutils": "^2.29.0" }, "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 061ba06..237f267 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -62,4 +62,4 @@ "tslint": "~6.1.3", "typescript": "~4.3.5" } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index b6f662d..ce2f02e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.0.0-M2 + 3.1.0-M2 @@ -58,27 +58,23 @@ - 18 - 18 - 18 + 20 + 20 + 20 + 20 UTF-8 UTF-8 - 3.0.0-M5 - 3.0.0-M5 - 0.11.2 - 1.5.0.RC1 - 2.0.0-M1 - 1.18.22 - 5.6 + 3.0.0 + 3.0.0 + 0.11.5 + 1.5.3.Final + 2.1.0 + 5.7.1 1.0.1 - 1.8.0 - 1.4 - 2.11.0 + 1.8.1 4.4 - 5.6.5.Final - 2.13.2.2 @@ -92,18 +88,6 @@ org.springframework.boot spring-boot-starter-data-jpa - - - org.hibernate - hibernate-core-jakarta - - - - - - org.hibernate - hibernate-core-jakarta - ${hibernate-core-jakarta.version} @@ -124,38 +108,12 @@ org.springframework.boot spring-boot-starter-web - - - - com.fasterxml.jackson.core - jackson-databind - - org.springdoc springdoc-openapi-starter-webmvc-ui ${org.springdoc.version} - - - - com.fasterxml.jackson.core - jackson-databind - - - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson-databind.version} @@ -192,7 +150,7 @@ ${io.jsonwebtoken.version} runtime - + @@ -207,11 +165,11 @@ org.springframework.boot spring-boot-starter-actuator + org.projectlombok lombok - ${lombok.version} true @@ -302,6 +260,9 @@ maven-compiler-plugin true + + -Xlint + org.projectlombok diff --git a/src/main/java/org/siriusxi/htec/fa/api/controller/AuthController.java b/src/main/java/org/siriusxi/htec/fa/api/controller/AuthController.java index 32c2611..163c35a 100644 --- a/src/main/java/org/siriusxi/htec/fa/api/controller/AuthController.java +++ b/src/main/java/org/siriusxi/htec/fa/api/controller/AuthController.java @@ -3,12 +3,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.siriusxi.htec.fa.api.model.request.AuthRequest; import org.siriusxi.htec.fa.api.model.request.CreateUserRequest; import org.siriusxi.htec.fa.api.model.response.UserView; import org.siriusxi.htec.fa.domain.User; -import org.siriusxi.htec.fa.infra.mapper.UserMapper; import org.siriusxi.htec.fa.infra.security.jwt.JwtTokenHelper; import org.siriusxi.htec.fa.service.UserService; import org.springframework.http.HttpHeaders; @@ -34,57 +34,49 @@ // TODO: add refresh token method, change password. @Log4j2 @Tag(name = "Authentication", - description = "A set of public APIs, for managing user authentication, and the registration.") + description = "A set of public APIs, for managing user authentication, and the registration.") @RestController @RequestMapping("public") +@RequiredArgsConstructor public class AuthController { - + private final AuthenticationManager authenticationManager; private final UserService userService; - private final UserMapper userMapper; - - public AuthController(AuthenticationManager authenticationManager, - UserService userService, - UserMapper userMapper) { - this.authenticationManager = authenticationManager; - this.userService = userService; - this.userMapper = userMapper; - } - + @Operation(description = """ - An API call to authenticate user before using the system, - and if successful a valid token is returned. - """) + An API call to authenticate user before using the system, + and if successful a valid token is returned. + """) @PostMapping(value = "login") public ResponseEntity authenticate(@RequestBody @Valid AuthRequest request) { try { var authenticate = authenticationManager - .authenticate(new UsernamePasswordAuthenticationToken( - request.username(), - request.password())); - + .authenticate(new UsernamePasswordAuthenticationToken( + request.username(), + request.password())); + User user = (User) authenticate.getPrincipal(); - + return ResponseEntity.ok() - .header(HttpHeaders.AUTHORIZATION, - JwtTokenHelper.generateAccessToken( - user.getId(), - user.getUsername())) - .body(userMapper.toView(user)); + .header(HttpHeaders.AUTHORIZATION, + JwtTokenHelper.generateAccessToken( + user.getId(), + user.getUsername())) + .body(userService.mapper().toView(user)); } catch (BadCredentialsException ex) { throw new HttpClientErrorException(UNAUTHORIZED, UNAUTHORIZED.getReasonPhrase()); } } - + @Operation(description = """ - An API call, to register the user, - to be able to authenticate and use the system. - """) + An API call, to register the user, + to be able to authenticate and use the system. + """) @PostMapping(value = "register") public UserView register(@RequestBody @Valid CreateUserRequest userRequest) { - + log.debug("User to be created: {}", userRequest); return userService.create(userRequest); } - + } diff --git a/src/main/java/org/siriusxi/htec/fa/api/controller/CityController.java b/src/main/java/org/siriusxi/htec/fa/api/controller/CityController.java index f9fcf1e..0aca0fa 100644 --- a/src/main/java/org/siriusxi/htec/fa/api/controller/CityController.java +++ b/src/main/java/org/siriusxi/htec/fa/api/controller/CityController.java @@ -4,11 +4,11 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.security.RolesAllowed; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Size; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.siriusxi.htec.fa.api.model.request.CommentUpSrtRequest; import org.siriusxi.htec.fa.api.model.request.CreateCityRequest; @@ -18,20 +18,11 @@ import org.siriusxi.htec.fa.api.model.response.CityView; import org.siriusxi.htec.fa.api.model.response.CommentView; import org.siriusxi.htec.fa.api.model.response.TripView; -import org.siriusxi.htec.fa.domain.Role; import org.siriusxi.htec.fa.domain.User; import org.siriusxi.htec.fa.service.CityMgmtService; import org.siriusxi.htec.fa.service.TravelService; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -43,81 +34,77 @@ */ @Log4j2 @Tag(name = "City Management", - description = "A set of authorized APIs, for getting and managing system cities.") + description = "A set of authorized APIs, for getting and managing system cities.") @RestController -@RequestMapping("v1/cities") +@RequestMapping("cities") +@RequiredArgsConstructor public class CityController { - + private final CityMgmtService cityMgmtService; private final TravelService travelService; - - public CityController(CityMgmtService cityMgmtService, TravelService travelService) { - this.cityMgmtService = cityMgmtService; - this.travelService = travelService; - } - + @Operation(summary = "Get all cities.", - description = "Get city or all cities. You can limit the # of returned comments.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Get city or all cities. You can limit the # of returned comments.", + security = {@SecurityRequirement(name = "bearer-key")}) @GetMapping public List getAllCities(@RequestParam(defaultValue = "0") @Min(0) @Max(Integer.MAX_VALUE) int cLimit) { return cityMgmtService.searchCities(new SearchCityRequest(""), cLimit); } - + @Operation(summary = "Search all cities by name.", - description = "Find city or all cities by name. You can limit the # of returned comments.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Find city or all cities by name. You can limit the # of returned comments.", + security = {@SecurityRequirement(name = "bearer-key")}) @PostMapping("search") public List searchCities(@RequestParam(defaultValue = "0") @Min(0) @Max(Integer.MAX_VALUE) int cLimit, @RequestBody @Valid SearchCityRequest request) { return cityMgmtService.searchCities(request, cLimit); } - - @RolesAllowed(Role.ADMIN) + + @Operation(summary = "Add a new city.", - description = """ - Add a new city to the system. - - Note that you can add country to city either: - 1. by its id if it is already exist in the system, or - 2. by country name if not exist, then the system will \ - creat the country and attach it to the city. - """, - security = {@SecurityRequirement(name = "bearer-key")}) + description = """ + Add a new city to the system. + - Note that you can add country to city either: + 1. by its id if it is already exist in the system, or + 2. by country name if not exist, then the system will \ + creat the country and attach it to the city. + """, + security = {@SecurityRequirement(name = "bearer-key")}) @PostMapping public CityView createCity(@RequestBody @Valid CreateCityRequest request) { return cityMgmtService.addCity(request); } - - + + @Operation(summary = "Exited! and wanna travel.", - description = "Find the cheapest trip from source country to a destination country.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Find the cheapest trip from source country to a destination country.", + security = {@SecurityRequirement(name = "bearer-key")}) @GetMapping("travel") public List travel(@RequestParam @Size(min = 3) String from, @RequestParam @Size(min = 3) String to) { - + if (from.isBlank() || to.isBlank()) throw new IllegalArgumentException(""" - Neither source nor destination airport codes can be empty! \ - Please try with other valid values. - """); - + Neither source nor destination airport codes can be empty! \ + Please try with other valid values. + """); + if (from.trim().equals(to.trim())) throw new IllegalArgumentException(String.format( - "You are traveling from and to the same destination [%s]", to)); - + "You are traveling from and to the same destination [%s]", to)); + return travelService - .travel(from.trim().toUpperCase(), to.trim().toUpperCase()); + .travel(from.trim().toUpperCase(), to.trim().toUpperCase()); } - + /* Airport Management */ @Operation(summary = "Find city airports.", - description = "Find all airports for a specific city.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Find all airports for a specific city.", + security = {@SecurityRequirement(name = "bearer-key")}) @PostMapping("{id}/airports") public List searchAirports(@Parameter(description = "City Id") @PathVariable(name = "id") @@ -125,25 +112,25 @@ public List searchAirports(@Parameter(description = "City Id") @RequestBody @Valid SearchAirportRequest request) { return cityMgmtService.searchAirports(request, cityId); } - + @Operation(summary = "Get all airports by a any name.", - description = "Find all airports by airport, city or country name.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Find all airports by airport, city or country name.", + security = {@SecurityRequirement(name = "bearer-key")}) @GetMapping("/airports") public List searchForCityOrCountryAirports( - @Parameter(description = "Airport, city or country name") - @RequestParam @Size(min = 1) String name) { + @Parameter(description = "Airport, city or country name") + @RequestParam @Size(min = 1) String name) { return travelService.findAirportsForCityOrCountry(name); } /* Comments Management */ - + //Add comment @Operation(summary = "Add a city comment.", - description = "Wanna add a comment to a city you have visited.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Wanna add a comment to a city you have visited.", + security = {@SecurityRequirement(name = "bearer-key")}) @PostMapping("{id}/comments") public CommentView addComment(@Parameter(description = "City Id") @PathVariable(name = "id") @@ -151,11 +138,11 @@ public CommentView addComment(@Parameter(description = "City Id") @RequestBody @Valid CommentUpSrtRequest request) { return cityMgmtService.addComment(getCurrentLoginUser(), cityId, request); } - + //update comment @Operation(summary = "Update my city comment.", - description = "Wanna change your comment to a city you have visited.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Wanna change your comment to a city you have visited.", + security = {@SecurityRequirement(name = "bearer-key")}) @PutMapping("{id}/comments/{cid}") public void updateComment(@Parameter(description = "City Id") @PathVariable(name = "id") @@ -164,23 +151,23 @@ public void updateComment(@Parameter(description = "City Id") @PathVariable("cid") @Min(1) @Max(Integer.MAX_VALUE) int commentId, @RequestBody @Valid CommentUpSrtRequest request) { - + cityMgmtService.updateComment(getCurrentLoginUser(), cityId, commentId, request); } - + //Delete comment @Operation(summary = "Delete my city comment.", - description = "Changed your mind, don't like your comment then delete it.", - security = {@SecurityRequirement(name = "bearer-key")}) + description = "Changed your mind, don't like your comment then delete it.", + security = {@SecurityRequirement(name = "bearer-key")}) @DeleteMapping("{id}/comments/{cid}") public void deleteComment(@Parameter(description = "City Id") @PathVariable(name = "id") @Min(1) @Max(Integer.MAX_VALUE) int cityId, @Parameter(description = "Comment Id") @PathVariable("cid") @Min(1) @Max(Integer.MAX_VALUE) int commentId) { - + cityMgmtService.deleteComment(getCurrentLoginUser(), cityId, commentId); } - + private User getCurrentLoginUser() { return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } diff --git a/src/main/java/org/siriusxi/htec/fa/api/controller/CountryController.java b/src/main/java/org/siriusxi/htec/fa/api/controller/CountryController.java index b98bc6a..683f83b 100644 --- a/src/main/java/org/siriusxi/htec/fa/api/controller/CountryController.java +++ b/src/main/java/org/siriusxi/htec/fa/api/controller/CountryController.java @@ -3,11 +3,10 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.security.RolesAllowed; import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.siriusxi.htec.fa.api.model.response.CountryView; -import org.siriusxi.htec.fa.domain.Role; import org.siriusxi.htec.fa.infra.mapper.CountryMapper; import org.siriusxi.htec.fa.repository.CountryRepository; import org.springframework.web.bind.annotation.GetMapping; @@ -27,34 +26,29 @@ // TODO: Add Swagger documentation @Log4j2 @Tag(name = "Country Management", - description = "A set of authorized APIs, for getting and managing system countries.") -@RolesAllowed(Role.ADMIN) + description = "A set of authorized APIs, for getting and managing system countries.") @RestController -@RequestMapping("v1/countries") +@RequestMapping("countries") +@RequiredArgsConstructor public class CountryController { - + private final CountryRepository countryRepository; private final CountryMapper mapper; - - public CountryController(CountryRepository countryRepository, CountryMapper mapper) { - this.countryRepository = countryRepository; - this.mapper = mapper; - } - + @Operation(security = {@SecurityRequirement(name = "bearer-key")}) @GetMapping public Set getAllCountries() { return mapper - .toViews(countryRepository - .findAllByNameIgnoreCaseIsLike("%%")); + .toViews(countryRepository + .findAllByNameIgnoreCaseIsLike("%%")); } - + @Operation(security = {@SecurityRequirement(name = "bearer-key")}) @GetMapping("search") public Set searchCountries(@RequestParam @NotBlank String name) { return mapper - .toViews(countryRepository - .findAllByNameIgnoreCaseIsLike("%" + name + "%")); + .toViews(countryRepository + .findAllByNameIgnoreCaseIsLike("%" + name + "%")); } - + } diff --git a/src/main/java/org/siriusxi/htec/fa/api/controller/FileUploadController.java b/src/main/java/org/siriusxi/htec/fa/api/controller/FileUploadController.java index 1bf8523..0e8669c 100644 --- a/src/main/java/org/siriusxi/htec/fa/api/controller/FileUploadController.java +++ b/src/main/java/org/siriusxi/htec/fa/api/controller/FileUploadController.java @@ -10,14 +10,13 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.security.RolesAllowed; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.siriusxi.htec.fa.api.model.upload.airport.AirportDto; import org.siriusxi.htec.fa.api.model.upload.airport.verifer.AirportBeanVerifier; import org.siriusxi.htec.fa.api.model.upload.route.RouteDto; import org.siriusxi.htec.fa.api.model.upload.route.verifer.RouteBeanVerifier; import org.siriusxi.htec.fa.domain.Country; -import org.siriusxi.htec.fa.domain.Role; import org.siriusxi.htec.fa.infra.mapper.AirportMapper; import org.siriusxi.htec.fa.infra.mapper.RouteMapper; import org.siriusxi.htec.fa.repository.AirportRepository; @@ -32,17 +31,10 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; +import java.io.*; import java.util.List; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.EXPECTATION_FAILED; -import static org.springframework.http.HttpStatus.FAILED_DEPENDENCY; -import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.*; /** * Files Upload controller used to handle all data feeding the system like airports and routes. @@ -54,55 +46,44 @@ // TODO: Improve performance of the file processing. @Log4j2 @Tag(name = "Files Upload Management", - description = """ - A set of authorized file management APIs, used to feed the system with data - files like airports and routes.""") -@RolesAllowed(Role.ADMIN) + description = """ + A set of authorized file management APIs, used to feed the system with data + files like airports and routes.""") @RestController -@RequestMapping(value = "v1/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) +@RequestMapping(value = "upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) +@RequiredArgsConstructor public class FileUploadController { - + private final RouteRepository routeRepository; private final AirportRepository airportRepository; private final CityRepository cityRepository; private final CountryRepository countryRepository; private final RouteMapper routeMapper; private final AirportMapper airportMapper; - - public FileUploadController(RouteRepository routeRepository, RouteMapper routeMapper, - AirportRepository airportRepository, AirportMapper airportMapper, - CityRepository cityRepository, CountryRepository countryRepository) { - this.routeRepository = routeRepository; - this.airportRepository = airportRepository; - this.routeMapper = routeMapper; - this.cityRepository = cityRepository; - this.countryRepository = countryRepository; - this.airportMapper = airportMapper; - } - + @Operation(summary = "Upload file that contains flights airports.", - security = {@SecurityRequirement(name = "bearer-key")}, - responses = { - @ApiResponse(responseCode = "200", - description = "OK; File is uploaded and parsed successfully.", - content = @Content(schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "400", - description = "Bad Request; File is not valid.", - content = @Content(schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "417", - description = "Expectation Failed; Error while parsing or processing the file.", - content = @Content(schema = @Schema(implementation = String.class)))}) + security = {@SecurityRequirement(name = "bearer-key")}, + responses = { + @ApiResponse(responseCode = "200", + description = "OK; File is uploaded and parsed successfully.", + content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", + description = "Bad Request; File is not valid.", + content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "417", + description = "Expectation Failed; Error while parsing or processing the file.", + content = @Content(schema = @Schema(implementation = String.class)))}) @PostMapping("airports") public ResponseEntity uploadAirports(@Parameter(name = "file", required = true, - description = "Select airport file to upload.") @RequestPart("file") MultipartFile file) { - + description = "Select airport file to upload.") @RequestPart("file") MultipartFile file) { + if (file.isEmpty()) return new ResponseEntity<>("Please upload a valid file.", BAD_REQUEST); else { try { // convert `CsvToBean` object to list of airports List airports = parseCsvContent(file.getInputStream(), - AirportDto.class, new AirportBeanVerifier()); + AirportDto.class, new AirportBeanVerifier()); /* Save All Airports to database after: @@ -112,96 +93,98 @@ public ResponseEntity uploadAirports(@Parameter(name = "file", required 4. Return all airports as list to be saved to DB. */ airportRepository.saveAll( - airports - .stream()// Save countries and cities to database - .map(airportDto -> { - - Country country = countryRepository - .findOrSaveBy(airportDto.getCountry()); - - airportDto.setCountryId(country.getId()); - - airportDto.setCityId(cityRepository.findOrSaveBy(country, - airportDto.getCity()).getId()); - - // then converting to - return airportMapper.toModel(airportDto); - }) - // Then collect them to list - .toList()); - + airports + .stream()// Save countries and cities to database + .map(airportDto -> { + + Country country = countryRepository + .findOrSaveBy(airportDto.getCountry()); + + airportDto.setCountryId(country.getId()); + + airportDto.setCityId(cityRepository + .findOrSaveBy(country, airportDto.getCity()) + .getId()); + + // then converting to + return airportMapper.toModel(airportDto); + }) + // Then collect them to list + .toList()); + } catch (Exception ex) { log.error("Error while reading airports file.", ex); return new ResponseEntity<>("Error while reading or processing airport file.", - EXPECTATION_FAILED); + EXPECTATION_FAILED); } } - + return new ResponseEntity<>("Airports file uploaded successfully.", OK); } - + @Operation(summary = "Upload file that contains flights routes.", - security = {@SecurityRequirement(name = "bearer-key")}, - responses = { - @ApiResponse(responseCode = "200", - description = "OK; File is uploaded and parsed successfully.", - content = @Content(schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "400", - description = "Bad Request; File is not valid.", - content = @Content(schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "417", - description = "Expectation Failed; Error while parsing or processing the file.", - content = @Content(schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "424", - description = "Failed Dependency; System doesn't have any airports.", - content = @Content(schema = @Schema(implementation = String.class)))}) + security = {@SecurityRequirement(name = "bearer-key")}, + responses = { + @ApiResponse(responseCode = "200", + description = "OK; File is uploaded and parsed successfully.", + content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", + description = "Bad Request; File is not valid.", + content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "417", + description = "Expectation Failed; Error while parsing or processing the file.", + content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "424", + description = "Failed Dependency; System doesn't have any airports.", + content = @Content(schema = @Schema(implementation = String.class)))}) @PostMapping("routes") public ResponseEntity uploadRoutes(@Parameter(name = "file", required = true, - description = "Select flights routes file to upload.") + description = "Select flights routes file to upload.") @RequestPart("file") MultipartFile file) { - + if (file.isEmpty()) return new ResponseEntity<>("Please upload a valid file.", BAD_REQUEST); else if (airportRepository.count() == 0) { return new ResponseEntity<>(""" - "Can't upload flight routes, system doesn't has Airports defined. - Please upload airport file, to feed the system with airports." - """, FAILED_DEPENDENCY); + "Can't upload flight routes, system doesn't has Airports defined. + Please upload airport file, to feed the system with airports." + """, FAILED_DEPENDENCY); } else { - + // parse CSV file to create a list of `RouteDto` objects try { - + // convert `CsvToBean` object to list of routeDto List routes = parseCsvContent(file.getInputStream(), - RouteDto.class, new RouteBeanVerifier()); + RouteDto.class, new RouteBeanVerifier()); /* 1. Convert to Route model. 2. Save routes in DB. */ routeRepository - .saveAll(routes - .stream() - // filter routes doesn't exists - .filter(dto -> - airportRepository.findById(dto.getSrcAirportId()).isPresent() && - airportRepository.findById(dto.getDestAirportId()).isPresent()) - // converting to - .map(routeMapper::toModel) - // Then collect them to list - .toList()); - + .saveAll(routes + .stream() + // filter routes doesn't exists + .filter(dto -> + airportRepository + .findById(dto.getSrcAirportId()).isPresent() && + airportRepository.findById(dto.getDestAirportId()).isPresent()) + // converting to + .map(routeMapper::toModel) + // Then collect them to list + .toList()); + } catch (Exception ex) { log.error("Error while reading routes file.", ex); return new ResponseEntity<>("Error while reading or processing routes file.", - EXPECTATION_FAILED); + EXPECTATION_FAILED); } } - + return new ResponseEntity<>("Routes file uploaded successfully.", OK); } - + /** * This method is used to convert CSV content from any input stream to a bean list of type T. * @@ -216,17 +199,17 @@ else if (airportRepository.count() == 0) { */ private List parseCsvContent(InputStream content, Class clazz, BeanVerifier beanVerifier) throws IOException { - + List dtoList; // parse CSV file to create a list of `TypeDto` objects try (Reader reader = new BufferedReader(new InputStreamReader(content))) { // create csv bean reader CsvToBean csvToBeans = new CsvToBeanBuilder(reader) - .withType(clazz) - .withVerifier(beanVerifier) - .withIgnoreLeadingWhiteSpace(true) - .build(); - + .withType(clazz) + .withVerifier(beanVerifier) + .withIgnoreLeadingWhiteSpace(true) + .build(); + // convert `CsvToBean` object to a dto list dtoList = csvToBeans.parse(); } diff --git a/src/main/java/org/siriusxi/htec/fa/domain/Role.java b/src/main/java/org/siriusxi/htec/fa/domain/Role.java index 1fac7a0..1019a09 100644 --- a/src/main/java/org/siriusxi/htec/fa/domain/Role.java +++ b/src/main/java/org/siriusxi/htec/fa/domain/Role.java @@ -22,7 +22,7 @@ @Getter @Setter @RequiredArgsConstructor -@NoArgsConstructor +@NoArgsConstructor(force = true) @ToString(exclude = {"rolePK"}) public class Role implements GrantedAuthority { diff --git a/src/main/java/org/siriusxi/htec/fa/domain/User.java b/src/main/java/org/siriusxi/htec/fa/domain/User.java index ebd159e..1e0bc52 100644 --- a/src/main/java/org/siriusxi/htec/fa/domain/User.java +++ b/src/main/java/org/siriusxi/htec/fa/domain/User.java @@ -46,46 +46,56 @@ }) @Getter @Setter -@NoArgsConstructor +@NoArgsConstructor(force = true) @RequiredArgsConstructor public class User implements UserDetails, Serializable { @Serial private static final long serialVersionUID = 5666668516577592568L; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(nullable = false) private Integer id; + @Basic(optional = false) @Column(name = "USER_UUID", nullable = false, updatable = false) @ToString.Exclude private String userUuid; + @NonNull @Basic(optional = false) @Column(name = "FIRST_NAME", nullable = false, length = 100) private String firstName; + @NonNull @Basic(optional = false) @Column(name = "LAST_NAME", nullable = false, length = 100) private String lastName; + @NonNull @Basic(optional = false) @Column(nullable = false) @ToString.Exclude private String username; + @NonNull @Basic(optional = false) @Column(nullable = false) @ToString.Exclude private String password; + @OneToMany(cascade = ALL, mappedBy = "user", fetch = LAZY) @ToString.Exclude private List comments; - @JoinColumn(name = "USER_ID", referencedColumnName = "ID", - nullable = false, insertable = false, updatable = false) + + @OneToMany(cascade = ALL, fetch = EAGER) + @JoinColumn(name = "USER_ID", referencedColumnName = "ID", + nullable = false, insertable = false, updatable = false) private Set authorities = new HashSet<>(); + @ToString.Exclude private boolean enabled = true; diff --git a/src/main/java/org/siriusxi/htec/fa/infra/algorithm/distance/Point.java b/src/main/java/org/siriusxi/htec/fa/infra/algorithm/distance/Point.java index c2ed30e..df3c987 100644 --- a/src/main/java/org/siriusxi/htec/fa/infra/algorithm/distance/Point.java +++ b/src/main/java/org/siriusxi/htec/fa/infra/algorithm/distance/Point.java @@ -6,6 +6,4 @@ * @author Mohamed Taman * @since v0.4 */ -public record Point(double latitude, double longitude) { - -} +public record Point(double latitude, double longitude) { } diff --git a/src/main/java/org/siriusxi/htec/fa/infra/mapper/CommentMapper.java b/src/main/java/org/siriusxi/htec/fa/infra/mapper/CommentMapper.java index 4ea6b11..0cf29fa 100644 --- a/src/main/java/org/siriusxi/htec/fa/infra/mapper/CommentMapper.java +++ b/src/main/java/org/siriusxi/htec/fa/infra/mapper/CommentMapper.java @@ -19,14 +19,14 @@ public interface CommentMapper { @Mapping(target = "createdAt", ignore = true) @Mapping(target = "user", source = "user") @Mapping(target = "city", source = "city") - Comment toNewModel(CommentUpSrtRequest request, User user, City city); + Comment toModel(CommentUpSrtRequest request, User user, City city); @Mapping(target = "createdAt", ignore = true) @Mapping(target = "updatedAt", expression = "java( LocalDateTime.now() )") @Mapping(target = "user", source = "user") @Mapping(target = "city", source = "city") @Mapping(target = "id", source = "commentId") - Comment toUpdateModel(CommentUpSrtRequest request, int commentId, User user, City city); + Comment toUpdatedModel(CommentUpSrtRequest request, int commentId, User user, City city); @Mapping(target = "id", source = "id") @Mapping(target = "by", source = "user.fullName") diff --git a/src/main/java/org/siriusxi/htec/fa/infra/security/JwtTokenFilter.java b/src/main/java/org/siriusxi/htec/fa/infra/security/JwtTokenFilter.java index 59d432c..a906a9e 100644 --- a/src/main/java/org/siriusxi/htec/fa/infra/security/JwtTokenFilter.java +++ b/src/main/java/org/siriusxi/htec/fa/infra/security/JwtTokenFilter.java @@ -5,7 +5,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.log4j.Log4j2; -import org.siriusxi.htec.fa.infra.security.jwt.JwtTokenHelper; import org.siriusxi.htec.fa.repository.UserRepository; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -18,6 +17,7 @@ import java.util.List; import java.util.Optional; +import static org.siriusxi.htec.fa.infra.security.jwt.JwtTokenHelper.*; import static org.springframework.util.StringUtils.hasText; @Component @@ -48,9 +48,9 @@ protected void doFilterInternal(HttpServletRequest request, // Get user identity and set it on the spring security context var userDetails = userRepository - .findByUsernameIgnoreCase(JwtTokenHelper.getUsername(token)) + .findByUsernameIgnoreCase(getUsername(token)) .orElse(null); - + var authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails == null ? List.of() : userDetails.getAuthorities() @@ -76,7 +76,7 @@ private Optional getJwtTokenIfValid(String header) { if (hasText(header) && header.startsWith("Bearer ")) { // Get jwt token and validate token = header.split(" ")[1].trim(); - if (JwtTokenHelper.validate(token)) + if (validate(token)) return Optional.of(token); } diff --git a/src/main/java/org/siriusxi/htec/fa/infra/security/SecurityConfig.java b/src/main/java/org/siriusxi/htec/fa/infra/security/SecurityConfig.java index deb0532..608e424 100644 --- a/src/main/java/org/siriusxi/htec/fa/infra/security/SecurityConfig.java +++ b/src/main/java/org/siriusxi/htec/fa/infra/security/SecurityConfig.java @@ -1,130 +1,154 @@ package org.siriusxi.htec.fa.infra.security; import lombok.extern.log4j.Log4j2; -import org.siriusxi.htec.fa.repository.UserRepository; +import org.siriusxi.htec.fa.domain.Role; +import org.siriusxi.htec.fa.service.UserService; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; -import static java.lang.String.format; +import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; import static org.springframework.security.core.context.SecurityContextHolder.MODE_INHERITABLETHREADLOCAL; import static org.springframework.security.core.context.SecurityContextHolder.setStrategyName; +import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; @Log4j2 +@Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity( - securedEnabled = true, - jsr250Enabled = true, - prePostEnabled = true +@EnableMethodSecurity( + jsr250Enabled = true ) -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - private final UserRepository userRepository; +public class SecurityConfig { + private final JwtTokenFilter jwtTokenFilter; private final String allowedOrigins; - - public SecurityConfig(UserRepository userRepository, - JwtTokenFilter jwtTokenFilter, - @Value("${app.allowedOrigins:*}") String allowedOrigins) { - super(); - - this.userRepository = userRepository; + private final UserService userService; + private final PasswordEncoder passwordEncoder; + + public SecurityConfig(JwtTokenFilter jwtTokenFilter, + @Value("${app.allowedOrigins:*}") String allowedOrigins, + UserService userService, PasswordEncoder passwordEncoder) { this.jwtTokenFilter = jwtTokenFilter; this.allowedOrigins = allowedOrigins; - + this.userService = userService; + this.passwordEncoder = passwordEncoder; + // Inherit security context in async function calls setStrategyName(MODE_INHERITABLETHREADLOCAL); } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(username -> - userRepository - .findByUsernameIgnoreCase(username) - .orElseThrow( - () -> new UsernameNotFoundException( - format("User: %s, not found", - username)))) - .passwordEncoder(passwordEncoder()); + + // Configure DaoAuthenticationProvider for username and password + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + + authProvider.setUserDetailsService(userDetailsService()); + authProvider.setPasswordEncoder(this.passwordEncoder); + + return authProvider; } - - // Set password encoding schema + + // Expose authentication manager bean + @Bean + public AuthenticationManager authenticationManager( + AuthenticationConfiguration authConfig) throws Exception { + return authConfig.getAuthenticationManager(); + } + @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + public UserDetailsService userDetailsService() { + return this.userService; } - + // Security configurations - @Override - protected void configure(HttpSecurity http) throws Exception { - - // List of Swagger URLs - var swaggerAuthList = new String[]{ - "/api-docs/**", "/webjars/**", - "/swagger-ui/**", "/doc/**"}; - + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + // List of Swagger URLs, root page, Our public endpoints, images + final var AUTH_WHITELIST = new String[]{ + "/api-docs/**", "/webjars/**", "/public/**", + "/swagger-ui/**", "/doc/**", "/", "/index.html", + "/assets/**"}; + http - // Enable CORS - .cors().and() - - //Disable CSRF - .csrf().disable() - - // Set session management to stateless - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - - // Set unauthorized requests exception handler - .exceptionHandling() - .authenticationEntryPoint( - (request, response, ex) -> { - log.error("Unauthorized request - {}", ex.getMessage()); - response.sendError(SC_UNAUTHORIZED, ex.getMessage()); + // Enable CORS + .cors() + .and() + + //Disable CSRF + .csrf() + .disable() + + // Set session management to stateless + .sessionManagement() + .sessionCreationPolicy(STATELESS) + .and() + + // Set unauthorized requests exception handler + .exceptionHandling() + .authenticationEntryPoint( + (request, response, ex) -> { + log.error("Unauthorized request - {}", ex.getMessage()); + response.sendError(SC_UNAUTHORIZED, ex.getMessage()); + }) + .and() + // Set permission to allow open db-console + .authorizeHttpRequests(auth -> + { + try { + auth.requestMatchers(antMatcher("/db-console/**")) + .permitAll() + .and() + // This will allow frames with same origin which is much more safe + .headers(headers -> + headers.frameOptions() + .sameOrigin() + .disable()); + } catch (Exception e) { + log.error("Exception in headers - {}", e.getMessage()); + } }) - .and() - - - // Set H2 database console permission - .authorizeRequests() - .antMatchers("/db-console/**").permitAll() - .and() - // This will allow frames with same origin which is much more safe - .headers().frameOptions().disable() - .and() - - // Set permissions on endpoints - .authorizeRequests() - //Enable root - .antMatchers("/", "/index.html").permitAll() - //Enables images - .antMatchers("/assets/**").permitAll() - // Swagger endpoints must be publicly accessible - .antMatchers(swaggerAuthList).permitAll() - // Our public endpoints - .antMatchers("/public/**").permitAll() - //Our private endpoints - .anyRequest().authenticated() - .and() - - // Add JWT token filter - .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); + + // Enable all whitelisted pages + .authorizeHttpRequests(auth -> + auth.requestMatchers(AUTH_WHITELIST) + .permitAll()) + + // Only Admin Allowed to do the following + .authorizeHttpRequests(auth -> + // Upload files and manage countries + auth.requestMatchers("/upload/**", "/countries/**") + .hasAuthority(Role.ADMIN). + // Create cities + requestMatchers(HttpMethod.POST, "/cities") + .hasAuthority(Role.ADMIN)) + + //Our private endpoints + .authorizeHttpRequests(auth -> + auth.anyRequest() + .authenticated()) + + // Add JWT token filter + .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); } - + // Used by spring security if CORS is enabled. @Bean public CorsFilter corsFilter() { @@ -155,15 +179,8 @@ public CorsFilter corsFilter() { read them. So we use here config.addExposedHeader() method. */ config.addExposedHeader("Authorization"); - + source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } - - // Expose authentication manager bean - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } } diff --git a/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordDigester.java b/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordDigester.java deleted file mode 100644 index 4d80b37..0000000 --- a/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordDigester.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.siriusxi.htec.fa.infra.security.password; - -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; - -import static java.lang.System.arraycopy; -import static java.util.Arrays.copyOfRange; -import static java.util.Base64.getDecoder; -import static java.util.Base64.getEncoder; -import static java.util.Objects.isNull; - -/** - * Component allowing to salt, hash and base64-encode passwords, as well as match passwords against - * encoded passwords. - * - * @author Mohamed Taman - */ -@Component -public class PasswordDigester { - - /** - * Generates a salt, hashes the password with this salt, concatenates the salt and the hash, and - * encodes the result using Base64. - */ - public String hash(String password) { - var salt = Salt.generateSalt(); - var digest = Salt.hash(password, salt); - var concatenation = concat(salt, digest); - return getEncoder().encodeToString(concatenation); - } - - /** - * Concat two byte arrays into one - * - * @param first - the first array - * @param second - the second array - * @return a concatenated array - */ - private byte[] concat(byte[] first, byte[] second) { - var concatenation = new byte[first.length + second.length]; - arraycopy(first, 0, concatenation, 0, first.length); - arraycopy(second, 0, concatenation, first.length, second.length); - return concatenation; - } - - /** - * Verifies a password against a hashed password generated by [.hash] - */ - public boolean match(String password, String hashedPassword) { - if (isNull(hashedPassword) || hashedPassword.isBlank()) - return false; - - var concatenation = getDecoder().decode(hashedPassword); - var salt = copyOfRange(concatenation, 0, Salt.SALT_LENGTH); - var digest = copyOfRange(concatenation, Salt.SALT_LENGTH, concatenation.length); - var newDigest = Salt.hash(password, salt); - return Arrays.equals(newDigest, digest); - } - - private static final class Salt { - private static final int SALT_LENGTH = 8; - private static final int ITERATION_COUNT = 4096; - private static final int KEY_LENGTH = 256; - - private static byte[] generateSalt() { - try { - var salt = new byte[SALT_LENGTH]; - SecureRandom.getInstance("SHA1PRNG").nextBytes(salt); - return salt; - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } - } - - private static byte[] hash(String password, byte[] salt) { - try { - var factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); - var keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); - var secretKey = factory.generateSecret(keySpec); - return secretKey.getEncoded(); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new IllegalStateException(e); - } - } - } -} diff --git a/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordGenerator.java b/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordGenerator.java deleted file mode 100644 index cf26c3a..0000000 --- a/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordGenerator.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.siriusxi.htec.fa.infra.security.password; - -import lombok.extern.log4j.Log4j2; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.stereotype.Component; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -/** - * A random password generator. - * - * @author Mohamed Taman - */ -@Log4j2 -@Component -public final class PasswordGenerator { - - - /** - * The size of generated passwords - */ - private static final int SIZE = 8; - /** - * The characters composing generated passwords. - * The generators randomly picks from these characters. - */ - private static final char[] CHARACTERS = - ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_$%?/+=.<>#*") - .toCharArray(); - private static SecureRandom random; - - static { - try { - random = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - log.error("", e); - } - } - - private PasswordGenerator() { - } - - public static String sha1Random() { - var builder = new StringBuilder(SIZE); - for (int i = 0; i < SIZE; i++) - builder.append(CHARACTERS[random.nextInt(CHARACTERS.length)]); - - return builder.toString(); - } - - public static String bcrypt(String password) { - return new BCryptPasswordEncoder() - .encode(password); - } -} diff --git a/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordManager.java b/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordManager.java new file mode 100644 index 0000000..cf5cd12 --- /dev/null +++ b/src/main/java/org/siriusxi/htec/fa/infra/security/password/PasswordManager.java @@ -0,0 +1,17 @@ +package org.siriusxi.htec.fa.infra.security.password; + +import lombok.experimental.UtilityClass; +import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Component +public class PasswordManager { + + // Set password encoding schema + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/org/siriusxi/htec/fa/service/CityMgmtService.java b/src/main/java/org/siriusxi/htec/fa/service/CityMgmtService.java index 5eacfe8..71738a6 100644 --- a/src/main/java/org/siriusxi/htec/fa/service/CityMgmtService.java +++ b/src/main/java/org/siriusxi/htec/fa/service/CityMgmtService.java @@ -1,5 +1,6 @@ package org.siriusxi.htec.fa.service; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.siriusxi.htec.fa.api.model.request.CommentUpSrtRequest; import org.siriusxi.htec.fa.api.model.request.CreateCityRequest; @@ -30,6 +31,7 @@ import static java.util.Objects.isNull; +@RequiredArgsConstructor @Log4j2 @Service public class CityMgmtService { @@ -42,19 +44,7 @@ public class CityMgmtService { private final CityMapper cityMapper; private final AirportRepository airportRepository; private final AirportMapper airportMapper; - - public CityMgmtService(CountryRepository countryRepository, - CityRepository cityRepository, CityMapper cityMapper, - CommentRepository commentRepository, CommentMapper commentMapper, - AirportRepository airportRepository, AirportMapper airportMapper) { - this.cityRepository = cityRepository; - this.countryRepository = countryRepository; - this.commentRepository = commentRepository; - this.cityMapper = cityMapper; - this.commentMapper = commentMapper; - this.airportRepository = airportRepository; - this.airportMapper = airportMapper; - } + @Transactional public CityView addCity(CreateCityRequest cityRequest) { @@ -126,7 +116,7 @@ public CommentView addComment(User user, int cityId, return commentMapper .toView(commentRepository .save(commentMapper - .toNewModel(request, user, city))); + .toModel(request, user, city))); } @Transactional @@ -150,7 +140,7 @@ public void updateComment(User user, int cityId, int commentId, .ifPresent(comment -> commentMapper .toView(commentRepository .save(commentMapper - .toUpdateModel(request, comment.getId(), user, city)))); + .toUpdatedModel(request, comment.getId(), user, city)))); } public void deleteComment(User user, int cityId, int commentId) { diff --git a/src/main/java/org/siriusxi/htec/fa/service/TravelService.java b/src/main/java/org/siriusxi/htec/fa/service/TravelService.java index 8ae102f..e2d9ebe 100644 --- a/src/main/java/org/siriusxi/htec/fa/service/TravelService.java +++ b/src/main/java/org/siriusxi/htec/fa/service/TravelService.java @@ -5,6 +5,7 @@ import es.usc.citius.hipster.graph.GraphBuilder; import es.usc.citius.hipster.graph.GraphSearchProblem; import es.usc.citius.hipster.model.impl.WeightedNode; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.siriusxi.htec.fa.api.model.response.AirportView; import org.siriusxi.htec.fa.api.model.response.TripView; @@ -58,6 +59,7 @@ * @see DistanceAlgorithm * @since v0.4 */ +@RequiredArgsConstructor @Log4j2 @Service @CacheConfig(cacheNames = "travels") @@ -66,18 +68,9 @@ public class TravelService { private final RouteRepository routeRepository; private final AirportRepository airportRepository; private final AirportMapper airportMapper; - private final DistanceAlgorithm orthodromicAlgorithm; + private final DistanceAlgorithm orthodromicAlgorithm = getAlgorithm(DistanceAlgorithm.Type.ORTHODROMIC); private final DecimalFormat formatter = new DecimalFormat("#.00"); - - public TravelService(RouteRepository routeRepository, - AirportRepository airportRepository, - AirportMapper airportMapper) { - this.routeRepository = routeRepository; - this.airportRepository = airportRepository; - this.airportMapper = airportMapper; - this.orthodromicAlgorithm = getAlgorithm(DistanceAlgorithm.Type.ORTHODROMIC); - } - + private static FinalTrip getTrip(Algorithm> .SearchResult result) { diff --git a/src/main/java/org/siriusxi/htec/fa/service/UserService.java b/src/main/java/org/siriusxi/htec/fa/service/UserService.java index 2b370a1..3852243 100644 --- a/src/main/java/org/siriusxi/htec/fa/service/UserService.java +++ b/src/main/java/org/siriusxi/htec/fa/service/UserService.java @@ -1,6 +1,7 @@ package org.siriusxi.htec.fa.service; import jakarta.validation.ValidationException; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.siriusxi.htec.fa.api.model.request.CreateUserRequest; import org.siriusxi.htec.fa.api.model.response.UserView; @@ -16,19 +17,14 @@ import static java.lang.String.format; import static org.siriusxi.htec.fa.domain.Role.CLIENT; +@RequiredArgsConstructor @Log4j2 @Service public class UserService implements UserDetailsService { - + private final UserRepository repository; private final UserMapper userMapper; - - public UserService(UserRepository repository, - UserMapper userMapper) { - this.repository = repository; - this.userMapper = userMapper; - } - + /** * This method is responsible to create a new user. * @@ -37,28 +33,32 @@ public UserService(UserRepository repository, */ @Transactional public UserView create(CreateUserRequest request) { - + if (repository.findByUsernameIgnoreCase(request.username()).isPresent()) { throw new ValidationException("Username exists!"); } - + // Add user User user = repository.save(userMapper.toUser(request)); // Add user authorities user.setAuthorities(CLIENT); // Update user to add authorities repository.save(user); - + // Return user view return userMapper.toView(user); } - + @Override public UserDetails loadUserByUsername(String username) { return repository - .findByUsernameIgnoreCase(username) - .orElseThrow( - () -> new UsernameNotFoundException( - format("User with username - %s, not found", username))); + .findByUsernameIgnoreCase(username) + .orElseThrow( + () -> new UsernameNotFoundException( + format("User with username - %s, not found", username))); + } + + public UserMapper mapper() { + return this.userMapper; } } diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 35cd26c..81c002d 100755 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -19,7 +19,7 @@ Flight image

Welcome to Flight Advisor Service

-

+

Click here  to explore the system APIs documentation.

diff --git a/src/test/java/org/siriusxi/htec/fa/CsvToBeanTests.java b/src/test/java/org/siriusxi/htec/fa/CsvToBeanTests.java index da26a1e..a1c3e38 100644 --- a/src/test/java/org/siriusxi/htec/fa/CsvToBeanTests.java +++ b/src/test/java/org/siriusxi/htec/fa/CsvToBeanTests.java @@ -1,6 +1,5 @@ package org.siriusxi.htec.fa; -import com.opencsv.bean.CsvToBean; import com.opencsv.bean.CsvToBeanBuilder; import lombok.extern.log4j.Log4j2; import org.assertj.core.api.Assertions; @@ -20,42 +19,42 @@ class CsvToBeanTests { //@Test void parseRoutesCSVFileToBeans() { - + try (Reader fileReader = new FileReader("./data/routes.txt")) { - - CsvToBean csvToRouteBeans = new CsvToBeanBuilder(fileReader) + + com.opencsv.bean.CsvToBean csvToRouteBeans = new CsvToBeanBuilder(fileReader) .withType(RouteDto.class) .withVerifier(new RouteBeanVerifier()) .withIgnoreLeadingWhiteSpace(true) .build(); - + // convert `CsvToBean` object to list of airports List routes = csvToRouteBeans.parse(); - + System.out.println(routes.size()); - + Assertions.assertThat(routes).hasSize(66754); } catch (IOException e) { throw new RuntimeException(e); } } - + // @Test void parseAirportsCSVFileToBeans() throws FileNotFoundException { - + try (Reader fileReader = new FileReader("./data/airports.txt")) { - - CsvToBean csvToAirportBeans = new CsvToBeanBuilder(fileReader) + + com.opencsv.bean.CsvToBean csvToAirportBeans = new CsvToBeanBuilder(fileReader) .withType(AirportDto.class) .withVerifier(new AirportBeanVerifier()) .withIgnoreLeadingWhiteSpace(true) .build(); - + // convert `CsvToBean` object to list of airports List airports = csvToAirportBeans.parse(); - + System.out.println(airports.size()); - + Assertions.assertThat(airports).hasSize(7140); } catch (IOException e) { throw new RuntimeException(e);