diff --git a/.env.sample b/.env.sample new file mode 100644 index 00000000..659928a1 --- /dev/null +++ b/.env.sample @@ -0,0 +1,17 @@ +DB_NAME=skilltree +MYSQL_DB_USERNAME=testuser +MYSQL_DB_PASSWORD=testpassword +MYSQL_ROOT_PASSWORD=password +DB_DDL_POLICY=update +RDS_BACKEND_BASE_URL=http://localhost:3000 +SKILL_TREE_FRONTEND_BASE_URL=https://localhost +SKILL_TREE_BACKEND_BASE_URL=http://localhost:8080 +RDS_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg5HrGgKFmH485DXLG4fG + 1mKNXceWthXgAozsUGHxLIM3Fa5wU+tLi7tLDZ6LRKo4ZZV2gJJdDqFSNsvn1Uvr + XygvkCCRD/DYjxpG/qNiq9kgv7G8gjVI394oJWgx9uWTmDp4DmEVi7rIviyqVimE + V1nzdlKfGeLckagsetPS28rqULVGXaIAOL52vzrk3O74MnvCSVkjH/+cxEHE7XGg + ryy1fcH9CXvgrXxOLG2xrIdHrV39WBaMC4KOt5piCXgz2tAfhy47x6tIaEj5URx/ + jxQoSPZJ5TAf27pjPvUkGVs726RlO0tBLteNzTAWCVZ0QeMoMzBwtdloouC+Rzj2 + 8QIDAQAB + -----END PUBLIC KEY-----" \ No newline at end of file diff --git a/.github/workflows/deploy-to-ec2.yml b/.github/workflows/deploy-to-ec2.yml index c31820d5..c7af87a2 100644 --- a/.github/workflows/deploy-to-ec2.yml +++ b/.github/workflows/deploy-to-ec2.yml @@ -5,7 +5,6 @@ on: branches: - main - develop - - feat/aws-deploy # Remove before merting this PR to dev jobs: build-and-push: @@ -53,5 +52,14 @@ jobs: --name ${{ github.event.repository.name }}-${{ vars.ENV }} \ --network=${{ vars.DOCKER_NETWORK }} \ -e RDS_PUBLIC_KEY="${{ secrets.RDS_PUBLIC_KEY }}" \ + -e MYSQL_HOST="${{ secrets.MYSQL_HOST }}" \ + -e MYSQL_PORT="${{ secrets.MYSQL_PORT }}" \ + -e DB_NAME="${{ vars.DB_NAME }}" \ + -e MYSQL_DB_USERNAME="${{ secrets.MYSQL_DB_USERNAME }}" \ + -e MYSQL_DB_PASSWORD="${{ secrets.MYSQL_DB_PASSWORD }}" \ + -e RDS_BACKEND_BASE_URL="${{ vars.RDS_BACKEND_BASE_URL }}" \ + -e SKILL_TREE_BACKEND_BASE_URL="${{ vars.SKILL_TREE_BACKEND_BASE_URL }}" \ + -e SKILL_TREE_FRONTEND_BASE_URL="${{ vars.SKILL_TREE_FRONTEND_BASE_URL }}" \ + -e SPRING_PROFILES_ACTIVE="${{ vars.SPRING_PROFILES_ACTIVE }}" \ -e API_V1_PREFIX=/api/v1 \ ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} diff --git a/Makefile b/Makefile index a8e5ceb6..62e3ca5e 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +copy-env: + cp .env.sample .env + # Setup MySql Database docker-run: @if command -v docker > /dev/null; then \ @@ -21,5 +24,8 @@ setup: @echo "--- Setting up docker ---" @make docker-run + @echo "--- Copying env ---" + @make copy-env + @echo "\n" @echo "Setup complete!" \ No newline at end of file diff --git a/README.md b/README.md index 3ac54c6d..53a67505 100644 --- a/README.md +++ b/README.md @@ -1,197 +1,78 @@ # Skill Tree Backend -## Steps to Run the Service Locally - -### Required Tools - -- [Maven](https://maven.apache.org/download.cgi) version 3.9.6 or higher - -Installing Maven on macOS using Homebrew: - -1. Open your terminal. -2. Type the following command and press Enter to install Homebrew (if not installed): - -```bash -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -``` - -3. Once Homebrew is installed, run the following command to install Maven: +## Required Tools +- [Homebrew](https://brew.sh/) (If you're using macOS) +- [Maven](https://maven.apache.org/download.cgi) (version 3.9.6 or higher) +- [Java](https://www.oracle.com/in/java/technologies/downloads/#java17) (version 17) +- [IntelliJ Community or Ultimate Edition](https://www.jetbrains.com/idea/download/other.html) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) + +## Development Setup +### Install Maven +#### macOS +1. To install maven type the following command in your terminal: + ```bash brew install maven - -4. Wait for the installation process to complete. - -5. Verify the Maven installation by typing: + ``` +2. Verify your installation using the following command: + ```bash mvn -version + ``` + If maven is installed correctly you should see the maven version in the terminal: + ```bash + Apache Maven 3.9.7 + ``` -This should display information about the installed Maven version. - -Installing Maven on Windows: - +#### Windows 1. To install Maven on Windows, we head over to the Apache Maven site to download the latest version and select the Maven zip file, for example, apache-maven-3.9.6-bin.zip. - 2. Adding Maven to the Environment Path We add both M2_HOME and MAVEN_HOME variables to the Windows environment using system properties and point them to our Maven folder. - 3. Verify the Maven installation by typing: mvn -version +4. Then, we update the PATH variable by appending the Maven bin folder — %M2_HOME%\bin — so that we can run the Maven command everywhere. + +### Setup database and configure env +- To setup the database & create a `.env` file run the following command in your terminal: + ```bash + make setup + ``` + - To edit your environment variables edit the `.env` file. + +#### NOTE: +- If you do not have `make` installed, run the `docker-run` and `copy-env` commands from the `Makefile` manually. + +### Install EnvFile Plugin in IntelliJ +- To read the values from your `.env` you will need install the `EnvFile` plugin. +- To Install the plugin to go `settings` > `Plugins` > `Marketplace` > EnvFile. + ![img.png](public/highlight-env-file-plugin.png) + +### Create a configuration in IntelliJ +- Press the Edit configurations button on the top right of your screen + ![img.png](public/highlight-edit-config.png) +- Create a new application + ![img.png](public/create-new-application.png) +- Select `Java version 17`, `Skill tree` and `SkillTreeApplication` + ![img.png](public/highlight-java-version-main-class.png) +- Check `EnvFile` and `Substitute Environment Variables` + ![img.png](public/highlight-check-env.png) +- Add path to the `.env` file you created above + ![img.png](public/highlight-add-new-env-cta.png) + - You should a new entry like so: + ![img.png](public/highlight-env-list.png) + - Click on `Apply` and then `Ok` + +### Running your application +- Press the `Run` button on the top right of your screen to run the application. + ![img.png](public/highlight-run-application.png) + +## Steps to authenticate a user during development +- Run RDS backend on your local machine +- Copy the `public key` from `local.js` (if not in `local.js` copy it from `development.js`) and add it in your `.env` file. +- Run skill tree frontend, click on `signin with github` button + - Once authenticated you will be redirected to `localhost:4000` (skill tree frontend homepage) and a `rds-session-v2-development` cookie will be set in the browser which will be used for authentication when making API calls. + - NOTE: + - make sure that `incompleteUserDetails` is false for the user in firestore, else the redirect will not work as intended. -Then, we update the PATH variable by appending the Maven bin folder — %M2_HOME%\bin — so that we can run the Maven command everywhere. - -Then, we unzip it to the folder where we want Maven to live. - -- Java (version 17 or higher) [Link](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) -- Any text editor of your choice (Preferred - IntelliJ Community or Ultimate Edition). -- Docker Desktop: [Installation steps](https://docs.docker.com/engine/install/) -- MySQL Docker Image: [Installation steps](https://hub.docker.com/_/mysql) - -### Steps to Login to MySQL - -1. Go to Docker Desktop - -2. You'll see skill-tree-backed (If the process is running) - -3. skill-tree-backend > skill-tree-backend-db-1 > open in terminal - -To login to MySQL - -``` -mysql -u root -p -``` - -(in terminal) -password : password - -Reference Screenshots: - -If the project is started with `docker compose up`, this can be seen once you open Docker Desktop: - -Screenshot 2023-12-26 at 9 33 17 PM - -Terminal needs to be opened here: - -![image](https://github.com/ashifkhn/skill-tree-backend/assets/54736284/d66166ae-b931-40ab-914f-f42615323a32) - -## Steps for Creating the Database (To be executed in order) - -Semicolon is important in the commands - -Username: testuser -Password: testpassword - -``` -CREATE DATABASE skilltree; -SHOW DATABASES; -CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpassword'; -GRANT ALL ON skilltree.* TO testuser; -``` - -## Steps for setting up skill-tree in Intellij - -1. After creating the database project needs to be compiled. -2. Open skill-tree-backend in intellij. -3. Java_Home path needs to be added here. -4. You can either add the existing path and JDK 17 can be downloaded inside intellij. -5. If adding from the existing path, go to Settings > Project Structure > Choose the earlier installed Java 17 SDK. -6. Install Lombok plug-in, if you see annotation errors - -## Creating Run/Debug Configuration - -1. Create a .env file inside the skill-tree folder with content mentioned in the Additional Configuration Steps below -2. Click on "Edit Configurations" > Add new "Application" Configuration -3. Choose "Java 17" and "skill-tree" folder in the dropdown -4. Choose com.RDS.skilltree.SkillTreeApplication as the Main class -5. Add the .env file you created in the first step for the environment variables and click "OK" - -(Below steps are not required as of now.) - -### Steps to Connect the Service to MySQL Running in Docker - -Refer to this [link](https://find10archived.medium.com/how-to-connect-a-mysql-docker-container-with-a-local-spring-boot-application-9366707dce0d) for detailed steps. - -1. Create a database user with full access for table creation, deletion, and modification (for development only in local MySQL). -2. After creating the database user, enter the credentials in the Skill Tree Spring Boot application. -3. Attempt to connect to the database using the URL "jdbc:mysql://${MYSQL_HOST:localhost}:3306/${DB_NAME}". -4. Create a simple API to test the database connection and data retrieval. For example, create a `/test` route in `skill-tree/src/main/java/com/RDS/skilltree/User/UserController.java`. - - ```java - package com.RDS.skilltree.User; - - import org.springframework.web.bind.annotation.GetMapping; - import org.springframework.web.bind.annotation.RestController; - - @RestController - public class UserController { - @GetMapping("/test") - public void test(){ - System.out.println("test123"); - } - } - ``` - -### Steps to Generate a JWT Token (Similar to [Website Backend Repo](https://github.com/Real-Dev-Squad/website-backend/issues)) - -1. If you've already generated the public and private keys during the website backend setup, you can proceed directly to step 5. -2. Ensure the website-backend is running locally. -3. Generate Public and Private keys using openssl with the following commands: - - `openssl genrsa -out ./private.key 4096` - - `openssl rsa -in private.key -pubout -outform PEM -out public.key` -4. Obtain a user token (private and public key stored in `local.js` file). -5. After calling `localhost:3000/auth/github/login`, the backend will generate the JWT token. -6. Validate the token using [jwt.io](https://jwt.io/) by entering the public and private keys stored in the website backend. -7. Use the public key in the Skill Tree repo to decrypt the JWT token passed for authentication. - -## Steps for connecting mysql workbench to run mysql inside docker - -1. `docker exec -it skill-tree-db-1 bin/bash` -2. bash-4.4# `mysql -u root -p -A` - -By default after deployment MySQL has following connection restrictions: - -``` -mysql> select host, user from mysql.user; -+-----------+---------------+ -| host | user | -+-----------+---------------+ -| localhost | healthchecker | -| localhost | mysql.session | -| localhost | mysql.sys | -| localhost | root | -+-----------+---------------+ -4 rows in set (0.00 sec) -``` - -Apparently, for security purposes, you will not be able to connect to it from outside of the docker container. If you need to change that to allow root to connect from any host (say, for development purposes), do the following: - -3. update mysql.user set host='%' where user='root'; -4. Quit the mysql client. -5. Restart the docker container - -Now you can connect to the mysql running in the docker container, also to connect to it on the terminal type `mysql -utestuser -p -P3306 -h127.0.0.1` - -## Additional Configuration Steps - -1. Download the EnvFile plugin from the marketplace. -2. Create a new Env file with the provided content and fill in the RDS public key value. - ```env - DB_NAME=${DB_NAME} - MYSQL_DB_USERNAME=testuser - MYSQL_DB_PASSWORD=testpassword - MYSQL_ROOT_PASSWORD=password - DB_DDL_POLICY=update - RDS_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - -----END PUBLIC KEY-----" - ``` - > Note : Publickey in both [RDS backend](https://github.com/Real-Dev-Squad/website-backend) and skilltree backend should be the same. -3. Click "Edit Configurations" -> Create a new application. -4. Give it a name instead of "Unnamed". -5. In "Build and Run", select Java 17. -6. In the class , check "Enable Env file" and "Substitute env var" checkboxes. -7. Click "Apply" and "OK". -8. Click "Build and Run". -9. Retrieve the Bearer token by accessing `http://localhost:3000/auth/github/login` and locating the key `rds-session-development` in the application. The value associated with this key is the `Bearer token`. -10. Click the green "Run" button or "Shift + F10" to start the application -11. After starting the Tomcat server on port `8080`, attempt to access the dummy route `http://localhost:8080/test` using the `GET` method in Postman or ThunderClient while providing the `bearer token`. If the terminal displays `test123`, it indicates that the setup has been successful. ## To Authenticate Yourself @@ -212,10 +93,3 @@ Please build using `mvn compile` in local or run `mvn spotless:apply` before pus To check if the codebase is formatted, you can explicitly use `mvn spotless:check` The Continuous Integration build for pushed commits may fail when a Pull Request is created if your code doesn't follow project's formatting guideline. - -## Known Issues Faced by Other Developers - -1. Port 8080 Conflict: Make sure there is no other process running on the 8080 port where we are going to run our server check this with lsof -p PID (PID - port id) -2. Local MySQL Conflict: Make sure there is no local Mysql running on the local machine - ---- diff --git a/public/create-new-application.png b/public/create-new-application.png new file mode 100644 index 00000000..625fc9f7 Binary files /dev/null and b/public/create-new-application.png differ diff --git a/public/highlight-add-new-env-cta.png b/public/highlight-add-new-env-cta.png new file mode 100644 index 00000000..b5bd64d9 Binary files /dev/null and b/public/highlight-add-new-env-cta.png differ diff --git a/public/highlight-check-env.png b/public/highlight-check-env.png new file mode 100644 index 00000000..fc25934e Binary files /dev/null and b/public/highlight-check-env.png differ diff --git a/public/highlight-edit-config.png b/public/highlight-edit-config.png new file mode 100644 index 00000000..7f362182 Binary files /dev/null and b/public/highlight-edit-config.png differ diff --git a/public/highlight-env-file-plugin.png b/public/highlight-env-file-plugin.png new file mode 100644 index 00000000..25aba38c Binary files /dev/null and b/public/highlight-env-file-plugin.png differ diff --git a/public/highlight-env-list.png b/public/highlight-env-list.png new file mode 100644 index 00000000..adac3344 Binary files /dev/null and b/public/highlight-env-list.png differ diff --git a/public/highlight-java-version-main-class.png b/public/highlight-java-version-main-class.png new file mode 100644 index 00000000..742a0e8b Binary files /dev/null and b/public/highlight-java-version-main-class.png differ diff --git a/public/highlight-run-application.png b/public/highlight-run-application.png new file mode 100644 index 00000000..0c729b38 Binary files /dev/null and b/public/highlight-run-application.png differ diff --git a/skill-tree/pom.xml b/skill-tree/pom.xml index 167467e8..ac132714 100644 --- a/skill-tree/pom.xml +++ b/skill-tree/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.2 + 3.3.2 com.RDS @@ -114,6 +114,16 @@ 5.3.1 test + + org.flywaydb + flyway-core + 10.17.1 + + + org.flywaydb + flyway-mysql + 10.17.1 + diff --git a/skill-tree/skilltree-database-script.sql b/skill-tree/skilltree-database-script.sql deleted file mode 100644 index 2c9afbb9..00000000 --- a/skill-tree/skilltree-database-script.sql +++ /dev/null @@ -1,90 +0,0 @@ -CREATE DATABASE IF NOT EXISTS skilltree; -USE skilltree; - -CREATE TABLE `users` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `first_name` varchar(255) NOT NULL, - `image_url` varchar(255) NOT NULL, - `last_name` varchar(255) DEFAULT NULL, - `rds_user_id` varchar(255) DEFAULT NULL, - `user_role` enum('MAVEN','MEMBER','SUPERUSER','USER') NOT NULL, - `created_by` binary(16) DEFAULT NULL, - `updated_by` binary(16) DEFAULT NULL, - `user_type` tinyint DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `FKibk1e3kaxy5sfyeekp8hbhnim` (`created_by`), - KEY `FKci7xr690rvyv3bnfappbyh8x0` (`updated_by`), - CONSTRAINT `FKci7xr690rvyv3bnfappbyh8x0` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`), - CONSTRAINT `FKibk1e3kaxy5sfyeekp8hbhnim` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - - -CREATE TABLE `skill` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `is_deleted` bit(1) NOT NULL, - `name` varchar(255) NOT NULL, - `skill_type` enum('ATOMIC','DERIVED') NOT NULL, - `created_by` binary(16) DEFAULT NULL, - `updated_by` binary(16) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `uc_skill_name` (`name`), - KEY `FKk9ihlls00bo5ljvdgnsgf7r5w` (`created_by`), - KEY `FKga0cp46ei9oe50sknbkty2xh7` (`updated_by`), - CONSTRAINT `FKga0cp46ei9oe50sknbkty2xh7` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`), - CONSTRAINT `FKk9ihlls00bo5ljvdgnsgf7r5w` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - -CREATE TABLE `endorsements` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `endorsement_status` enum('APPROVED','PENDING','REJECTED') DEFAULT NULL, - `created_by` binary(16) DEFAULT NULL, - `updated_by` binary(16) DEFAULT NULL, - `skill_id` binary(16) DEFAULT NULL, - `user_id` binary(16) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `FK3sf16mlcqdyg1onb1jc6mxeio` (`created_by`), - KEY `FK6emgcd942r9a82xxexnqnylq1` (`updated_by`), - KEY `FKjuhgii7d9bdgjke3oj23bekjr` (`skill_id`), - KEY `FKqifr4ov88b3t52y9y0ulyuk84` (`user_id`), - CONSTRAINT `FK3sf16mlcqdyg1onb1jc6mxeio` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`), - CONSTRAINT `FK6emgcd942r9a82xxexnqnylq1` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`), - CONSTRAINT `FKjuhgii7d9bdgjke3oj23bekjr` FOREIGN KEY (`skill_id`) REFERENCES `skill` (`id`), - CONSTRAINT `FKqifr4ov88b3t52y9y0ulyuk84` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - -CREATE TABLE `endorsement_list` ( - `id` binary(16) NOT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `is_deleted` bit(1) NOT NULL, - `description` varchar(255) DEFAULT NULL, - `type` enum('NEGATIVE','POSITIVE') NOT NULL, - `created_by` binary(16) DEFAULT NULL, - `updated_by` binary(16) DEFAULT NULL, - `endorsement_id` binary(16) DEFAULT NULL, - `user_id` binary(16) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `UK_b2pke547fdyxfb3oxigi5pjm3` (`user_id`), - KEY `FKqqxe0t3a2s23kgb2f48holnkk` (`created_by`), - KEY `FKpegfdaa9fjc38wu10554ib3bj` (`updated_by`), - KEY `FK1b6wybyeg8g4h5ov6pku3or7x` (`endorsement_id`), - CONSTRAINT `FK1b6wybyeg8g4h5ov6pku3or7x` FOREIGN KEY (`endorsement_id`) REFERENCES `endorsements` (`id`), - CONSTRAINT `FKpegfdaa9fjc38wu10554ib3bj` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`), - CONSTRAINT `FKqqxe0t3a2s23kgb2f48holnkk` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`), - CONSTRAINT `FKr2uc9u9bi64lae1hiy61v206h` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - -CREATE TABLE `user_skill` ( - `user_id` binary(16) NOT NULL, - `skill_id` binary(16) NOT NULL, - PRIMARY KEY (`user_id`,`skill_id`), - KEY `FKj53flyds4vknyh8llw5d7jdop` (`skill_id`), - CONSTRAINT `FKc2o84wspod5se9pa8lxmhig0q` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), - CONSTRAINT `FKj53flyds4vknyh8llw5d7jdop` FOREIGN KEY (`skill_id`) REFERENCES `skill` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Authentication/UserAuthenticationToken.java b/skill-tree/src/main/java/com/RDS/skilltree/Authentication/UserAuthenticationToken.java index e2efea72..7780b9b4 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Authentication/UserAuthenticationToken.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/Authentication/UserAuthenticationToken.java @@ -1,7 +1,7 @@ package com.RDS.skilltree.Authentication; -import com.RDS.skilltree.User.UserModel; -import com.RDS.skilltree.User.UserRole; +import com.RDS.skilltree.enums.UserRoleEnum; +import com.RDS.skilltree.models.JwtUser; import java.util.List; import javax.security.auth.Subject; import org.springframework.security.authentication.AbstractAuthenticationToken; @@ -9,11 +9,12 @@ public class UserAuthenticationToken extends AbstractAuthenticationToken { - private final UserModel user; + private final JwtUser user; public UserAuthenticationToken(String role, String rdsUserId) { - super(List.of(new SimpleGrantedAuthority(UserRole.fromString(role).name()))); - this.user = UserModel.builder().rdsUserId(rdsUserId).role(UserRole.fromString(role)).build(); + super(List.of(new SimpleGrantedAuthority(UserRoleEnum.fromString(role).name()))); + + this.user = new JwtUser(rdsUserId, UserRoleEnum.fromString(role)); setAuthenticated(true); } @@ -23,7 +24,7 @@ public Object getCredentials() { } @Override - public UserModel getPrincipal() { + public JwtUser getPrincipal() { return user; } diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementController.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementController.java deleted file mode 100644 index 7b154118..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementController.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import com.RDS.skilltree.Common.Response.GenericResponse; -import jakarta.persistence.EntityNotFoundException; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import java.io.IOException; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/v1/endorsements") -@Slf4j -@RequiredArgsConstructor -public class EndorsementController { - private final EndorsementService endorsementService; - - @GetMapping(value = "") - public ResponseEntity> getAllEndorsements( - @RequestParam(name = "offset", defaultValue = "0", required = false) @Min(0) int offset, - @RequestParam(name = "limit", defaultValue = "10", required = false) @Min(1) int limit, - @RequestParam(name = "skillID", required = false) String skillID, - @RequestParam(name = "userID", required = false) String userID, - @RequestParam(name = "dummyData", required = false) boolean dummyData) - throws IOException { - PageRequest pageRequest = PageRequest.of(offset, limit); - if (dummyData) { - Page pagedEndorsements = - endorsementService.getEndorsementsFromDummyData(pageRequest, skillID, userID); - if (pagedEndorsements.isEmpty()) { - return ResponseEntity.noContent().build(); - } else { - return ResponseEntity.ok(pagedEndorsements); - } - } else { - return ResponseEntity.ok(endorsementService.getEndorsements(pageRequest)); - } - } - - @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> getEndorsementById( - @PathVariable(value = "id", required = true) String id) { - try { - UUID uuid = UUID.fromString(id); - EndorsementDTO response = endorsementService.getEndorsementById(uuid); - return ResponseEntity.ok() - .body(new GenericResponse(response, "Data retrieved successfully")); - } catch (IllegalArgumentException e) { - String message = "Invalid UUID: " + id; - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new GenericResponse(null, message)); - } catch (EntityNotFoundException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(new GenericResponse(null, e.getMessage())); - } catch (Exception e) { - String message = "Something went wrong. Please contact admin."; - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(new GenericResponse(null, message)); - } - } - - @PostMapping(value = "") - public ResponseEntity> postEndorsement( - @RequestBody @Valid EndorsementDRO endorsementDRO) { - - EndorsementModel endorsementModel = endorsementService.createEndorsement(endorsementDRO); - if (endorsementModel != null) - return new ResponseEntity<>( - new GenericResponse(EndorsementDTO.toDto(endorsementModel), ""), - HttpStatus.CREATED); - return new ResponseEntity<>( - new GenericResponse(null, "Failed to create endorsement"), - HttpStatus.BAD_REQUEST); - } - - @PatchMapping(value = "/{id}") - public ResponseEntity> updateEndorsementStatus( - @PathVariable(value = "id") UUID id, @RequestParam String status) { - return ResponseEntity.ok().body(endorsementService.updateEndorsementStatus(id, status)); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementDRO.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementDRO.java deleted file mode 100644 index d0910431..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementDRO.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import jakarta.validation.constraints.NotNull; -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@AllArgsConstructor -@Data -@Builder -public class EndorsementDRO { - @NotNull(message = "user id cannot be null") - private UUID endorseeId; - - @NotNull(message = "skill id cannot be null") - private UUID skillId; -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementDTO.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementDTO.java deleted file mode 100644 index 3047e63f..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementDTO.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import com.RDS.skilltree.EndorsementList.EndorsementListModel; -import com.RDS.skilltree.Skill.SkillDTO; -import com.RDS.skilltree.utils.TrackedProperties; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.util.List; -import java.util.UUID; -import lombok.*; - -@Getter -@Builder -@JsonIgnoreProperties(ignoreUnknown = true) -public class EndorsementDTO extends TrackedProperties { - private UUID id; - private UUID endorseeId; - private SkillDTO skill; - private EndorsementStatus status; - private List endorsersList; - - public static EndorsementDTO toDto(EndorsementModel endorsementModel) { - EndorsementDTO endorsementDTO = - EndorsementDTO.builder() - .id(endorsementModel.getId()) - .endorseeId(endorsementModel.getEndorseeId()) - .skill(SkillDTO.toDto(endorsementModel.getSkill())) - .status(endorsementModel.getStatus()) - .endorsersList(endorsementModel.getEndorsersList()) - .build(); - endorsementDTO.setCreatedAt(endorsementModel.getCreatedAt()); - endorsementDTO.setUpdatedAt(endorsementModel.getUpdatedAt()); - endorsementDTO.setCreatedBy(endorsementModel.getCreatedBy()); - endorsementDTO.setUpdatedBy(endorsementModel.getUpdatedBy()); - return endorsementDTO; - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementModel.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementModel.java deleted file mode 100644 index 68a3faec..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementModel.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import com.RDS.skilltree.EndorsementList.EndorsementListModel; -import com.RDS.skilltree.Skill.SkillModel; -import com.RDS.skilltree.utils.TrackedProperties; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import jakarta.persistence.*; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import lombok.*; - -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Builder -@Getter -@Table(name = "endorsements") -public class EndorsementModel extends TrackedProperties { - @Id - @GeneratedValue - @Column(name = "id", columnDefinition = "BINARY(16)") - private UUID id; - - @Column(name = "endorsee_id") - private UUID endorseeId; - - @ManyToOne(targetEntity = SkillModel.class, cascade = CascadeType.ALL) - @JoinColumn(name = "skill_id", referencedColumnName = "id") - private SkillModel skill; - - @OneToMany(mappedBy = "endorsement") - @JsonManagedReference - private List endorsersList = new ArrayList<>(); - - @Column(name = "endorsement_status") - @Enumerated(value = EnumType.STRING) - private EndorsementStatus status = EndorsementStatus.PENDING; -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementModelFromJSON.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementModelFromJSON.java deleted file mode 100644 index c399043d..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementModelFromJSON.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.time.LocalDateTime; -import java.util.UUID; -import lombok.*; - -/* TODO:Dummy JSON code, needs to be changed as part of #103 */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -public class EndorsementModelFromJSON { - private UUID id; - - @JsonProperty("user_id") - private UUID userID; - - @JsonProperty("skill_id") - private UUID skillId; - - private String status; - - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") - @JsonProperty("created_at") - private LocalDateTime createdAt; - - @JsonProperty("created_by") - private UUID createdBy; - - @JsonProperty("updated_at") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") - private LocalDateTime updatedAt; - - @JsonProperty("updated_by") - private UUID updatedBy; -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementRepository.java deleted file mode 100644 index 6da326dd..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import java.util.List; -import java.util.UUID; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface EndorsementRepository extends JpaRepository { - List findByEndorseeId(UUID userId); - - List findBySkillId(UUID skillId); -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementService.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementService.java deleted file mode 100644 index a7254a48..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementService.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import com.RDS.skilltree.Common.Response.GenericResponse; -import java.io.IOException; -import java.util.UUID; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; - -public interface EndorsementService { - EndorsementDTO getEndorsementById(UUID id); - - Page getEndorsements(PageRequest pageRequest); - - /* TODO:Dummy JSON code, needs to be changed as part of #103 */ - Page getEndorsementsFromDummyData( - PageRequest pageRequest, String skillID, String userID) throws IOException; - - EndorsementModel createEndorsement(EndorsementDRO endorsementDRO); - - GenericResponse updateEndorsementStatus(UUID id, String status); -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementServiceImpl.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementServiceImpl.java deleted file mode 100644 index fce1ed63..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementServiceImpl.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import com.RDS.skilltree.Common.Response.GenericResponse; -import com.RDS.skilltree.Exceptions.EntityAlreadyExistsException; -import com.RDS.skilltree.Exceptions.InvalidParameterException; -import com.RDS.skilltree.Exceptions.NoEntityException; -import com.RDS.skilltree.Skill.SkillModel; -import com.RDS.skilltree.Skill.SkillRepository; -import com.RDS.skilltree.User.UserModel; -import com.RDS.skilltree.User.UserRole; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.persistence.EntityNotFoundException; -import java.io.IOException; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ClassPathResource; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class EndorsementServiceImpl implements EndorsementService { - private final EndorsementRepository endorsementRepository; - private final SkillRepository skillRepository; - - private final ObjectMapper objectMapper; - - @Value("${endorsements.dummmy-data.path}") - private String dummyEndorsementDataPath; - - private static final Logger logger = LoggerFactory.getLogger(EndorsementServiceImpl.class); - - @Override - public EndorsementDTO getEndorsementById(UUID id) throws IllegalStateException { - Optional endorsementModel = endorsementRepository.findById(id); - return EndorsementDTO.toDto( - endorsementModel.orElseThrow( - () -> new EntityNotFoundException("No endorsement with the id " + id + " found"))); - } - - @Override - public Page getEndorsements(PageRequest pageRequest) { - return endorsementRepository.findAll(pageRequest); - } - - /* TODO:Dummy JSON code, needs to be changed as part of #103 */ - @Override - public Page getEndorsementsFromDummyData( - PageRequest pageRequest, String skillID, String userID) throws IOException { - - try { - List endorsementModelFromJSONList = readEndorsementsFromJSON(); - - if (endorsementModelFromJSONList.isEmpty()) { - return Page.empty(pageRequest); - } - - List filteredEndorsements = - filterEndorsements(endorsementModelFromJSONList, skillID, userID); - return createPagedEndorsements(filteredEndorsements, pageRequest); - } catch (IOException e) { - logger.error("Error reading endorsements JSON data: {}", e.getMessage()); - throw new IOException("Error reading endorsements JSON data", e); - } - } - - private List readEndorsementsFromJSON() throws IOException { - ClassPathResource resource = new ClassPathResource(dummyEndorsementDataPath); - return objectMapper.readValue( - resource.getInputStream(), new TypeReference>() {}); - } - - private List filterEndorsements( - List endorsements, String skillID, String userID) { - return endorsements.stream() - .filter(endorsement -> isMatchingSkillId(endorsement, skillID)) - .filter(endorsement -> isMatchingUserId(endorsement, userID)) - .collect(Collectors.toList()); - } - - private boolean isMatchingSkillId(EndorsementModelFromJSON endorsement, String skillID) { - return skillID == null - || skillID.isEmpty() - || endorsement.getSkillId().equals(UUID.fromString(skillID)); - } - - private boolean isMatchingUserId(EndorsementModelFromJSON endorsement, String userID) { - return userID == null - || userID.isEmpty() - || endorsement.getUserID().equals(UUID.fromString(userID)); - } - - private Page createPagedEndorsements( - List endorsements, PageRequest pageRequest) { - int startIdx = (int) pageRequest.getOffset(); - int totalEndorsements = endorsements.size(); - - if (startIdx >= totalEndorsements) { - return Page.empty(pageRequest); - } - - int endIdx = Math.min(startIdx + pageRequest.getPageSize(), endorsements.size()); - List currentPageEndorsements = endorsements.subList(startIdx, endIdx); - return new PageImpl<>(currentPageEndorsements, pageRequest, endorsements.size()); - } - - @Override - public EndorsementModel createEndorsement(EndorsementDRO endorsementDRO) { - UUID userId = endorsementDRO.getEndorseeId(); - UUID skillId = endorsementDRO.getSkillId(); - - Optional skillOptional = skillRepository.findById(skillId); - if (skillOptional.isPresent()) { - EndorsementModel endorsementModel = - EndorsementModel.builder().endorseeId(userId).skill(skillOptional.get()).build(); - - return endorsementRepository.save(endorsementModel); - } else { - - throw new NoEntityException("Skill with id:" + skillId + " not found"); - } - } - - @Override - public GenericResponse updateEndorsementStatus(UUID id, String status) { - UserModel user = - (UserModel) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - if (!user.getRole().equals(UserRole.SUPERUSER)) { - throw new AccessDeniedException("Unauthorized, Access is only available to super users"); - } - - EndorsementStatus endorsementStatus = EndorsementStatus.fromString(status); - if (endorsementStatus.equals(EndorsementStatus.PENDING)) { - throw new InvalidParameterException("endorsement status", status); - } - Optional optionalEndorsementModel = endorsementRepository.findById(id); - if (optionalEndorsementModel.isPresent()) { - if (optionalEndorsementModel.get().getStatus() != EndorsementStatus.PENDING) { - throw new EntityAlreadyExistsException( - "Endorsement is already updated. Cannot modify status"); - } - EndorsementModel updatedEndorsementModel = - EndorsementModel.builder() - .id(optionalEndorsementModel.get().getId()) - .endorseeId(optionalEndorsementModel.get().getEndorseeId()) - .skill(optionalEndorsementModel.get().getSkill()) - .endorsersList(optionalEndorsementModel.get().getEndorsersList()) - .status(EndorsementStatus.valueOf(status)) - .build(); - endorsementRepository.save(updatedEndorsementModel); - return new GenericResponse<>(null, "Successfully updated endorsement status"); - } - throw new NoEntityException("No endorsement with id " + id + " was found"); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementStatus.java b/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementStatus.java deleted file mode 100644 index 9300b618..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Endorsement/EndorsementStatus.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.RDS.skilltree.Endorsement; - -import com.RDS.skilltree.Exceptions.InvalidParameterException; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public enum EndorsementStatus { - APPROVED, - PENDING, - REJECTED; - - private static final Map endorsementStatusMap = - Stream.of(values()) - .collect(Collectors.toMap(EndorsementStatus::toString, Function.identity())); - - public static EndorsementStatus fromString(final String name) { - EndorsementStatus endorsementStatus = endorsementStatusMap.get(name); - if (endorsementStatus == null) { - throw new InvalidParameterException("endorsement status", name); - } - return endorsementStatus; - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListDRO.java b/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListDRO.java deleted file mode 100644 index 99ad11b2..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListDRO.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.RDS.skilltree.EndorsementList; - -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@AllArgsConstructor -@NoArgsConstructor -@Data -@Builder -public class EndorsementListDRO { - private String description; - private UUID endorsementId; - private UUID endorserId; - private EndorsementType type; -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListDTO.java b/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListDTO.java deleted file mode 100644 index d840d6c9..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.RDS.skilltree.EndorsementList; - -import com.RDS.skilltree.User.UserModel; -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; - -@Builder -@Data -@AllArgsConstructor -public class EndorsementListDTO { - private UUID id; - private String description; - private UserModel endorser; - private EndorsementType type; -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListModel.java b/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListModel.java deleted file mode 100644 index c734925d..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListModel.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.RDS.skilltree.EndorsementList; - -import com.RDS.skilltree.Endorsement.EndorsementModel; -import com.RDS.skilltree.utils.TrackedProperties; -import com.fasterxml.jackson.annotation.JsonBackReference; -import jakarta.persistence.*; -import java.util.UUID; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@EqualsAndHashCode(callSuper = true) -@Entity -@NoArgsConstructor -@Data -@Table(name = "endorsement_list") -public class EndorsementListModel extends TrackedProperties { - @Id - @GeneratedValue - @Column(name = "id", columnDefinition = "BINARY(16)") - private UUID id; - - @JoinColumn(name = "endorsement_id", referencedColumnName = "id") - @ManyToOne(targetEntity = EndorsementModel.class, cascade = CascadeType.ALL) - @JsonBackReference - private EndorsementModel endorsement; - - @Column(name = "endorser_id") - private UUID endorserId; - - @Column(name = "description") - private String description; - - @Column(name = "is_deleted", nullable = false) - private boolean deleted; - - @Column(name = "type", nullable = false) - @Enumerated(value = EnumType.STRING) - private EndorsementType type; - - public EndorsementListModel( - EndorsementModel endorsement, UUID endorserId, String description, EndorsementType type) { - this.endorsement = endorsement; - this.endorserId = endorserId; - this.description = description; - this.type = type; - this.deleted = false; - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListRepository.java deleted file mode 100644 index ce372c5f..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.RDS.skilltree.EndorsementList; - -import java.util.UUID; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface EndorsementListRepository extends JpaRepository {} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListService.java b/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListService.java deleted file mode 100644 index c26308b2..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementListService.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.RDS.skilltree.EndorsementList; - -import com.RDS.skilltree.Endorsement.EndorsementModel; -import com.RDS.skilltree.Endorsement.EndorsementRepository; -import com.RDS.skilltree.Exceptions.NoEntityException; -import java.util.Optional; -import java.util.UUID; -import org.springframework.stereotype.Service; - -@Service -public class EndorsementListService { - - private final EndorsementListRepository endorsementListRepository; - private final EndorsementRepository endorsementRepository; - - public EndorsementListService( - EndorsementListRepository endorsementListRepository, - EndorsementRepository endorsementRepository) { - this.endorsementListRepository = endorsementListRepository; - this.endorsementRepository = endorsementRepository; - } - - public EndorsementListModel createEndorsementListEntry(EndorsementListDRO endorsementListDRO) { - EndorsementListModel endorsementListEntry = new EndorsementListModel(); - - UUID endorserId = endorsementListDRO.getEndorserId(); - UUID endorsementId = endorsementListDRO.getEndorsementId(); - - Optional endorsementOptional = endorsementRepository.findById(endorsementId); - if (endorsementOptional.isPresent()) { - - endorsementListEntry.setEndorserId(endorserId); - endorsementListEntry.setEndorsement(endorsementOptional.get()); - endorsementListEntry.setDescription(endorsementListDRO.getDescription()); - endorsementListEntry.setType(endorsementListDRO.getType()); - endorsementListRepository.save(endorsementListEntry); - return endorsementListEntry; - - } else { - throw new NoEntityException("Endorsement with id:" + endorsementId + " not found"); - } - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementType.java b/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementType.java deleted file mode 100644 index 1d028fc8..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/EndorsementList/EndorsementType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.RDS.skilltree.EndorsementList; - -public enum EndorsementType { - POSITIVE, - NEGATIVE -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Health/HealthCheckController.java b/skill-tree/src/main/java/com/RDS/skilltree/Health/HealthCheckController.java deleted file mode 100644 index 854ddece..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Health/HealthCheckController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.RDS.skilltree.Health; - -import com.RDS.skilltree.metrics.MetricService; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v1/health") -@Slf4j -public class HealthCheckController { - - private final MetricService metricService; - - @Autowired - public HealthCheckController(MetricService metricService) { - this.metricService = metricService; - } - - @GetMapping("") - public Map checkHealth() { - double uptime = metricService.getUptime(); - return Map.of("uptimeInSeconds", uptime); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillDRO.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillDRO.java deleted file mode 100644 index 543c4ace..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillDRO.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.RDS.skilltree.Skill; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import jakarta.validation.constraints.NotNull; -import java.util.UUID; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -@JsonIgnoreProperties(ignoreUnknown = true) -public class SkillDRO { - @NotNull(message = "Name cannot be null") - private String name; - - @NotNull(message = "SkillType cannot be null") - private SkillType type; - - @NotNull(message = "Created by user Id cannot be null") - private UUID createdBy; - - public static SkillModel toModel(SkillDRO skillDRO) { - return SkillModel.builder() - .name(skillDRO.getName()) - .type(skillDRO.getType()) - .deleted(false) - .build(); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillDTO.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillDTO.java deleted file mode 100644 index a6304e2b..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillDTO.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.RDS.skilltree.Skill; - -import com.RDS.skilltree.User.UserDTO; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -@JsonIgnoreProperties(ignoreUnknown = true) -public class SkillDTO { - private UUID id; - private SkillType type; - private String name; - private Set users; - - public static SkillDTO toDto(SkillModel skillModel) { - return SkillDTO.builder() - .id(skillModel.getId()) - .name(skillModel.getName()) - .type(skillModel.getType()) - .build(); - } - - public static SkillDTO getSkillsWithUsers(SkillModel skillModel) { - Set users = new HashSet<>(); - if (skillModel.getUsers() != null) { - users = skillModel.getUsers().stream().map(UserDTO::toDTO).collect(Collectors.toSet()); - } - return SkillDTO.builder() - .id(skillModel.getId()) - .name(skillModel.getName()) - .type(skillModel.getType()) - .users(users) - .build(); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillModel.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillModel.java deleted file mode 100644 index 0074c602..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillModel.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.RDS.skilltree.Skill; - -import com.RDS.skilltree.User.UserModel; -import com.RDS.skilltree.utils.TrackedProperties; -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonIgnore; -import jakarta.persistence.*; -import java.util.Set; -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Builder -@Getter -@Table(name = "Skill") -public class SkillModel extends TrackedProperties { - @Id - @GeneratedValue - @Column(name = "id", columnDefinition = "BINARY(16)") - private UUID id; - - @Column(name = "name", unique = true, nullable = false) - private String name; - - @Column(name = "skill_type", nullable = false) - @Enumerated(value = EnumType.STRING) - private SkillType type = SkillType.ATOMIC; - - @Column(name = "is_deleted", nullable = false) - private boolean deleted; - - @JsonBackReference - @JsonIgnore - @ManyToMany(mappedBy = "skills", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - private Set users; -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillRepository.java deleted file mode 100644 index a523a3b0..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.RDS.skilltree.Skill; - -import java.util.Optional; -import java.util.UUID; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface SkillRepository extends JpaRepository { - Optional findByName(String name); - - Page findAll(Pageable pageable); -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillType.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillType.java deleted file mode 100644 index 32500383..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.RDS.skilltree.Skill; - -public enum SkillType { - ATOMIC, - DERIVED -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsController.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsController.java deleted file mode 100644 index 00e9f669..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsController.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.RDS.skilltree.Skill; - -import com.RDS.skilltree.utils.MessageResponse; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import java.util.UUID; -import lombok.extern.slf4j.Slf4j; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.ObjectUtils; -import org.springframework.web.bind.annotation.*; - -@RestController -@Slf4j -@RequestMapping("/v1/skills") -public class SkillsController { - private final SkillsService skillsService; - - public SkillsController(SkillsService skillsService) { - this.skillsService = skillsService; - } - - @PostMapping("/") - public ResponseEntity createSkill(@RequestBody(required = true) @Valid SkillDRO skillDRO) { - try { - return ResponseEntity.status(HttpStatus.CREATED).body(skillsService.createSkill(skillDRO)); - } catch (DataIntegrityViolationException ex) { - return ResponseEntity.status(HttpStatus.CONFLICT) - .body(new MessageResponse("Cannot create entry for Skill as Skill name is duplicate")); - } catch (Exception ex) { - log.error( - "There is some error in storing the skills, error message: {}", ex.getMessage(), ex); - throw ex; - } - } - - @GetMapping("/") - public Page getAllSkills( - @RequestParam(value = "offset", defaultValue = "0", required = false) @Min(0) int offset, - @RequestParam(value = "limit", defaultValue = "10", required = false) @Min(1) int limit) { - Pageable pageable = PageRequest.of(offset, limit); - return skillsService.getAllSkills(pageable); - } - - @GetMapping("/name/{name}") - public ResponseEntity getSkillByName( - @PathVariable(value = "name", required = true) String name) { - SkillDTO skillDTO = skillsService.getSkillByName(name); - if (ObjectUtils.isEmpty(skillDTO)) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(new MessageResponse("Skill not found with the given name")); - } - return ResponseEntity.ok(skillDTO); - } - - @GetMapping("/{id}") - public ResponseEntity getSkillById(@PathVariable(value = "id", required = true) UUID id) { - SkillDTO skillDTO = skillsService.getSkillById(id); - if (ObjectUtils.isEmpty(skillDTO)) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(new MessageResponse("Skill not found with given Id")); - } - return ResponseEntity.ok(skillDTO); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsService.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsService.java deleted file mode 100644 index b8a4e015..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.RDS.skilltree.Skill; - -import java.util.UUID; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -public interface SkillsService { - SkillDTO getSkillById(UUID id); - - SkillDTO getSkillByName(String skillName); - - Page getAllSkills(Pageable pageable); - - SkillDTO createSkill(SkillDRO skillDRO); -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsServiceImpl.java b/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsServiceImpl.java deleted file mode 100644 index 61943723..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/Skill/SkillsServiceImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.RDS.skilltree.Skill; - -import com.RDS.skilltree.User.UserRepository; -import java.time.Instant; -import java.util.Optional; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -@RequiredArgsConstructor -public class SkillsServiceImpl implements SkillsService { - private final SkillRepository skillRepository; - private final UserRepository userRepository; - - @Override - public SkillDTO getSkillById(UUID id) { - Optional skillModel = skillRepository.findById(id); - return skillModel.map(SkillDTO::getSkillsWithUsers).orElse(null); - } - - @Override - public SkillDTO getSkillByName(String skillName) { - Optional skillModel = skillRepository.findByName(skillName); - return skillModel.map(SkillDTO::getSkillsWithUsers).orElse(null); - } - - @Override - public Page getAllSkills(Pageable pageable) { - Page skillModels = skillRepository.findAll(pageable); - return skillModels.map(SkillDTO::getSkillsWithUsers); - } - - @Override - public SkillDTO createSkill(SkillDRO skillDRO) { - SkillModel newSkill = SkillDRO.toModel(skillDRO); - newSkill.setCreatedAt(Instant.now()); - newSkill.setUpdatedAt(Instant.now()); - - try { - skillRepository.save(newSkill); - } catch (DataIntegrityViolationException ex) { - log.error( - "Error saving the skills object with name : {}, with exception :{}", - skillDRO.getName(), - ex.getMessage(), - ex); - throw ex; - } - return SkillDTO.toDto(newSkill); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/User/UserDRO.java b/skill-tree/src/main/java/com/RDS/skilltree/User/UserDRO.java deleted file mode 100644 index c0141963..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/User/UserDRO.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.RDS.skilltree.User; - -import java.net.URL; -import java.time.Instant; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@AllArgsConstructor -@NoArgsConstructor -@Data -@Builder -public class UserDRO { - private String rdsUserId; - - private String firstName; - - private String lastName; - - private URL imageUrl; - - private UserRole role; - - public static UserModel toModel(UserDRO user) { - return UserModel.builder() - .rdsUserId(user.getRdsUserId()) - .firstName(user.getFirstName()) - .lastName(user.getLastName()) - .role(user.getRole()) - .imageUrl(user.getImageUrl()) - .build(); - } - - public static UserDRO fromModel(UserModel user) { - return UserDRO.builder() - .rdsUserId(user.getRdsUserId()) - .role(user.getRole()) - .firstName(user.getFirstName()) - .lastName(user.getLastName()) - .imageUrl(user.getImageUrl()) - .build(); - } - - public static UserModel compareAndUpdateModel(UserModel user, UserDRO userDRO) { - if (userDRO.getRdsUserId() != null) { - user.setRdsUserId(user.getRdsUserId()); - } - if (userDRO.getFirstName() != null) { - user.setFirstName(user.getFirstName()); - } - if (userDRO.getLastName() != null) { - user.setLastName(user.getLastName()); - } - if (userDRO.getImageUrl() != null) { - user.setImageUrl(user.getImageUrl()); - } - if (userDRO.getRole() != null) { - user.setRole(user.getRole()); - } - user.setUpdatedAt(Instant.now()); - return user; - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/User/UserDTO.java b/skill-tree/src/main/java/com/RDS/skilltree/User/UserDTO.java deleted file mode 100644 index fedccf7b..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/User/UserDTO.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.RDS.skilltree.User; - -import com.RDS.skilltree.Skill.SkillDTO; -import java.net.URL; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class UserDTO { - - private UUID id; - - private String rdsUserId; - - private String firstName; - - private String lastName; - - private URL imageUrl; - - private UserRole role; - - private Set skills; - - public static UserDTO toDTO(UserModel user) { - - return UserDTO.builder() - .id(user.getId()) - .rdsUserId(user.getRdsUserId()) - .firstName(user.getFirstName()) - .lastName(user.getLastName()) - .imageUrl(user.getImageUrl()) - .role(user.getRole()) - .build(); - } - - public static UserDTO getUsersWithSkills(UserModel user) { - Set skills = - user.getSkills().stream().map(SkillDTO::toDto).collect(Collectors.toSet()); - - return UserDTO.builder() - .id(user.getId()) - .rdsUserId(user.getRdsUserId()) - .firstName(user.getFirstName()) - .lastName(user.getLastName()) - .imageUrl(user.getImageUrl()) - .skills(skills) - .role(user.getRole()) - .build(); - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/User/UserModel.java b/skill-tree/src/main/java/com/RDS/skilltree/User/UserModel.java deleted file mode 100644 index 42776846..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/User/UserModel.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.RDS.skilltree.User; - -import com.RDS.skilltree.Skill.SkillModel; -import com.RDS.skilltree.utils.TrackedProperties; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.persistence.*; -import java.net.URL; -import java.util.Set; -import java.util.UUID; -import lombok.*; - -@Entity -@Getter -@Setter -@Builder -@JsonSerialize -@NoArgsConstructor -@AllArgsConstructor -@Table(name = "Users") -public class UserModel extends TrackedProperties { - @Id - @GeneratedValue - @Column(name = "id", columnDefinition = "BINARY(16)") - private UUID id; - - @Column(name = "rds_user_id", unique = true) - private String rdsUserId; - - @Column(name = "first_name", nullable = false) - private String firstName; - - @Column(name = "last_name") - private String lastName; - - @Column(name = "image_url", nullable = false) - private URL imageUrl; - - @Column(name = "user_role", nullable = false) - @Enumerated(value = EnumType.STRING) - private UserRole role = UserRole.USER; - - @JsonManagedReference - @ManyToMany(targetEntity = SkillModel.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinTable( - name = "user_skill", - joinColumns = @JoinColumn(name = "user_id"), - inverseJoinColumns = @JoinColumn(name = "skill_id")) - private Set skills; -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/User/UserRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/User/UserRepository.java deleted file mode 100644 index a557c19e..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/User/UserRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.RDS.skilltree.User; - -import java.util.UUID; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface UserRepository extends JpaRepository {} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/User/UserService.java b/skill-tree/src/main/java/com/RDS/skilltree/User/UserService.java deleted file mode 100644 index f853bf04..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/User/UserService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.RDS.skilltree.User; - -import java.util.List; -import java.util.UUID; - -public interface UserService { - UserDTO createUser(UserDRO user); - - void updateUser(UUID id, UserDRO user); - - UserDTO getUserById(UUID id); - - List getAllUsers(); - - void addSkill(UUID skill, UUID userId); -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/User/UserServiceImpl.java b/skill-tree/src/main/java/com/RDS/skilltree/User/UserServiceImpl.java deleted file mode 100644 index c85f9d94..00000000 --- a/skill-tree/src/main/java/com/RDS/skilltree/User/UserServiceImpl.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.RDS.skilltree.User; - -import com.RDS.skilltree.Exceptions.NoEntityException; -import com.RDS.skilltree.Skill.SkillModel; -import com.RDS.skilltree.Skill.SkillRepository; -import jakarta.transaction.Transactional; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.stereotype.Service; - -@Service -public class UserServiceImpl implements UserService { - private final UserRepository userRepository; - private final SkillRepository skillRepository; - - public UserServiceImpl(UserRepository userRepository, SkillRepository skillRepository) { - this.userRepository = userRepository; - this.skillRepository = skillRepository; - } - - @Override - public UserDTO createUser(UserDRO user) { - UserModel userModel = UserDRO.toModel(user); - userRepository.save(userModel); - return UserDTO.toDTO(userModel); - } - - @Override - public void updateUser(UUID id, UserDRO user) {} - - @Override - public UserDTO getUserById(UUID id) { - Optional userModel = userRepository.findById(id); - return userModel.map(UserDTO::getUsersWithSkills).orElse(null); - } - - @Override - public List getAllUsers() { - return null; - } - - /** - * updates the user and skill both - * @param skillId - * @param userId - */ - @Override - @Transactional - public void addSkill(UUID skillId, UUID userId) { - Optional userOptional = userRepository.findById(userId); - Optional skillOptional = skillRepository.findById(skillId); - - if (userOptional.isPresent() && skillOptional.isPresent()) { - UserModel userModel = userOptional.get(); - SkillModel skillModel = skillOptional.get(); - - userModel.getSkills().add(skillModel); - skillModel.getUsers().add(userModel); - - userRepository.save(userModel); - skillRepository.save(skillModel); - } else { - if (skillOptional.isEmpty()) { - throw new NoEntityException("Skill Id is not passed in the input"); - } - throw new NoEntityException("User with Id doesn't exists"); - } - } -} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/annotations/AuthorizedRoles.java b/skill-tree/src/main/java/com/RDS/skilltree/annotations/AuthorizedRoles.java new file mode 100644 index 00000000..c788b99c --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/annotations/AuthorizedRoles.java @@ -0,0 +1,13 @@ +package com.RDS.skilltree.annotations; + +import com.RDS.skilltree.enums.UserRoleEnum; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorizedRoles { + UserRoleEnum[] value() default {}; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/apis/EndorsementsApi.java b/skill-tree/src/main/java/com/RDS/skilltree/apis/EndorsementsApi.java new file mode 100644 index 00000000..cd3d5212 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/apis/EndorsementsApi.java @@ -0,0 +1,28 @@ +package com.RDS.skilltree.apis; + +import com.RDS.skilltree.annotations.AuthorizedRoles; +import com.RDS.skilltree.enums.UserRoleEnum; +import com.RDS.skilltree.services.EndorsementService; +import com.RDS.skilltree.viewmodels.EndorsementViewModel; +import com.RDS.skilltree.viewmodels.UpdateEndorsementViewModel; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("v1/endorsements") +@AuthorizedRoles({UserRoleEnum.USER, UserRoleEnum.SUPERUSER}) +public class EndorsementsApi { + private final EndorsementService endorsementService; + + @PatchMapping("/{id}") + public ResponseEntity update( + @PathVariable Integer id, @Valid @RequestBody UpdateEndorsementViewModel body) { + return new ResponseEntity<>(endorsementService.update(id, body), HttpStatus.OK); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/apis/HealthApi.java b/skill-tree/src/main/java/com/RDS/skilltree/apis/HealthApi.java new file mode 100644 index 00000000..88dd1d20 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/apis/HealthApi.java @@ -0,0 +1,25 @@ +package com.RDS.skilltree.apis; + +import com.RDS.skilltree.services.MetricService; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("v1/health") +public class HealthApi { + private final MetricService metricService; + + @GetMapping + public ResponseEntity> getUptime() { + return new ResponseEntity<>( + Map.of("uptime in seconds", metricService.getUptime()), HttpStatus.OK); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java b/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java new file mode 100644 index 00000000..c829e997 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java @@ -0,0 +1,79 @@ +package com.RDS.skilltree.apis; + +import com.RDS.skilltree.annotations.AuthorizedRoles; +import com.RDS.skilltree.dtos.CreateEndorsementRequestDto; +import com.RDS.skilltree.dtos.SkillRequestActionRequestDto; +import com.RDS.skilltree.dtos.SkillRequestsDto; +import com.RDS.skilltree.enums.UserRoleEnum; +import com.RDS.skilltree.enums.UserSkillStatusEnum; +import com.RDS.skilltree.services.EndorsementService; +import com.RDS.skilltree.services.SkillService; +import com.RDS.skilltree.utils.GenericResponse; +import com.RDS.skilltree.viewmodels.CreateEndorsementViewModel; +import com.RDS.skilltree.viewmodels.CreateSkillViewModel; +import com.RDS.skilltree.viewmodels.EndorsementViewModel; +import com.RDS.skilltree.viewmodels.SkillViewModel; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("v1/skills") +public class SkillsApi { + private final SkillService skillService; + private final EndorsementService endorsementService; + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(skillService.getAll()); + } + + @GetMapping("/requests") + public ResponseEntity getAllRequests( + @RequestParam(value = "status", required = false) UserSkillStatusEnum status) { + if (status != null) { + return ResponseEntity.ok(skillService.getRequestsByStatus(status)); + } + + return ResponseEntity.ok(skillService.getAllRequests()); + } + + @PostMapping("/requests/{skillId}/action") + @AuthorizedRoles({UserRoleEnum.SUPERUSER}) + public ResponseEntity> approveRejectSkillRequest( + @PathVariable(value = "skillId") Integer skillId, + @Valid @RequestBody SkillRequestActionRequestDto skillRequestAction) { + return ResponseEntity.ok( + skillService.approveRejectSkillRequest( + skillId, skillRequestAction.getEndorseId(), skillRequestAction.getAction())); + } + + @PostMapping + @AuthorizedRoles({UserRoleEnum.SUPERUSER}) + public ResponseEntity create(@Valid @RequestBody CreateSkillViewModel skill) { + return ResponseEntity.ok(skillService.create(skill)); + } + + @GetMapping("/{id}/endorsements") + public ResponseEntity> getEndorsementsBySkillId( + @PathVariable(value = "id") Integer skillID) { + return ResponseEntity.ok(endorsementService.getAllEndorsementsBySkillId(skillID)); + } + + @PostMapping("/{id}/endorsements") + public ResponseEntity create( + @PathVariable(value = "id") Integer skillID, + @Valid @RequestBody CreateEndorsementRequestDto endorsementRequest) { + return new ResponseEntity<>( + endorsementService.create( + CreateEndorsementViewModel.toViewModel( + skillID, endorsementRequest.getEndorseId(), endorsementRequest.getMessage())), + HttpStatus.CREATED); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/aspects/AuthorizedRolesAspect.java b/skill-tree/src/main/java/com/RDS/skilltree/aspects/AuthorizedRolesAspect.java new file mode 100644 index 00000000..6505c3c9 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/aspects/AuthorizedRolesAspect.java @@ -0,0 +1,60 @@ +package com.RDS.skilltree.aspects; + +import com.RDS.skilltree.annotations.AuthorizedRoles; +import com.RDS.skilltree.enums.UserRoleEnum; +import com.RDS.skilltree.exceptions.ForbiddenException; +import com.RDS.skilltree.models.JwtUser; +import java.lang.reflect.Method; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class AuthorizedRolesAspect { + + @Around("@within(authorizedRoles) || @annotation(authorizedRoles)") + public Object authorize(ProceedingJoinPoint joinPoint, AuthorizedRoles authorizedRoles) + throws Throwable { + JwtUser jwtDetails = + (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + UserRoleEnum role = jwtDetails.getRole(); + + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + + AuthorizedRoles methodAuthorized = method.getAnnotation(AuthorizedRoles.class); + AuthorizedRoles classAuthorized = targetClass.getAnnotation(AuthorizedRoles.class); + + UserRoleEnum[] allowedRoles = {}; + + if (methodAuthorized != null) { + allowedRoles = methodAuthorized.value(); + } else if (classAuthorized != null) { + allowedRoles = classAuthorized.value(); + } else { + // If no roles are specified, proceed with the method execution + joinPoint.proceed(); + } + + if (!isAuthorized(role, allowedRoles)) { + throw new ForbiddenException("You're not authorized to make this request"); + } + + return joinPoint.proceed(); + } + + private boolean isAuthorized(UserRoleEnum userRole, UserRoleEnum[] allowedRoles) { + for (UserRoleEnum role : allowedRoles) { + if (role.equals(userRole)) { + return true; + } + } + + return false; + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/config/AppConfig.java b/skill-tree/src/main/java/com/RDS/skilltree/config/AppConfig.java new file mode 100644 index 00000000..8a71d39f --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/config/AppConfig.java @@ -0,0 +1,13 @@ +package com.RDS.skilltree.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class AppConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Config/SecurityConfig.java b/skill-tree/src/main/java/com/RDS/skilltree/config/SecurityConfig.java similarity index 73% rename from skill-tree/src/main/java/com/RDS/skilltree/Config/SecurityConfig.java rename to skill-tree/src/main/java/com/RDS/skilltree/config/SecurityConfig.java index ac4fa3dc..520aff3a 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Config/SecurityConfig.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/config/SecurityConfig.java @@ -1,9 +1,9 @@ -package com.RDS.skilltree.Config; +package com.RDS.skilltree.config; import com.RDS.skilltree.Authentication.AuthEntryPoint; import com.RDS.skilltree.Authentication.CustomAccessDeniedHandler; -import com.RDS.skilltree.Filters.JWTAuthenticationFilter; -import com.RDS.skilltree.User.UserRole; +import com.RDS.skilltree.enums.UserRoleEnum; +import com.RDS.skilltree.utils.JWTAuthenticationFilter; import java.util.Arrays; import java.util.List; import org.springframework.context.annotation.Bean; @@ -27,6 +27,8 @@ public class SecurityConfig { private final AuthEntryPoint authEntryPoint; private final CustomAccessDeniedHandler accessDeniedHandler; + public static final List NON_AUTH_ROUTES = List.of("/v1/health", "/v1/auth"); + public SecurityConfig( AuthEntryPoint authEntryPoint, CustomAccessDeniedHandler accessDeniedHandler) { this.authEntryPoint = authEntryPoint; @@ -41,21 +43,18 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(corsConfigurationSource())) .authorizeHttpRequests( - auth -> - auth.requestMatchers("/v1/health") - .permitAll() - .requestMatchers( - request -> - request.getQueryString() != null - && request.getQueryString().contains("dummyData=true")) - .permitAll() - .requestMatchers(HttpMethod.GET, "/v1/**") - .hasAnyAuthority(UserRole.getAllRoles()) // give read-only access to all - .requestMatchers("/v1/**") - .hasAnyAuthority( - UserRole.USER.name(), UserRole.MEMBER.name(), UserRole.SUPERUSER.name()) - .anyRequest() - .authenticated()) + auth -> { + NON_AUTH_ROUTES.forEach(path -> auth.requestMatchers(path + "/**").permitAll()); + auth.requestMatchers(HttpMethod.GET, "/v1/**") + .hasAnyAuthority(UserRoleEnum.getAllRoles()) // give read-only access to all + .requestMatchers("/v1/**") + .hasAnyAuthority( + UserRoleEnum.USER.name(), + UserRoleEnum.MEMBER.name(), + UserRoleEnum.SUPERUSER.name()) + .anyRequest() + .authenticated(); + }) .exceptionHandling( ex -> ex.accessDeniedHandler(this.accessDeniedHandler) diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Config/WebMvcConfig.java b/skill-tree/src/main/java/com/RDS/skilltree/config/WebMvcConfig.java similarity index 96% rename from skill-tree/src/main/java/com/RDS/skilltree/Config/WebMvcConfig.java rename to skill-tree/src/main/java/com/RDS/skilltree/config/WebMvcConfig.java index f02f79ea..6b5a96e0 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Config/WebMvcConfig.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/config/WebMvcConfig.java @@ -1,4 +1,4 @@ -package com.RDS.skilltree.Config; +package com.RDS.skilltree.config; import com.RDS.skilltree.utils.UUIDValidationInterceptor; import org.springframework.beans.factory.annotation.Autowired; diff --git a/skill-tree/src/main/java/com/RDS/skilltree/dtos/CreateEndorsementRequestDto.java b/skill-tree/src/main/java/com/RDS/skilltree/dtos/CreateEndorsementRequestDto.java new file mode 100644 index 00000000..281e09f1 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/dtos/CreateEndorsementRequestDto.java @@ -0,0 +1,15 @@ +package com.RDS.skilltree.dtos; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CreateEndorsementRequestDto { + @NotNull(message = "Message cannot be empty") + private String message; + + @NotNull(message = "user id cannot be null") + private String endorseId; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/dtos/RdsGetUserDetailsResDto.java b/skill-tree/src/main/java/com/RDS/skilltree/dtos/RdsGetUserDetailsResDto.java new file mode 100644 index 00000000..7f2a765a --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/dtos/RdsGetUserDetailsResDto.java @@ -0,0 +1,12 @@ +package com.RDS.skilltree.dtos; + +import com.RDS.skilltree.viewmodels.RdsUserViewModel; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RdsGetUserDetailsResDto { + private String message; + private RdsUserViewModel user; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/dtos/SkillRequestActionRequestDto.java b/skill-tree/src/main/java/com/RDS/skilltree/dtos/SkillRequestActionRequestDto.java new file mode 100644 index 00000000..66cf83e4 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/dtos/SkillRequestActionRequestDto.java @@ -0,0 +1,16 @@ +package com.RDS.skilltree.dtos; + +import com.RDS.skilltree.enums.UserSkillStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SkillRequestActionRequestDto { + @NotNull(message = "user id cannot be null") + private String endorseId; + + @NotNull(message = "Action should not empty") + private UserSkillStatusEnum action; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/dtos/SkillRequestsDto.java b/skill-tree/src/main/java/com/RDS/skilltree/dtos/SkillRequestsDto.java new file mode 100644 index 00000000..cbb3f277 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/dtos/SkillRequestsDto.java @@ -0,0 +1,23 @@ +package com.RDS.skilltree.dtos; + +import com.RDS.skilltree.viewmodels.SkillRequestViewModel; +import com.RDS.skilltree.viewmodels.UserViewModel; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class SkillRequestsDto { + private List requests; + private List users; + + public static SkillRequestsDto toDto( + List skillRequests, List users) { + SkillRequestsDto skillRequestsDto = new SkillRequestsDto(); + skillRequestsDto.setRequests(skillRequests); + skillRequestsDto.setUsers(users); + + return skillRequestsDto; + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/enums/SkillTypeEnum.java b/skill-tree/src/main/java/com/RDS/skilltree/enums/SkillTypeEnum.java new file mode 100644 index 00000000..255de25c --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/enums/SkillTypeEnum.java @@ -0,0 +1,5 @@ +package com.RDS.skilltree.enums; + +public enum SkillTypeEnum { + ATOMIC, +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/User/UserRole.java b/skill-tree/src/main/java/com/RDS/skilltree/enums/UserRoleEnum.java similarity index 50% rename from skill-tree/src/main/java/com/RDS/skilltree/User/UserRole.java rename to skill-tree/src/main/java/com/RDS/skilltree/enums/UserRoleEnum.java index 04e1adaa..19537b69 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/User/UserRole.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/enums/UserRoleEnum.java @@ -1,8 +1,8 @@ -package com.RDS.skilltree.User; +package com.RDS.skilltree.enums; import java.util.Arrays; -public enum UserRole { +public enum UserRoleEnum { USER("user"), MEMBER("member"), SUPERUSER("super_user"), @@ -10,20 +10,20 @@ public enum UserRole { public final String label; - UserRole(String label) { + UserRoleEnum(String label) { this.label = label; } - public static UserRole fromString(String text) { - for (UserRole role : UserRole.values()) { + public static UserRoleEnum fromString(String text) { + for (UserRoleEnum role : UserRoleEnum.values()) { if (role.label.equalsIgnoreCase(text)) { return role; } } - return UserRole.GUEST; + return UserRoleEnum.GUEST; } public static String[] getAllRoles() { - return Arrays.stream(UserRole.values()).map(UserRole::name).toArray(String[]::new); + return Arrays.stream(UserRoleEnum.values()).map(UserRoleEnum::name).toArray(String[]::new); } } diff --git a/skill-tree/src/main/java/com/RDS/skilltree/enums/UserSkillStatusEnum.java b/skill-tree/src/main/java/com/RDS/skilltree/enums/UserSkillStatusEnum.java new file mode 100644 index 00000000..caa48ac7 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/enums/UserSkillStatusEnum.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.enums; + +public enum UserSkillStatusEnum { + APPROVED, + REJECTED, + PENDING +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EndorsementNotFoundException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EndorsementNotFoundException.java new file mode 100644 index 00000000..15790383 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EndorsementNotFoundException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class EndorsementNotFoundException extends RuntimeException { + public EndorsementNotFoundException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Exceptions/EntityAlreadyExistsException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EntityAlreadyExistsException.java similarity index 80% rename from skill-tree/src/main/java/com/RDS/skilltree/Exceptions/EntityAlreadyExistsException.java rename to skill-tree/src/main/java/com/RDS/skilltree/exceptions/EntityAlreadyExistsException.java index a013a043..3fc9a5d7 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Exceptions/EntityAlreadyExistsException.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EntityAlreadyExistsException.java @@ -1,4 +1,4 @@ -package com.RDS.skilltree.Exceptions; +package com.RDS.skilltree.exceptions; public class EntityAlreadyExistsException extends RuntimeException { public EntityAlreadyExistsException(String message) { diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/ForbiddenException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/ForbiddenException.java new file mode 100644 index 00000000..47b3c385 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/ForbiddenException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/utils/GlobalExceptionHandler.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java similarity index 59% rename from skill-tree/src/main/java/com/RDS/skilltree/utils/GlobalExceptionHandler.java rename to skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java index 35cfff7e..e5abe97c 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/utils/GlobalExceptionHandler.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java @@ -1,11 +1,11 @@ -package com.RDS.skilltree.utils; +package com.RDS.skilltree.exceptions; -import com.RDS.skilltree.Common.Response.GenericResponse; -import com.RDS.skilltree.Exceptions.EntityAlreadyExistsException; -import com.RDS.skilltree.Exceptions.InvalidParameterException; -import com.RDS.skilltree.Exceptions.NoEntityException; +import com.RDS.skilltree.utils.GenericResponse; import jakarta.validation.ConstraintViolationException; +import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.tomcat.websocket.AuthenticationException; import org.springframework.http.HttpStatus; @@ -16,9 +16,10 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; -@ControllerAdvice @Slf4j +@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler({NoEntityException.class}) public ResponseEntity> handleNoEntityException(NoEntityException ex) { @@ -95,4 +96,62 @@ public ResponseEntity> handleException(ConstraintViolati return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new GenericResponse<>(null, ex.getMessage())); } + + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handleUserNotFoundException( + UserNotFoundException ex, WebRequest request) { + log.error("Exception - Error : {}", ex.getMessage(), ex); + return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(SkillAlreadyExistsException.class) + public ResponseEntity handleSkillAlreadyExistsException( + SkillAlreadyExistsException ex, WebRequest request) { + log.error("Exception - Error : {}", ex.getMessage(), ex); + return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.CONFLICT); + } + + @ExceptionHandler(SelfEndorsementNotAllowedException.class) + public ResponseEntity handleSelfEndorsementNotAllowedException( + SelfEndorsementNotAllowedException ex, WebRequest request) { + log.error("Exception - Error : {}", ex.getMessage(), ex); + return new ResponseEntity<>( + new GenericResponse<>(null, ex.getMessage()), HttpStatus.METHOD_NOT_ALLOWED); + } + + @ExceptionHandler(SkillNotFoundException.class) + public ResponseEntity handleSkillNotFoundException( + SkillNotFoundException ex, WebRequest request) { + log.error("Exception - Error : {}", ex.getMessage(), ex); + return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(EndorsementNotFoundException.class) + public ResponseEntity handleEndorsementNotException( + EndorsementNotFoundException ex, WebRequest request) { + log.error("Exception - Error : {}", ex.getMessage(), ex); + return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity handleForbiddenException(ForbiddenException ex, WebRequest request) { + log.error("Exception - Error : {}", ex.getMessage(), ex); + return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(InternalServerErrorException.class) + public ResponseEntity handleInternalServerErrorException( + InternalServerErrorException ex, WebRequest request) { + log.error("Internal Server Error", ex); + // Create a more specific error message based on the exception type or cause + String errorMessage = "An unexpected error occurred."; + + // Consider adding more details to the response for debugging + Map errorDetails = new HashMap<>(); + errorDetails.put("timestamp", LocalDateTime.now()); + errorDetails.put("message", errorMessage); + errorDetails.put("details", ex.getMessage()); // Include exception details for debugging + + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); + } } diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/InternalServerErrorException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/InternalServerErrorException.java new file mode 100644 index 00000000..1bdafb12 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/InternalServerErrorException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class InternalServerErrorException extends RuntimeException { + public InternalServerErrorException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Exceptions/InvalidParameterException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/InvalidParameterException.java similarity index 85% rename from skill-tree/src/main/java/com/RDS/skilltree/Exceptions/InvalidParameterException.java rename to skill-tree/src/main/java/com/RDS/skilltree/exceptions/InvalidParameterException.java index fda72a6d..0e5bd832 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Exceptions/InvalidParameterException.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/InvalidParameterException.java @@ -1,4 +1,4 @@ -package com.RDS.skilltree.Exceptions; +package com.RDS.skilltree.exceptions; public class InvalidParameterException extends IllegalArgumentException { public InvalidParameterException(String parameterName, String message) { diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Exceptions/NoEntityException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/NoEntityException.java similarity index 89% rename from skill-tree/src/main/java/com/RDS/skilltree/Exceptions/NoEntityException.java rename to skill-tree/src/main/java/com/RDS/skilltree/exceptions/NoEntityException.java index 0b8685bc..16143d89 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Exceptions/NoEntityException.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/NoEntityException.java @@ -1,4 +1,4 @@ -package com.RDS.skilltree.Exceptions; +package com.RDS.skilltree.exceptions; public class NoEntityException extends RuntimeException { diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SelfEndorsementNotAllowedException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SelfEndorsementNotAllowedException.java new file mode 100644 index 00000000..67d81250 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SelfEndorsementNotAllowedException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class SelfEndorsementNotAllowedException extends RuntimeException { + public SelfEndorsementNotAllowedException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SkillAlreadyExistsException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SkillAlreadyExistsException.java new file mode 100644 index 00000000..16322344 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SkillAlreadyExistsException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class SkillAlreadyExistsException extends RuntimeException { + public SkillAlreadyExistsException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SkillNotFoundException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SkillNotFoundException.java new file mode 100644 index 00000000..0a124fd0 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/SkillNotFoundException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class SkillNotFoundException extends RuntimeException { + public SkillNotFoundException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/UserNotFoundException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/UserNotFoundException.java new file mode 100644 index 00000000..6c7fc7fc --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/models/Endorsement.java b/skill-tree/src/main/java/com/RDS/skilltree/models/Endorsement.java new file mode 100644 index 00000000..91698288 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/models/Endorsement.java @@ -0,0 +1,32 @@ +package com.RDS.skilltree.models; + +import com.RDS.skilltree.utils.TrackedProperties; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "endorsements") +public class Endorsement extends TrackedProperties { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Integer id; + + @ManyToOne + @JoinColumn(name = "skill_id", referencedColumnName = "id") + private Skill skill; + + @Column(name = "endorse_id", nullable = false) + private String endorseId; + + @Column(name = "endorser_id", nullable = false) + private String endorserId; + + @Column(name = "message", nullable = false, columnDefinition = "TEXT") + private String message; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/models/JwtUser.java b/skill-tree/src/main/java/com/RDS/skilltree/models/JwtUser.java new file mode 100644 index 00000000..5ec4e5fc --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/models/JwtUser.java @@ -0,0 +1,15 @@ +package com.RDS.skilltree.models; + +import com.RDS.skilltree.enums.UserRoleEnum; +import lombok.Getter; + +@Getter +public class JwtUser { + private final String rdsUserId; + private final UserRoleEnum role; + + public JwtUser(String rdsUserId, UserRoleEnum role) { + this.role = role; + this.rdsUserId = rdsUserId; + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/models/Skill.java b/skill-tree/src/main/java/com/RDS/skilltree/models/Skill.java new file mode 100644 index 00000000..1a543270 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/models/Skill.java @@ -0,0 +1,33 @@ +package com.RDS.skilltree.models; + +import com.RDS.skilltree.enums.SkillTypeEnum; +import com.RDS.skilltree.utils.TrackedProperties; +import jakarta.persistence.*; +import lombok.*; + +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Builder +@Getter +@Setter +@Table(name = "skills") +public class Skill extends TrackedProperties { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Integer id; + + @Column(name = "name", unique = true, nullable = false) + private String name; + + @Column(name = "skill_type", nullable = false) + @Enumerated(value = EnumType.STRING) + private SkillTypeEnum type = SkillTypeEnum.ATOMIC; + + @Column(name = "created_by", nullable = false) + private String createdBy; + + @Column(name = "updated_by") + private String updateBy; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/models/UserSkills.java b/skill-tree/src/main/java/com/RDS/skilltree/models/UserSkills.java new file mode 100644 index 00000000..6f32579f --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/models/UserSkills.java @@ -0,0 +1,33 @@ +package com.RDS.skilltree.models; + +import com.RDS.skilltree.enums.UserSkillStatusEnum; +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "user_skills") +public class UserSkills { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private String id; + + @Column(name = "user_id", nullable = false) + private String userId; + + @JsonBackReference + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "skill_id", nullable = false) + private Skill skill; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + @Builder.Default + private UserSkillStatusEnum status = UserSkillStatusEnum.PENDING; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/repositories/EndorsementRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/repositories/EndorsementRepository.java new file mode 100644 index 00000000..9b8d3c2f --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/repositories/EndorsementRepository.java @@ -0,0 +1,11 @@ +package com.RDS.skilltree.repositories; + +import com.RDS.skilltree.models.Endorsement; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EndorsementRepository extends JpaRepository { + List findBySkillId(Integer skillId); + + List findByEndorseIdAndSkillId(String endorseId, Integer skillId); +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/repositories/SkillRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/repositories/SkillRepository.java new file mode 100644 index 00000000..8f005d92 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/repositories/SkillRepository.java @@ -0,0 +1,10 @@ +package com.RDS.skilltree.repositories; + +import com.RDS.skilltree.models.Skill; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SkillRepository extends JpaRepository { + boolean existsByName(String name); +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/repositories/UserSkillRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/repositories/UserSkillRepository.java new file mode 100644 index 00000000..12868976 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/repositories/UserSkillRepository.java @@ -0,0 +1,20 @@ +package com.RDS.skilltree.repositories; + +import com.RDS.skilltree.enums.UserSkillStatusEnum; +import com.RDS.skilltree.models.UserSkills; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface UserSkillRepository extends JpaRepository { + List findByStatus(UserSkillStatusEnum status); + + List findByUserIdAndSkillId(String userId, Integer skillId); + + @Query( + "SELECT us FROM UserSkills us " + + "JOIN Endorsement e ON us.userId = e.endorseId " + + "WHERE e.endorserId = :endorserId") + List findUserSkillsByEndorserId(@Param("endorserId") String endorserId); +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementService.java b/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementService.java new file mode 100644 index 00000000..36ca6653 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementService.java @@ -0,0 +1,14 @@ +package com.RDS.skilltree.services; + +import com.RDS.skilltree.viewmodels.CreateEndorsementViewModel; +import com.RDS.skilltree.viewmodels.EndorsementViewModel; +import com.RDS.skilltree.viewmodels.UpdateEndorsementViewModel; +import java.util.List; + +public interface EndorsementService { + List getAllEndorsementsBySkillId(Integer skillId); + + EndorsementViewModel create(CreateEndorsementViewModel endorsement); + + EndorsementViewModel update(Integer endorsementId, UpdateEndorsementViewModel endorsement); +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementServiceImplementation.java b/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementServiceImplementation.java new file mode 100644 index 00000000..b5fd5a10 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementServiceImplementation.java @@ -0,0 +1,149 @@ +package com.RDS.skilltree.services; + +import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto; +import com.RDS.skilltree.exceptions.EndorsementNotFoundException; +import com.RDS.skilltree.exceptions.SelfEndorsementNotAllowedException; +import com.RDS.skilltree.exceptions.SkillNotFoundException; +import com.RDS.skilltree.models.Endorsement; +import com.RDS.skilltree.models.JwtUser; +import com.RDS.skilltree.models.Skill; +import com.RDS.skilltree.models.UserSkills; +import com.RDS.skilltree.repositories.EndorsementRepository; +import com.RDS.skilltree.repositories.SkillRepository; +import com.RDS.skilltree.repositories.UserSkillRepository; +import com.RDS.skilltree.services.external.RdsService; +import com.RDS.skilltree.viewmodels.CreateEndorsementViewModel; +import com.RDS.skilltree.viewmodels.EndorsementViewModel; +import com.RDS.skilltree.viewmodels.UpdateEndorsementViewModel; +import com.RDS.skilltree.viewmodels.UserViewModel; +import java.util.*; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class EndorsementServiceImplementation implements EndorsementService { + private static final Logger log = LoggerFactory.getLogger(EndorsementServiceImplementation.class); + private final RdsService rdsService; + private final SkillRepository skillRepository; + private final EndorsementRepository endorsementRepository; + private final UserSkillRepository userSkillRepository; + + @Override + public List getAllEndorsementsBySkillId(Integer skillId) { + List endorsements = endorsementRepository.findBySkillId(skillId); + + // store all users data that are a part of this request + Map userDetails = new HashMap<>(); + + return endorsements.stream() + .map( + endorsement -> { + String endorseId = endorsement.getEndorseId(); + String endorserId = endorsement.getEndorserId(); + + if (!userDetails.containsKey(endorseId)) { + RdsGetUserDetailsResDto endorseDetails = + rdsService.getUserDetails(endorsement.getEndorseId()); + userDetails.put(endorseId, UserViewModel.toViewModel(endorseDetails.getUser())); + } + + if (!userDetails.containsKey(endorserId)) { + RdsGetUserDetailsResDto endorserDetails = rdsService.getUserDetails(endorserId); + userDetails.put(endorserId, UserViewModel.toViewModel(endorserDetails.getUser())); + } + + return EndorsementViewModel.toViewModel( + endorsement, userDetails.get(endorseId), userDetails.get(endorserId)); + }) + .toList(); + } + + @Override + // TODO : add a check for when a endorsement is already created by a user for a particular skill. + public EndorsementViewModel create(CreateEndorsementViewModel endorsementViewModel) { + String message = endorsementViewModel.getMessage(); + Integer skillId = endorsementViewModel.getSkillId(); + String endorseId = endorsementViewModel.getEndorseId(); + + JwtUser jwtDetails = + (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + String endorserId = jwtDetails.getRdsUserId(); + + if (Objects.equals(endorseId, endorserId)) { + log.info( + "Self endorsement not allowed, endorseId: {}, endorserId: {}", endorseId, endorserId); + throw new SelfEndorsementNotAllowedException("Self endorsement not allowed"); + } + + Optional skillDetails = skillRepository.findById(skillId); + + if (skillDetails.isEmpty()) { + log.info(String.format("Skill id: %s not found", skillId)); + throw new SkillNotFoundException("Skill does not exist"); + } + + // Get endorse(person being endorsed) & endorser(person endorsing) details from RDS + RdsGetUserDetailsResDto endorseDetails = rdsService.getUserDetails(endorseId); + RdsGetUserDetailsResDto endorserDetails = rdsService.getUserDetails(endorserId); + + List userSkillEntry = + userSkillRepository.findByUserIdAndSkillId(endorseId, skillId); + + Endorsement endorsement = + Endorsement.builder() + .skill(skillDetails.get()) + .message(message) + .endorseId(endorseId) + .endorserId(endorserId) + .build(); + + // If a skill request is not created then create one + // This is because there is no specific api to create a skill request at the time of writing + if (userSkillEntry.isEmpty()) { + UserSkills userSkills = + UserSkills.builder().userId(endorseId).skill(skillDetails.get()).build(); + + userSkillRepository.save(userSkills); + } + + Endorsement newEndorsement = endorsementRepository.save(endorsement); + + return EndorsementViewModel.toViewModel( + newEndorsement, + UserViewModel.toViewModel(endorseDetails.getUser()), + UserViewModel.toViewModel(endorserDetails.getUser())); + } + + @Override + public EndorsementViewModel update(Integer endorsementId, UpdateEndorsementViewModel body) { + Optional exitingEndorsement = endorsementRepository.findById(endorsementId); + + if (exitingEndorsement.isEmpty()) { + log.info(String.format("Endorsement with id: %s not found", endorsementId)); + throw new EndorsementNotFoundException("Endorsement not found"); + } + + Endorsement endorsement = exitingEndorsement.get(); + String updatedMessage = body.getMessage(); + + if (updatedMessage != null) { + endorsement.setMessage(updatedMessage); + } + + Endorsement savedEndorsementDetails = endorsementRepository.save(endorsement); + RdsGetUserDetailsResDto endorseDetails = + rdsService.getUserDetails(savedEndorsementDetails.getEndorseId()); + RdsGetUserDetailsResDto endorserDetails = + rdsService.getUserDetails(savedEndorsementDetails.getEndorserId()); + + return EndorsementViewModel.toViewModel( + savedEndorsementDetails, + UserViewModel.toViewModel(endorseDetails.getUser()), + UserViewModel.toViewModel(endorserDetails.getUser())); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/MetricService.java b/skill-tree/src/main/java/com/RDS/skilltree/services/MetricService.java new file mode 100644 index 00000000..2670a51a --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/MetricService.java @@ -0,0 +1,5 @@ +package com.RDS.skilltree.services; + +public interface MetricService { + Double getUptime(); +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/metrics/MetricService.java b/skill-tree/src/main/java/com/RDS/skilltree/services/MetricServiceImplementation.java similarity index 57% rename from skill-tree/src/main/java/com/RDS/skilltree/metrics/MetricService.java rename to skill-tree/src/main/java/com/RDS/skilltree/services/MetricServiceImplementation.java index 60c86997..67df4a78 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/metrics/MetricService.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/MetricServiceImplementation.java @@ -1,20 +1,16 @@ -package com.RDS.skilltree.metrics; +package com.RDS.skilltree.services; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.boot.actuate.metrics.MetricsEndpoint; import org.springframework.stereotype.Service; @Service -public class MetricService { - +@RequiredArgsConstructor +public class MetricServiceImplementation implements MetricService { private final MetricsEndpoint metricsEndpoint; - @Autowired - public MetricService(MetricsEndpoint metricsEndpoint) { - this.metricsEndpoint = metricsEndpoint; - } - - public double getUptime() { + @Override + public Double getUptime() { return metricsEndpoint.metric("process.uptime", null).getMeasurements().stream() .findFirst() .map(MetricsEndpoint.Sample::getValue) diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/SkillService.java b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillService.java new file mode 100644 index 00000000..ce87d119 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillService.java @@ -0,0 +1,21 @@ +package com.RDS.skilltree.services; + +import com.RDS.skilltree.dtos.SkillRequestsDto; +import com.RDS.skilltree.enums.UserSkillStatusEnum; +import com.RDS.skilltree.utils.GenericResponse; +import com.RDS.skilltree.viewmodels.CreateSkillViewModel; +import com.RDS.skilltree.viewmodels.SkillViewModel; +import java.util.List; + +public interface SkillService { + List getAll(); + + SkillViewModel create(CreateSkillViewModel skill); + + SkillRequestsDto getAllRequests(); + + SkillRequestsDto getRequestsByStatus(UserSkillStatusEnum status); + + GenericResponse approveRejectSkillRequest( + Integer skillId, String endorseId, UserSkillStatusEnum action); +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/SkillServiceImplementation.java b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillServiceImplementation.java new file mode 100644 index 00000000..153ea33a --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillServiceImplementation.java @@ -0,0 +1,183 @@ +package com.RDS.skilltree.services; + +import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto; +import com.RDS.skilltree.dtos.SkillRequestsDto; +import com.RDS.skilltree.enums.UserSkillStatusEnum; +import com.RDS.skilltree.exceptions.InternalServerErrorException; +import com.RDS.skilltree.exceptions.NoEntityException; +import com.RDS.skilltree.exceptions.SkillAlreadyExistsException; +import com.RDS.skilltree.models.Endorsement; +import com.RDS.skilltree.models.JwtUser; +import com.RDS.skilltree.models.Skill; +import com.RDS.skilltree.models.UserSkills; +import com.RDS.skilltree.repositories.EndorsementRepository; +import com.RDS.skilltree.repositories.SkillRepository; +import com.RDS.skilltree.repositories.UserSkillRepository; +import com.RDS.skilltree.services.external.RdsService; +import com.RDS.skilltree.utils.GenericResponse; +import com.RDS.skilltree.viewmodels.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class SkillServiceImplementation implements SkillService { + private static final Logger log = LoggerFactory.getLogger(SkillServiceImplementation.class); + private final RdsService rdsService; + private final SkillRepository skillRepository; + private final UserSkillRepository userSkillRepository; + private final EndorsementRepository endorsementRepository; + + private static UserViewModel getUserModalFromRdsDetails( + String id, RdsGetUserDetailsResDto rdsDetails) { + String firstName = + rdsDetails.getUser().getFirst_name() != null ? rdsDetails.getUser().getFirst_name() : ""; + String lastName = + rdsDetails.getUser().getLast_name() != null ? rdsDetails.getUser().getLast_name() : ""; + + return UserViewModel.builder().id(id).name(firstName + ' ' + lastName).build(); + } + + @Override + public List getAll() { + return skillRepository.findAll().stream() + .map(SkillViewModel::toViewModel) + .collect(Collectors.toList()); + } + + @Override + public SkillRequestsDto getAllRequests() { + JwtUser jwtDetails = + (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + RdsGetUserDetailsResDto userDetails = rdsService.getUserDetails(jwtDetails.getRdsUserId()); + RdsUserViewModel.Roles userRole = userDetails.getUser().getRoles(); + String userId = userDetails.getUser().getId(); + + List skillRequests = null; + + if (userRole.isSuper_user()) { + skillRequests = userSkillRepository.findAll(); + } else { + skillRequests = userSkillRepository.findUserSkillsByEndorserId(userId); + } + + if (skillRequests == null) { + throw new InternalServerErrorException("Unable to fetch skill requests"); + } + + SkillRequestsWithUserDetailsViewModel skillRequestsWithUserDetails = + toSkillRequestsWithUserDetailsViewModel(skillRequests); + + return SkillRequestsDto.toDto( + skillRequestsWithUserDetails.getSkillRequests(), skillRequestsWithUserDetails.getUsers()); + } + + @Override + public SkillRequestsDto getRequestsByStatus(UserSkillStatusEnum status) { + List skillRequests = userSkillRepository.findByStatus(status); + SkillRequestsWithUserDetailsViewModel skillRequestsWithUserDetails = + toSkillRequestsWithUserDetailsViewModel(skillRequests); + + return SkillRequestsDto.toDto( + skillRequestsWithUserDetails.getSkillRequests(), skillRequestsWithUserDetails.getUsers()); + } + + @Override + public SkillViewModel create(CreateSkillViewModel skill) { + if (skillRepository.existsByName(skill.getName())) { + throw new SkillAlreadyExistsException( + String.format("Skill with name %s already exists", skill.getName())); + } + + JwtUser jwtDetails = + (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + RdsGetUserDetailsResDto userDetails = rdsService.getUserDetails(jwtDetails.getRdsUserId()); + + Skill newSkill = toEntity(skill); + newSkill.setCreatedBy(userDetails.getUser().getId()); + + return SkillViewModel.toViewModel(skillRepository.saveAndFlush(newSkill)); + } + + @Override + public GenericResponse approveRejectSkillRequest( + Integer skillId, String endorseId, UserSkillStatusEnum action) { + List existingSkillRequest = + userSkillRepository.findByUserIdAndSkillId(endorseId, skillId); + + if (existingSkillRequest.isEmpty()) { + log.info("Skill request not found! endorseId: {} and skillId: {}", endorseId, skillId); + throw new NoEntityException("Skill request not found"); + } + + UserSkills updatedSkillRequest = existingSkillRequest.get(0); + updatedSkillRequest.setStatus(action); + + userSkillRepository.save(updatedSkillRequest); + return new GenericResponse<>("Skill {}", action.toString().toLowerCase()); + } + + private SkillRequestsWithUserDetailsViewModel toSkillRequestsWithUserDetailsViewModel( + List skills) { + // store all users data that are a part of this request + Map userDetails = new HashMap<>(); + + List skillRequests = + skills.stream() + .map( + skill -> { + Integer skillId = skill.getSkill().getId(); + + String endorseId = skill.getUserId(); + + // Get all endorsement for a specific skill and user Id + List endorsements = + endorsementRepository.findByEndorseIdAndSkillId(endorseId, skillId); + + if (!userDetails.containsKey(endorseId)) { + RdsGetUserDetailsResDto endorseRdsDetails = + rdsService.getUserDetails(endorseId); + UserViewModel endorseDetails = + getUserModalFromRdsDetails(endorseId, endorseRdsDetails); + userDetails.put(endorseId, endorseDetails); + } + + endorsements.forEach( + endorsement -> { + String endorserId = endorsement.getEndorserId(); + + if (!userDetails.containsKey(endorserId)) { + RdsGetUserDetailsResDto endorserRdsDetails = + rdsService.getUserDetails(endorserId); + UserViewModel endorserDetails = + getUserModalFromRdsDetails(endorserId, endorserRdsDetails); + userDetails.put(endorserId, endorserDetails); + } + }); + + return SkillRequestViewModel.toViewModel(skill, endorsements); + }) + .toList(); + + return SkillRequestsWithUserDetailsViewModel.builder() + .skillRequests(skillRequests) + .users(userDetails.values().stream().toList()) + .build(); + } + + private Skill toEntity(CreateSkillViewModel viewModel) { + Skill entity = new Skill(); + BeanUtils.copyProperties(viewModel, entity); + return entity; + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/external/RdsService.java b/skill-tree/src/main/java/com/RDS/skilltree/services/external/RdsService.java new file mode 100644 index 00000000..191b4eb0 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/external/RdsService.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.services.external; + +import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto; + +public interface RdsService { + RdsGetUserDetailsResDto getUserDetails(String id); +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/external/RdsServiceImplementation.java b/skill-tree/src/main/java/com/RDS/skilltree/services/external/RdsServiceImplementation.java new file mode 100644 index 00000000..8bbb98db --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/services/external/RdsServiceImplementation.java @@ -0,0 +1,32 @@ +package com.RDS.skilltree.services.external; + +import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto; +import com.RDS.skilltree.exceptions.UserNotFoundException; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +@Service +@RequiredArgsConstructor +public class RdsServiceImplementation implements RdsService { + private static final Logger log = LoggerFactory.getLogger(RdsServiceImplementation.class); + private final RestTemplate restTemplate; + + @Value("${rds.backendBaseUrl}") + private String rdsBackendBaseUrl; + + @Override + public RdsGetUserDetailsResDto getUserDetails(String id) { + String url = rdsBackendBaseUrl + "/users?id=" + id; + try { + return restTemplate.getForObject(url, RdsGetUserDetailsResDto.class); + } catch (RestClientException error) { + log.error("Error calling url {}, error: {}", url, error.getMessage()); + throw new UserNotFoundException("Error getting user details"); + } + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Common/Response/GenericResponse.java b/skill-tree/src/main/java/com/RDS/skilltree/utils/GenericResponse.java similarity index 89% rename from skill-tree/src/main/java/com/RDS/skilltree/Common/Response/GenericResponse.java rename to skill-tree/src/main/java/com/RDS/skilltree/utils/GenericResponse.java index ee31959f..70d73df3 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Common/Response/GenericResponse.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/utils/GenericResponse.java @@ -1,4 +1,4 @@ -package com.RDS.skilltree.Common.Response; +package com.RDS.skilltree.utils; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; diff --git a/skill-tree/src/main/java/com/RDS/skilltree/Filters/JWTAuthenticationFilter.java b/skill-tree/src/main/java/com/RDS/skilltree/utils/JWTAuthenticationFilter.java similarity index 88% rename from skill-tree/src/main/java/com/RDS/skilltree/Filters/JWTAuthenticationFilter.java rename to skill-tree/src/main/java/com/RDS/skilltree/utils/JWTAuthenticationFilter.java index 5392978e..0e130899 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/Filters/JWTAuthenticationFilter.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/utils/JWTAuthenticationFilter.java @@ -1,7 +1,7 @@ -package com.RDS.skilltree.Filters; +package com.RDS.skilltree.utils; import com.RDS.skilltree.Authentication.UserAuthenticationToken; -import com.RDS.skilltree.utils.JWTUtils; +import com.RDS.skilltree.config.SecurityConfig; import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -49,6 +49,12 @@ public void doFilterInternal( filterChain.doFilter(request, response); } + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String path = request.getRequestURI(); + return SecurityConfig.NON_AUTH_ROUTES.stream().anyMatch(path::startsWith); + } + public String getJWTFromRequest(HttpServletRequest request) { /* check for cookie */ diff --git a/skill-tree/src/main/java/com/RDS/skilltree/utils/TrackedProperties.java b/skill-tree/src/main/java/com/RDS/skilltree/utils/TrackedProperties.java index 23601457..d11dd1e1 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/utils/TrackedProperties.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/utils/TrackedProperties.java @@ -3,22 +3,25 @@ import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; import java.time.Instant; -import java.util.UUID; import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.SourceType; +import org.hibernate.annotations.UpdateTimestamp; @Data @MappedSuperclass public abstract class TrackedProperties { - - @Column(name = "created_by") - private UUID createdBy; - - @Column(name = "updated_by") - private UUID updatedBy; - - @Column(name = "created_at") + @CreationTimestamp(source = SourceType.DB) + @Column(name = "created_at", nullable = false, updatable = false) private Instant createdAt; + @UpdateTimestamp(source = SourceType.DB) @Column(name = "updated_at") private Instant updatedAt; + + @Column(name = "is_deleted", nullable = false, columnDefinition = "boolean default false") + private boolean isDeleted; + + @Column(name = "deleted_at") + private Instant deletedAt; } diff --git a/skill-tree/src/main/java/com/RDS/skilltree/utils/UUIDValidationInterceptor.java b/skill-tree/src/main/java/com/RDS/skilltree/utils/UUIDValidationInterceptor.java index cccc1d5b..06393977 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/utils/UUIDValidationInterceptor.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/utils/UUIDValidationInterceptor.java @@ -1,6 +1,6 @@ package com.RDS.skilltree.utils; -import com.RDS.skilltree.Exceptions.InvalidParameterException; +import com.RDS.skilltree.exceptions.InvalidParameterException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/AuthSuccessViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/AuthSuccessViewModel.java new file mode 100644 index 00000000..1d8a9ea2 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/AuthSuccessViewModel.java @@ -0,0 +1,12 @@ +package com.RDS.skilltree.viewmodels; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AuthSuccessViewModel { + @NotNull(message = "Message cannot be empty") + private String message; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/CreateEndorsementViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/CreateEndorsementViewModel.java new file mode 100644 index 00000000..b68fd472 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/CreateEndorsementViewModel.java @@ -0,0 +1,24 @@ +package com.RDS.skilltree.viewmodels; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class CreateEndorsementViewModel { + private Integer skillId; + private String endorseId; + private String message; + + public static CreateEndorsementViewModel toViewModel( + Integer skillId, String endorseId, String message) { + + return CreateEndorsementViewModel.builder() + .skillId(skillId) + .endorseId(endorseId) + .message(message) + .build(); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/CreateSkillViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/CreateSkillViewModel.java new file mode 100644 index 00000000..b02a96be --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/CreateSkillViewModel.java @@ -0,0 +1,18 @@ +package com.RDS.skilltree.viewmodels; + +import com.RDS.skilltree.enums.SkillTypeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CreateSkillViewModel { + @NotNull(message = "Name cannot be empty") + private String name; + + @NotNull(message = "SkillType cannot be empty") + private SkillTypeEnum type; + + private String createdBy; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/EndorsementViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/EndorsementViewModel.java new file mode 100644 index 00000000..bfa7d1a9 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/EndorsementViewModel.java @@ -0,0 +1,29 @@ +package com.RDS.skilltree.viewmodels; + +import com.RDS.skilltree.models.Endorsement; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class EndorsementViewModel { + private Integer id; + private SkillViewModel skill; + private UserViewModel endorse; + private UserViewModel endorser; + private String message; + + public static EndorsementViewModel toViewModel( + Endorsement endorsement, UserViewModel endorse, UserViewModel endorser) { + + return EndorsementViewModel.builder() + .id(endorsement.getId()) + .skill(SkillViewModel.toViewModel((endorsement.getSkill()))) + .endorse(endorse) + .endorser(endorser) + .message(endorsement.getMessage()) + .build(); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/MinimalEndorsementViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/MinimalEndorsementViewModel.java new file mode 100644 index 00000000..fb5b9b3a --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/MinimalEndorsementViewModel.java @@ -0,0 +1,23 @@ +package com.RDS.skilltree.viewmodels; + +import com.RDS.skilltree.models.Endorsement; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class MinimalEndorsementViewModel { + private Integer id; + private String endorserId; + private String endorsementDate; + private String message; + + public static MinimalEndorsementViewModel toViewModel(Endorsement endorsement) { + return MinimalEndorsementViewModel.builder() + .id(endorsement.getId()) + .message(endorsement.getMessage()) + .endorserId(endorsement.getEndorserId()) + .endorsementDate(endorsement.getCreatedAt().toString()) + .build(); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/RdsUserViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/RdsUserViewModel.java new file mode 100644 index 00000000..6110042a --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/RdsUserViewModel.java @@ -0,0 +1,49 @@ +package com.RDS.skilltree.viewmodels; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RdsUserViewModel { + private String id; + private boolean incomplete_user_details; + private String discord_joined_at; + private String discord_id; + private Roles roles; + private String linkedin_id; + private Picture picture; + private float yoe; + private long github_created_at; + private String github_display_name; + private String github_id; + private String twitter_id; + private String username; + private String github_user_id; + private String first_name; + private String profile_url; + private String website; + private String last_name; + private String company; + private String designation; + private String instagram_id; + private String profile_status; + private long updated_at; + private long created_at; + + @Getter + @Setter + public static class Roles { + private boolean archived; + private boolean in_discord; + private boolean member; + private boolean super_user; + } + + @Getter + @Setter + public static class Picture { + private String url; + private String public_id; + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillRequestViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillRequestViewModel.java new file mode 100644 index 00000000..8c54d541 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillRequestViewModel.java @@ -0,0 +1,47 @@ +package com.RDS.skilltree.viewmodels; + +import com.RDS.skilltree.enums.UserSkillStatusEnum; +import com.RDS.skilltree.models.Endorsement; +import com.RDS.skilltree.models.Skill; +import com.RDS.skilltree.models.UserSkills; +import java.util.List; +import java.util.stream.Collectors; +import lombok.Data; + +@Data +public class SkillRequestViewModel { + private Integer skillId; + private String skillName; + private String endorseId; + private UserSkillStatusEnum status; + private List endorsements; + + public SkillRequestViewModel( + Integer id, + String name, + String endorseId, + List endorsements, + UserSkillStatusEnum status) { + this.skillId = id; + this.skillName = name; + this.endorseId = endorseId; + this.endorsements = endorsements; + this.status = status; + } + + public static SkillRequestViewModel toViewModel( + UserSkills userSkills, List endorsements) { + Skill skill = userSkills.getSkill(); + String userId = userSkills.getUserId(); + UserSkillStatusEnum status = userSkills.getStatus(); + + return new SkillRequestViewModel( + skill.getId(), + skill.getName(), + userId, + endorsements.stream() + .map(MinimalEndorsementViewModel::toViewModel) + .collect(Collectors.toList()), + status); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillRequestsWithUserDetailsViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillRequestsWithUserDetailsViewModel.java new file mode 100644 index 00000000..c51de05e --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillRequestsWithUserDetailsViewModel.java @@ -0,0 +1,12 @@ +package com.RDS.skilltree.viewmodels; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class SkillRequestsWithUserDetailsViewModel { + private List skillRequests; + private List users; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillViewModel.java new file mode 100644 index 00000000..6b9321e6 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/SkillViewModel.java @@ -0,0 +1,25 @@ +package com.RDS.skilltree.viewmodels; + +import com.RDS.skilltree.enums.SkillTypeEnum; +import com.RDS.skilltree.models.Skill; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.BeanUtils; + +@Getter +@Setter +public class SkillViewModel { + private Integer id; + private String name; + private SkillTypeEnum type = SkillTypeEnum.ATOMIC; + + public static SkillViewModel toViewModel(Skill skill) { + if (skill == null) { + return null; + } + + SkillViewModel viewModel = new SkillViewModel(); + BeanUtils.copyProperties(skill, viewModel); + return viewModel; + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/UpdateEndorsementViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/UpdateEndorsementViewModel.java new file mode 100644 index 00000000..0b1685a7 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/UpdateEndorsementViewModel.java @@ -0,0 +1,12 @@ +package com.RDS.skilltree.viewmodels; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UpdateEndorsementViewModel { + @NotNull(message = "Message cannot be empty") + private String message; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/UserViewModel.java b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/UserViewModel.java new file mode 100644 index 00000000..254c9485 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/viewmodels/UserViewModel.java @@ -0,0 +1,24 @@ +package com.RDS.skilltree.viewmodels; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class UserViewModel { + private String id; + private String name; + + public static UserViewModel toViewModel(RdsUserViewModel user) { + if (user == null) { + return null; + } + + return UserViewModel.builder() + .id(user.getId()) + .name(String.format("%s %s", user.getFirst_name(), user.getLast_name())) + .build(); + } +} diff --git a/skill-tree/src/main/resources/application-dev.properties b/skill-tree/src/main/resources/application-dev.properties index 8fa24448..21199428 100644 --- a/skill-tree/src/main/resources/application-dev.properties +++ b/skill-tree/src/main/resources/application-dev.properties @@ -1 +1,2 @@ -cookieName=rds-session-v2-development \ No newline at end of file +cookieName=rds-session-v2-development +logging.level.root=DEBUG \ No newline at end of file diff --git a/skill-tree/src/main/resources/application.properties b/skill-tree/src/main/resources/application.properties index 098a53b3..f9e6977c 100644 --- a/skill-tree/src/main/resources/application.properties +++ b/skill-tree/src/main/resources/application.properties @@ -1,15 +1,16 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${DB_NAME} -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.datasource.username=${MYSQL_DB_USERNAME} spring.datasource.password=${MYSQL_DB_PASSWORD} -spring.jpa.hibernate.ddl-auto=${DB_DDL_POLICY} +spring.flyway.enabled=true +spring.flyway.locations=classpath:db/migrations +spring.jpa.show-sql=true jwt.rds.public.key=${RDS_PUBLIC_KEY} API_V1_PREFIX=/api/v1 -spring.datasource.version=8.1.0 management.endpoints.web.exposure.include=health,info,metrics logging.level.root=ERROR # If no value received from env default is dev spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev} -# TODO:Dummy JSON code, needs to be changed as part of #103 -endorsements.dummmy-data.path=dummy-data/endorsements.json +rds.backendBaseUrl=${RDS_BACKEND_BASE_URL} +skilltree.frontendBaseUrl=${SKILL_TREE_FRONTEND_BASE_URL} +skilltree.backendBaseUrl=${SKILL_TREE_BACKEND_BASE_URL} diff --git a/skill-tree/src/main/resources/db/migrations/V1__initial_setup.sql b/skill-tree/src/main/resources/db/migrations/V1__initial_setup.sql new file mode 100644 index 00000000..6238105a --- /dev/null +++ b/skill-tree/src/main/resources/db/migrations/V1__initial_setup.sql @@ -0,0 +1,40 @@ +-- Skills table +CREATE TABLE `skills` ( + `id` int NOT NULL AUTO_INCREMENT, + `created_at` datetime(6) NOT NULL, + `deleted_at` datetime(6) DEFAULT NULL, + `is_deleted` tinyint(1) NOT NULL DEFAULT '0', + `updated_at` datetime(6) DEFAULT NULL, + `created_by` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `skill_type` enum('ATOMIC') NOT NULL, + `updated_by` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- Endorsements table +CREATE TABLE `endorsements` ( + `id` int NOT NULL AUTO_INCREMENT, + `created_at` datetime(6) NOT NULL, + `deleted_at` datetime(6) DEFAULT NULL, + `is_deleted` tinyint(1) NOT NULL DEFAULT '0', + `updated_at` datetime(6) DEFAULT NULL, + `endorse_id` varchar(255) NOT NULL, + `endorser_id` varchar(255) NOT NULL, + `message` text NOT NULL, + `skill_id` int NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_endorsements_skill_id` FOREIGN KEY (`skill_id`) REFERENCES `skills` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- User skills table +CREATE TABLE `user_skills` ( + `id` int NOT NULL AUTO_INCREMENT, + `status` enum('APPROVED','PENDING','REJECTED') NOT NULL, + `user_id` varchar(255) NOT NULL, + `skill_id` int NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unique_user_skills` (`user_id`, `skill_id`), + CONSTRAINT `fk_user_skills_skill_id` FOREIGN KEY (`skill_id`) REFERENCES `skills` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; \ No newline at end of file diff --git a/skill-tree/src/main/resources/dummy-data/endorsements.json b/skill-tree/src/main/resources/dummy-data/endorsements.json deleted file mode 100644 index cdad6516..00000000 --- a/skill-tree/src/main/resources/dummy-data/endorsements.json +++ /dev/null @@ -1,142 +0,0 @@ -[ - { - "id": "7f6cb5e8-2e22-4a33-a120-6a0f684fb1fc", - "user_id": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "skill_id": "7a6b8876-44e3-4b18-8579-79e9d4a5f0c9", - "status": "PENDING", - "created_at": "2024-02-18 17:34:47.504189", - "created_by": "7a6b8876-44e3-4b18-8579-79e9d4a5f0c9", - "updated_at": "2024-02-18 17:34:47.504189", - "updated_by": "7a6b8876-44e3-4b18-8579-79e9d4a5f0c9" - }, - { - "id": "c13a5db1-8df0-4b63-99fd-4e75f97d383c", - "user_id": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "skill_id": "4dcf2994-e5b4-45f3-8974-d2db0272a6cc", - "status": "APPROVED", - "created_at": "2024-02-18 17:34:47.504189", - "created_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "updated_at": "2024-02-18 17:34:47.504189", - "updated_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb" - }, - { - "id": "d2a40739-53a3-4a47-8c14-5b64ee5eaa3d", - "user_id": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "skill_id": "1e7af93e-3de5-4a24-a4b7-9fc4ff48e37e", - "status": "PENDING", - "created_at": "2024-02-19 12:45:32.092461", - "created_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "updated_at": "2024-02-19 12:45:32.092461", - "updated_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261" - }, - { - "id": "bd177123-0c50-458d-8d56-0d6f1a78f69c", - "user_id": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "skill_id": "f9d0cf6d-44b5-4c3e-867d-ee4a5ffad017", - "status": "APPROVED", - "created_at": "2024-02-20 09:21:18.735582", - "created_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "updated_at": "2024-02-20 09:21:18.735582", - "updated_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb" - }, - { - "id": "56d27502-39c3-4744-822f-c1e0504d149d", - "user_id": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "skill_id": "b29d4941-ef22-4c39-86f9-b048554c1419", - "status": "PENDING", - "created_at": "2024-02-25 19:17:08.201468", - "created_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "updated_at": "2024-02-25 19:17:08.201468", - "updated_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261" - }, - { - "id": "3a5c6a30-8c68-4d38-8b70-720a0e7a4a6b", - "user_id": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "skill_id": "7d6db19e-442e-4247-b5cf-87cf6db26bb2", - "status": "APPROVED", - "created_at": "2024-02-26 22:09:57.994362", - "created_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "updated_at": "2024-02-26 22:09:57.994362", - "updated_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb" - }, - { - "id": "1a0c1baf-86c6-4f13-8fd5-c736a94c2c54", - "user_id": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "skill_id": "d30e4b2d-9c53-48d7-8920-8a225526d688", - "status": "PENDING", - "created_at": "2024-02-27 19:42:31.628175", - "created_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "updated_at": "2024-02-27 19:42:31.628175", - "updated_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261" - }, - { - "id": "f4f4827d-5f22-4e01-9b97-d10889124578", - "user_id": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "skill_id": "0b37ef80-21d5-4960-995b-089221efc84f", - "status": "PENDING", - "created_at": "2024-02-28 14:51:22.941799", - "created_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "updated_at": "2024-02-28 14:51:22.941799", - "updated_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb" - }, - { - "id": "b0bda1de-3cf4-4f90-961f-d928334abf5b", - "user_id": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "skill_id": "e11cd9d8-0649-4d33-b4e3-60f9f68b6c39", - "status": "APPROVED", - "created_at": "2024-02-29 08:11:45.202497", - "created_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "updated_at": "2024-02-29 08:11:45.202497", - "updated_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261" - }, - { - "id": "c7e026b9-f184-4fb1-881f-625ccf937fd0", - "user_id": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "skill_id": "a94a7322-4f52-456d-850a-b2c03485be61", - "status": "PENDING", - "created_at": "2024-03-01 16:22:33.795328", - "created_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "updated_at": "2024-03-01 16:22:33.795328", - "updated_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb" - }, - { - "id": "99f2035f-4a6a-4f8c-938f-5e51b04d6a8d", - "user_id": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "skill_id": "e3084315-0355-4644-ae89-5a84f16d91e6", - "status": "APPROVED", - "created_at": "2024-03-02 11:59:59.999999", - "created_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "updated_at": "2024-03-02 11:59:59.999999", - "updated_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261" - }, - { - "id": "2513c590-78ff-4a56-90e4-5b3e06370b64", - "user_id": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "skill_id": "87490d7c-37a6-442d-8255-fd086555a35a", - "status": "PENDING", - "created_at": "2024-03-03 17:48:04.564729", - "created_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "updated_at": "2024-03-03 17:48:04.564729", - "updated_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb" - }, - { - "id": "cf4d6fc5-79c0-49ed-8f5b-4cc9c0e0c7b0", - "user_id": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "skill_id": "2bb70a9b-31d2-4a9b-9751-efb3feae4cd9", - "status": "APPROVED", - "created_at": "2024-03-04 14:33:27.901382", - "created_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261", - "updated_at": "2024-03-04 14:33:27.901382", - "updated_by": "73e0b7c4-d128-4e53-9501-0e7f4ff5a261" - }, - { - "id": "d1c947cb-ae6f-422e-ae67-bfd057f4177d", - "user_id": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "skill_id": "07a5b5c5-fd7f-4992-9fa7-5c4e41468b68", - "status": "PENDING", - "created_at": "2024-03-05 09:15:38.283750", - "created_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb", - "updated_at": "2024-03-05 09:15:38.283750", - "updated_by": "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb" - } -] \ No newline at end of file diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/EndorsementsIntegrationTests.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/EndorsementsIntegrationTests.java deleted file mode 100644 index a4b5cb1b..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/integration/EndorsementsIntegrationTests.java +++ /dev/null @@ -1,589 +0,0 @@ -package com.RDS.skilltree.integration; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.*; - -import com.RDS.skilltree.Endorsement.*; -import com.RDS.skilltree.Skill.*; -import com.RDS.skilltree.User.*; -import io.restassured.response.Response; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.UUID; -import org.junit.jupiter.api.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import utils.RestAPIHelper; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -public class EndorsementsIntegrationTests extends TestContainerManager { - - private UserRepository userRepository; - private SkillRepository skillRepository; - private final UserService userService; - private final SkillsService skillsService; - private UserDTO user; - private SkillDTO skill; - private EndorsementRepository endorsementRepository; - - @Autowired - public EndorsementsIntegrationTests( - UserService userService, - UserRepository userRepository, - SkillsService skillsService, - SkillRepository skillRepository, - EndorsementRepository endorsementRepository) { - this.userService = userService; - this.userRepository = userRepository; - this.skillsService = skillsService; - this.skillRepository = skillRepository; - this.endorsementRepository = endorsementRepository; - } - - @BeforeEach - private void addData() throws MalformedURLException { - user = - userService.createUser( - UserDRO.builder() - .role(UserRole.MEMBER) - .rdsUserId("p6Bo61VEClhtVdwW0ihg") - .lastName("Doe") - .firstName("John") - .imageUrl( - new URL( - "https://res.cloudinary.com/realdevsquad/image/upload/v1666193594/profile/p6Bo61VEClhtVdwW0iGH/lezguwdq5bgzawa3.jpg")) - .build()); - - skill = - skillsService.createSkill( - SkillDRO.builder().name("Java").type(SkillType.ATOMIC).createdBy(user.getId()).build()); - } - - @AfterEach - private void cleanUp() { - endorsementRepository.deleteAll(); - skillRepository.deleteAll(); - userRepository.deleteAll(); - } - - private UUID createEndorsementModel(Boolean isStatusPending) { - EndorsementStatus endorsementStatus; - if (isStatusPending) { - endorsementStatus = EndorsementStatus.PENDING; - } else { - endorsementStatus = EndorsementStatus.APPROVED; - } - EndorsementModel endorsementModel = - EndorsementModel.builder().status(endorsementStatus).build(); - return endorsementRepository.save(endorsementModel).getId(); - } - - @Test - @Disabled - @DisplayName("Fetch all the endorsements") - public void testAPIReturnsAllEndorsements() { - Response response = given().cookies(RestAPIHelper.getUserCookie()).get("/endorsements"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("data", hasSize(1)) - .body("data[0].endorseeId", equalTo("user-1")) - .body("data[0].skillName", equalTo("Java")) - .body("data[0].status", anyOf(equalTo("APPROVED"), equalTo("REJECTED"), equalTo("PENDING"))) - .body("data[0].endorsementType", anyOf(equalTo("POSITIVE"), equalTo("NEGATIVE"))) - .body("data[0].endorsersList", hasSize(1)) - .body("data[0].endorserList[0].endorseeId", equalTo("user-2")) - .body("data[0].endorserList[0].description", isA(String.class)) - .body( - "data[0].endorserList[0].userType", - anyOf(equalTo("NORMAL_USER"), equalTo("SUPER_USER"), equalTo("MAVEN"))); - } - - @Test - @Disabled - @DisplayName("Fetch all the endorsements given endorsement status") - public void testAPIReturnsEndorsementsGivenStatus() { - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .queryParam("status", "PENDING") - .get("/endorsements"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("data", hasSize(1)) - .body("data[0].endorseeId", equalTo("user-1")) - .body("data[0].skillName", equalTo("Java")) - .body("data[0].status", equalTo("PENDING")) - .body("data[0].endorsementType", anyOf(equalTo("POSITIVE"), equalTo("NEGATIVE"))) - .body("data[0].endorsersList", hasSize(1)) - .body("data[0].endorserList[0].endorseeId", equalTo("user-2")) - .body("data[0].endorserList[0].description", isA(String.class)) - .body( - "data[0].endorserList[0].userType", - anyOf(equalTo("NORMAL_USER"), equalTo("SUPER_USER"), equalTo("MAVEN"))); - } - - @Test - @Disabled - @DisplayName("Return 400 on invalid endorsement status passed") - public void testAPIReturns400_OnInvalidStatusPassed() { - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .queryParam("status", "APPROVAL") - .get("/endorsements"); - - response - .then() - .statusCode(400) - .body("code", equalTo(400)) - .body("message", equalTo("Invalid status passed")); - } - - @Test - @Disabled - @DisplayName("Return 400 on invalid endorsementId passed") - public void testAPIReturns400_OnInvalidParameterPassed() { - String endorsementId = "randomId"; - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("endorsementId", endorsementId) - .patch("/endorsements"); - - response - .then() - .statusCode(400) - .body("code", equalTo(400)) - .body("message", equalTo("Invalid endorsementId passed")); - } - - @Test - @DisplayName("Return 201 on endorsements creation") - public void testAPIReturns201_OnEndorsementCreation() { - UUID endorseeId = user.getId(); - UUID skillId = skill.getId(); - - EndorsementDRO endorsementDRO = new EndorsementDRO(); - endorsementDRO.setEndorseeId(endorseeId); - endorsementDRO.setSkillId(skillId); - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(endorsementDRO) - .post("/v1/endorsements"); - - response - .then() - .statusCode(201) - .contentType("application/json") - .body("data.endorseeId", equalTo(endorseeId.toString())) - .body("data.skill.name", equalTo("Java")); - } - - @Test - @DisplayName("Return 400 on endorsements userid null") - public void testAPIReturns400_OnEndorsementCreationUserIdNull() { - - UUID skillId = skill.getId(); - - EndorsementDRO endorsementDRO = new EndorsementDRO(); - - endorsementDRO.setSkillId(skillId); - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(endorsementDRO) - .post("/v1/endorsements"); - - response - .then() - .statusCode(400) - .contentType("application/json") - .body("data", equalTo(null)) - .body("message", equalTo("user id cannot be null")); - } - - @Test - @DisplayName("Return 400 on endorsements skillid null") - public void testAPIReturns400_OnEndorsementCreationSkillIdNull() { - UUID endorseeId = user.getId(); - - EndorsementDRO endorsementDRO = new EndorsementDRO(); - endorsementDRO.setEndorseeId(endorseeId); - - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(endorsementDRO) - .post("/v1/endorsements"); - - response - .then() - .statusCode(400) - .contentType("application/json") - .body("data", equalTo(null)) - .body("message", equalTo("skill id cannot be null")); - } - - @Test - @Disabled - @DisplayName("Return 200 on endorsements updation") - public void testAPIReturns200_OnEndorsementGivenId() { - String endorsementId = "e-1"; - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("endorsementId", endorsementId) - .patch("/endorsements"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("code", equalTo(200)) - .body("data.endorseeId", equalTo("user-1")) - .body("data.skillName", equalTo("Java")) - .body("data.status", anyOf(equalTo("APPROVED"), equalTo("PENDING"), equalTo("REJECTED"))) - .body("data.endorsementType", anyOf(equalTo("POSITIVE"), equalTo("NEGATIVE"))) - .body("data.endorsersList", hasSize(1)) - .body("data.endorsersList[0].endorseeId", equalTo("user-2")) - .body("data.endorsersList[0].description", isA(String.class)) - .body( - "data.endorsersList[0].userType", - anyOf(equalTo("SUPER_USER"), equalTo("MAVEN"), equalTo("USER"))); - } - - @Test - @Disabled - @DisplayName("Return 400 on invalid endorsementId passed") - public void testAPIReturn400_OnInvalidIdPassed() { - String endorsementId = "randomId"; - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("endorsementId", endorsementId) - .get("/endorsements"); - - response - .then() - .statusCode(400) - .body("code", equalTo(400)) - .body("message", equalTo("Invalid endorsementId passed")); - } - - @Test - @Disabled - @DisplayName("Return 404 when endorsement not found given endorsementId") - public void testAPIReturn404_OnEndorsementNotFound() { - String endorsementId = "randomId"; - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("endorsementId", endorsementId) - .get("/endorsements"); - - response - .then() - .statusCode(404) - .body("code", equalTo(404)) - .body("message", equalTo("Endorsement not found")); - } - - @Test - @DisplayName("Return 200, with the endorsements of a particular user given userID") - public void itShouldReturn200OnEndorsementSearchByUserIDPresentInList() { - String userID = "f13ac7a0-76ab-4215-8bfc-2dd5d9f8ebeb"; - - Response response = given().get("/v1/endorsements?dummyData=true&userID=" + userID); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("content", everyItem(hasKey("user_id"))) - .body("content.user_id", everyItem(equalTo(userID))) - .body("content.size()", equalTo(7)) - .body("totalPages", equalTo(1)) - .body("pageable.pageNumber", equalTo(0)) - .body("pageable.pageSize", equalTo(10)) - .body("totalElements", equalTo(7)); - } - - @Test - @DisplayName("Return 200, with the endorsements of a particular skill given skillID") - public void itShouldReturn200OnEndorsementSearchBySkillIDPresentInList() { - String skillID = "7a6b8876-44e3-4b18-8579-79e9d4a5f0c9"; - - Response response = given().get("/v1/endorsements?dummyData=true&skillID=" + skillID); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("content", everyItem(hasKey("skill_id"))) - .body("content.skill_id", everyItem(equalTo(skillID))) - .body("content.size()", equalTo(1)) - .body("totalPages", equalTo(1)) - .body("pageable.pageNumber", equalTo(0)) - .body("pageable.pageSize", equalTo(10)) - .body("totalElements", equalTo(1)); - } - - @Test - @DisplayName("Return 200, with 1st page all the endorsements with default pagesize") - public void itShouldReturn200OnEndorsementSearchAllEndorsements() { - Response response = given().get("/v1/endorsements?dummyData=true"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("totalPages", equalTo(2)) - .body("content.size()", equalTo(10)) - .body("pageable.pageNumber", equalTo(0)) - .body("pageable.pageSize", equalTo(10)) - .body("totalElements", equalTo(14)); - } - - @Test - @DisplayName("Return 200, with 1st page all the endorsements with custom limit value") - public void itShouldReturn200OnEndorsementSearchAllEndorsementsWithLimit() { - Response response = given().get("/v1/endorsements?dummyData=true&limit=15"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("totalPages", equalTo(1)) - .body("content.size()", equalTo(14)) - .body("pageable.pageNumber", equalTo(0)) - .body("pageable.pageSize", equalTo(15)) - .body("totalElements", equalTo(14)); - } - - @Test - @DisplayName("Return 200, with 1st page of all endorsements result where page size is 5") - public void itShouldReturn200OnEndorsementSearchAllEndorsementsWithMultiplePages() { - Response response = given().get("/v1/endorsements?dummyData=true&limit=5"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("totalPages", equalTo(3)) - .body("content.size()", equalTo(5)) - .body("pageable.pageNumber", equalTo(0)) - .body("pageable.pageSize", equalTo(5)) - .body("totalElements", equalTo(14)); - } - - @Test - @DisplayName("Return 200, with 2nd page of all the endorsements result") - public void itShouldReturn200With2ndPageOnEndorsementSearchAllEndorsementsWithMultiplePages() { - Response response = given().get("/v1/endorsements?dummyData=true&limit=5&offset=1"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("totalPages", equalTo(3)) - .body("content.size()", equalTo(5)) - .body("pageable.pageNumber", equalTo(1)) - .body("pageable.pageSize", equalTo(5)) - .body("totalElements", equalTo(14)); - } - - @Test - @DisplayName("Return 200, with the endorsements matching the given userID and skillID") - public void itShouldReturn200OnEndorsementSearchGivenBothUserIDAndSkillID() { - String userID = "73e0b7c4-d128-4e53-9501-0e7f4ff5a261"; - String skillID = "7a6b8876-44e3-4b18-8579-79e9d4a5f0c9"; - - Response response = - given().get("/v1/endorsements?dummyData=true&skillID=" + skillID + "&userID=" + userID); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("content", everyItem(hasKey("skill_id"))) - .body("content.skill_id", everyItem(equalTo(skillID))) - .body("content", everyItem(hasKey("user_id"))) - .body("content.user_id", everyItem(equalTo(userID))) - .body("content.size()", equalTo(1)) - .body("totalPages", equalTo(1)) - .body("pageable.pageNumber", equalTo(0)) - .body("pageable.pageSize", equalTo(10)) - .body("totalElements", equalTo(1)); - } - - @Test - @DisplayName( - "Return 204, when there are no endorsements present for the given userID in UUID form") - public void itShouldReturn204OnEndorsementSearchWithValidUserIDButNotPresentInList() { - String userID = UUID.randomUUID().toString(); - - Response response = given().get("/v1/endorsements?dummyData=true&userID=" + userID); - - response.then().statusCode(204); - } - - @Test - @DisplayName( - "Return 204, when there are no endorsements present for the given skillID in UUID form") - public void itShouldReturn204OnEndorsementSearchWithValidSkillIDButNotPresentInList() { - String skillID = UUID.randomUUID().toString(); - - Response response = given().get("/v1/endorsements?dummyData=true&skillID=" + skillID); - - response.then().statusCode(204); - } - - @Test - @DisplayName("Return 400, given a userID which is not a UUID") - public void itShouldReturn400OnEndorsementSearchWithInvalidUserID() { - String userID = "invalid-user-id"; - - Response response = given().get("/v1/endorsements?dummyData=true&userID=" + userID); - - response.then().statusCode(400); - } - - @Test - @DisplayName("Return 400, given a skillID which is not a UUID") - public void itShouldReturn400OnEndorsementSearchWithInvalidSkillID() { - String skillID = "invalid-skill-id"; - - Response response = given().get("/v1/endorsements?dummyData=true&skillID=" + skillID); - - response.then().statusCode(400); - } - - @Test - @DisplayName("Return 204, given an offset value greater than maximum endorsements") - public void itShouldReturn204OnEndorsementSearchWithOffsetGreaterThanMaximumEndorsements() { - Response response = given().get("/v1/endorsements?dummyData=true&offset=10"); - - response.then().statusCode(204); - } - - @Test - @Disabled - @DisplayName("Return 401, when request is made without a valid cookie") - public void itShouldReturn401OnEndorsementSearchWithoutCookie() { - Response response = given().get("/v1/endorsements"); - - response - .then() - .statusCode(401) - .body( - "message", - equalTo( - "The access token provided is expired, revoked, malformed, or invalid for other reasons.")); - } - - @Test - @DisplayName( - "Return 200, when request is made using super user cookie and status is APPROVED/REJECTED") - public void - itShouldReturn200OnUpdateEndorsementStatusWithSuperUserCookieAndAcceptOrRejectEndorsementStatus() { - UUID endorsementId = createEndorsementModel(true); - Response response = - given() - .cookies(RestAPIHelper.getSuperUserCookie()) - .queryParam("status", EndorsementStatus.APPROVED.name()) - .patch("/v1/endorsements/{id}", endorsementId); - - response - .then() - .statusCode(200) - .body("data", equalTo(null)) - .body("message", equalTo("Successfully updated endorsement status")); - } - - @Test - @DisplayName( - "Return 403, when request is made without using super user cookie and status is APPROVED/REJECTED") - public void - itShouldReturn403OnUpdateEndorsementStatusWithOutSuperUserCookieAndAcceptOrRejectEndorsementStatus() { - UUID endorsementId = createEndorsementModel(true); - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .queryParam("status", EndorsementStatus.APPROVED.name()) - .patch("/v1/endorsements/{id}", endorsementId); - - response - .then() - .statusCode(403) - .body("data", equalTo(null)) - .body("message", equalTo("Unauthorized, Access is only available to super users")); - } - - @Test - @DisplayName( - "Return 400, when request is made with using super user cookie and status is invalid") - public void - itShouldReturn400OnUpdateEndorsementStatusWithSuperUserCookieAndEndorsementStatusIsInvalid() { - UUID endorsementId = createEndorsementModel(true); - Response response = - given() - .cookies(RestAPIHelper.getSuperUserCookie()) - .queryParam("status", "invalid-status") - .patch("/v1/endorsements/{id}", endorsementId); - - response - .then() - .statusCode(400) - .body("data", equalTo(null)) - .body("message", equalTo("Invalid parameter endorsement status: invalid-status")); - } - - @Test - @DisplayName( - "Return 400, when request is made with using super user cookie and status is PENDING") - public void - itShouldReturn400OnUpdateEndorsementStatusWithSuperUserCookieAndEndorsementStatusIsPending() { - UUID endorsementId = createEndorsementModel(true); - Response response = - given() - .cookies(RestAPIHelper.getSuperUserCookie()) - .queryParam("status", EndorsementStatus.PENDING.name()) - .patch("/v1/endorsements/{id}", endorsementId); - - response - .then() - .statusCode(400) - .body("data", equalTo(null)) - .body("message", equalTo("Invalid parameter endorsement status: PENDING")); - } - - @Test - @DisplayName( - "Return 409, when request is made with using super user cookie and endorsement is already updated") - public void - itShouldReturn409OnUpdateEndorsementStatusWithSuperUserCookieAndEndorsementAlreadyUpdated() { - UUID endorsementId = createEndorsementModel(false); - Response response = - given() - .cookies(RestAPIHelper.getSuperUserCookie()) - .queryParam("status", EndorsementStatus.APPROVED.name()) - .patch("/v1/endorsements/{id}", endorsementId); - - response - .then() - .statusCode(409) - .body("data", equalTo(null)) - .body("message", equalTo("Endorsement is already updated. Cannot modify status")); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/SecurityContextIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/SecurityContextIntegrationTest.java deleted file mode 100644 index 19754e71..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/integration/SecurityContextIntegrationTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.RDS.skilltree.integration; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; - -import io.restassured.response.Response; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.springframework.boot.test.context.SpringBootTest; -import utils.RestAPIHelper; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -public class SecurityContextIntegrationTest extends TestContainerManager { - - @Test - public void testTokenIsNotPresent() { - - Response response = given().get("/v1/endorsement"); - response - .then() - .statusCode(401) - .body( - "message", - equalTo( - "The access token provided is expired, revoked, malformed, or invalid for other reasons.")); - } - - @Test - public void testInvalidToken() { - Response response = given().cookie("rds-session-v2", "invalidtoken").get("/v1/endorsement"); - response - .then() - .statusCode(401) - .body( - "message", - equalTo( - "The access token provided is expired, revoked, malformed, or invalid for other reasons.")); - } - - @Test - public void test_GetSkill_WithGuestToken() { - Response response = - given() - .cookies(RestAPIHelper.getGuestUserCookie()) - .contentType("application/json") - .get("/v1/skills/"); - response.then().statusCode(200); - } - - @Test - public void test_CreateSkill_WithGuestToken() { - Response response = - given() - .cookies(RestAPIHelper.getGuestUserCookie()) - .contentType("application/json") - .post("/v1/skills/"); - response.then().statusCode(403).body("message", equalTo("Access Denied")); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/SkillsIntegrationTests.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/SkillsIntegrationTests.java deleted file mode 100644 index 9fd65268..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/integration/SkillsIntegrationTests.java +++ /dev/null @@ -1,268 +0,0 @@ -package com.RDS.skilltree.integration; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.*; - -import com.RDS.skilltree.Skill.*; -import com.RDS.skilltree.User.*; -import io.restassured.response.Response; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.UUID; -import org.junit.jupiter.api.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import utils.RestAPIHelper; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -public class SkillsIntegrationTests extends TestContainerManager { - private UserRepository userRepository; - private SkillRepository skillRepository; - private final UserService userService; - private final SkillsService skillsService; - private UserDTO user; - private SkillDTO skill; - - @Autowired - public SkillsIntegrationTests( - UserService userService, - UserRepository userRepository, - SkillsService skillsService, - SkillRepository skillRepository) { - this.userService = userService; - this.userRepository = userRepository; - this.skillsService = skillsService; - this.skillRepository = skillRepository; - } - - @BeforeEach - private void addData() throws MalformedURLException { - user = - userService.createUser( - UserDRO.builder() - .role(UserRole.MEMBER) - .rdsUserId("p6Bo61VEClhtVdwW0ihg") - .lastName("Doe") - .firstName("John") - .imageUrl( - new URL( - "https://res.cloudinary.com/realdevsquad/image/upload/v1666193594/profile/p6Bo61VEClhtVdwW0iGH/lezguwdq5bgzawa3.jpg")) - .build()); - - skill = - skillsService.createSkill( - SkillDRO.builder().name("Java").type(SkillType.ATOMIC).createdBy(user.getId()).build()); - } - - @AfterEach - private void cleanUp() { - skillRepository.deleteAll(); - userRepository.deleteAll(); - } - - @Test - @DisplayName("Return 200, on all skills") - public void testAPIReturns200_OnAllSkillsFound() { - Response response = - given() - .queryParam("offset", 0) - .queryParam("limit", 1) - .cookies(RestAPIHelper.getUserCookie()) - .get("/v1/skills/"); - - response - .then() - .statusCode(200) - .body("content", hasSize(1)) - .body("content[0].type", equalTo("ATOMIC")) - .body("content[0].name", equalTo("Java")) - .body("content[0].users", empty()) - .body("totalPages", equalTo(1)) - .body("totalElements", equalTo(1)) - .body("last", equalTo(true)) - .body("size", equalTo(1)) - .body("number", equalTo(0)) - .body("numberOfElements", equalTo(1)) - .body("empty", equalTo(false)); - } - - @Test - @DisplayName("Return 200, on no skills found") - public void testAPIReturns200_OnNoSkillsFound() { - skillRepository.deleteAll(); - Response response = given().cookies(RestAPIHelper.getUserCookie()).get("/v1/skills/"); - - response - .then() - .statusCode(200) - .body("content", hasSize(0)) - .body("totalPages", equalTo(0)) - .body("totalElements", equalTo(0)) - .body("last", equalTo(true)) - .body("size", equalTo(10)) - .body("number", equalTo(0)) - .body("numberOfElements", equalTo(0)) - .body("empty", equalTo(true)); - } - - @Test - @DisplayName("Return 200, on skill found given skillId") - public void testAPIReturns200_OnSkillFoundById() { - UUID skillId = skill.getId(); - - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("id", skillId) - .get("/v1/skills/{id}"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("id", equalTo(String.valueOf(skillId))) - .body("name", equalTo("Java")) - .body("type", equalTo("ATOMIC")); - } - - @Test - @DisplayName("Return 404, on skill not found given SkillId") - public void testAPIReturns404_OnSkillNotFound() { - UUID skillId = UUID.randomUUID(); - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("id", skillId) - .get("/v1/skills/{id}"); - - response.then().statusCode(404).body("message", equalTo("Skill not found with given Id")); - } - - @Test - @DisplayName("Return 200, on skill with given name") - public void testAPIReturns200_OnSkillFoundGivenName() { - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("name", "Java") - .get("/v1/skills/name/{name}"); - - response - .then() - .statusCode(200) - .contentType("application/json") - .body("name", equalTo("Java")) - .body("id", equalTo(skill.getId().toString())) - .body("type", equalTo("ATOMIC")); - } - - @Test - @DisplayName("Return 404, if skill given skill name is not found") - public void testAPIReturns404_OnSkillGivenSkillNameNotFound() { - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .pathParam("name", "Go") - .get("/v1/skills/name/{name}"); - - response.then().statusCode(404).body("message", equalTo("Skill not found with the given name")); - } - - @Test - @DisplayName("Return 400, if createdBy is not passed for Skill creation") - public void testAPIReturns400_OnCreatedByNotPassedForSKillCreation() { - SkillDRO skillDRO = SkillDRO.builder().name("Go").type(SkillType.ATOMIC).build(); - - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(skillDRO) - .post("/v1/skills/"); - - response - .then() - .statusCode(400) - .body("data", equalTo(null)) - .body("message", equalTo("Created by user Id cannot be null")); - } - - @Test - @DisplayName("Return 400, if type is not passed for Skill creation") - public void testAPIReturns400_OnTypeNotPassedForSkillCreation() { - SkillDRO skillDRO = SkillDRO.builder().name("Go").createdBy(user.getId()).build(); - - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(skillDRO) - .post("/v1/skills/"); - - response - .then() - .statusCode(400) - .body("data", equalTo(null)) - .body("message", equalTo("SkillType cannot be null")); - } - - @Test - @DisplayName("Return 400, if name is not passed for Skill creation") - public void testAPIReturns400_OnNameNotPassedForSkillCreation() { - SkillDRO skillDRO = SkillDRO.builder().type(SkillType.ATOMIC).createdBy(user.getId()).build(); - - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(skillDRO) - .post("/v1/skills/"); - - response - .then() - .statusCode(400) - .body("data", equalTo(null)) - .body("message", equalTo("Name cannot be null")); - } - - @Test - @DisplayName("Return 409, if name is already used for Skill creation") - public void testAPIReturns409_OnNameAlreadyUsedForSkillCreation() { - SkillDRO skillDRO = - SkillDRO.builder().type(SkillType.ATOMIC).name("Java").createdBy(user.getId()).build(); - - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(skillDRO) - .post("/v1/skills/"); - - response - .then() - .statusCode(409) - .body("message", equalTo("Cannot create entry for Skill as Skill name is duplicate")); - } - - @Test - @DisplayName("Return 201, on successful Skill creation") - public void testAPIReturns201_OnSuccessfulSkillCreation() { - SkillDRO skillDRO = - SkillDRO.builder().type(SkillType.ATOMIC).name("Go").createdBy(user.getId()).build(); - - Response response = - given() - .cookies(RestAPIHelper.getUserCookie()) - .contentType("application/json") - .body(skillDRO) - .post("/v1/skills/"); - - response - .then() - .statusCode(201) - .contentType("application/json") - .body("name", equalTo("Go")) - .body("type", equalTo("ATOMIC")); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/TestContainerManager.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/TestContainerManager.java deleted file mode 100644 index ecd0ff75..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/integration/TestContainerManager.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.RDS.skilltree.integration; - -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; -import org.testcontainers.containers.MySQLContainer; - -abstract class TestContainerManager { - @ServiceConnection static final MySQLContainer mysqlContainer; - - static { - mysqlContainer = - new MySQLContainer("mysql:8.1") - .withDatabaseName("skilltree-test") - .withUsername("root") - .withPassword("password"); - mysqlContainer.start(); - } - - @DynamicPropertySource - static void configureTestProperties(DynamicPropertyRegistry registry) { - registry.add("spring.jpa.hibernate.ddl-auto", () -> "update"); - registry.add("cookieName", () -> "rds-session-v2"); - registry.add( - "jwt.rds.public.key", - () -> - "-----BEGIN PUBLIC KEY-----MIICITANBgkqhkiG9w0BAQEFAAOCAg4AMIICCQKCAgBpAet8sOf64PtzdnwtkZB4JEJTCtQT9ZQMuuWUDXZGTG0iO7x3WZw6GanBboKGblU4VZEgd8H7bKOOIaQF4AsiXsw/vUsOV5Ue73a9Jj5d57jyon7M8fFmjna3afZfb5SBru5Iv0ECePqIKUIhSmToML+y3bFKF2cbUTEe2qPK5xzBeH4AWq4Zb2N0gHNstinwrXL9LWawQPkJr23TohZZEFSzyZbeklWWwz67A6YnE01w42R/TLE3LmU8YKkrHkgFsAHtUMQO++JsH4q3F9J0e0VkLzj5sB5RgAYscs6YFKoFD5jKgtSRPIXz7O9GsC76dHtwGXOk47/NWxu7bUQ0VcD2hJYprR28PjdNk5KiRKO5Z83JkiM6ed9UAkiD/fIRI8LITaLayHdFfQvXM+d9v4ugPHEq+aVllFMH1lUu/2B1aJpk4D4w5JcIzIZ9og4cMz00EGU/1o+BX2S55/Ok 4MaxX6Zl3QYm1K0cPLOdisYoygPtnNEav32JLgM2yOXdyuhpYzmn66yyFFck2nnCkezG5Gvlf3MejMavRO+sfIz0gDIhXEwWu0EJDrG5nmNRwejrSXx42YxmYZGkK/c8 2fiwbOVqIuFgsI6lWGdyDayFRg9bjrk6KiQZFnP4KcmUXk4PhiSItDJAUEkNz0+4StHNoqFhNH5pnEj4VbmJqwIDAQAB -----END PUBLIC KEY-----"); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/unit/EndorsementListServiceTest.java b/skill-tree/src/test/java/com/RDS/skilltree/unit/EndorsementListServiceTest.java deleted file mode 100644 index 4a7e0ad4..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/unit/EndorsementListServiceTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.RDS.skilltree.unit; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -import com.RDS.skilltree.Endorsement.EndorsementModel; -import com.RDS.skilltree.Endorsement.EndorsementRepository; -import com.RDS.skilltree.EndorsementList.*; -import com.RDS.skilltree.Exceptions.NoEntityException; -import com.RDS.skilltree.User.UserModel; -import com.RDS.skilltree.User.UserRepository; -import java.util.Optional; -import java.util.UUID; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -class EndorsementListServiceTest { - - @Mock private EndorsementListRepository endorsementListRepository; - - @Mock private EndorsementRepository endorsementRepository; - - @Mock private UserRepository userRepository; - - @InjectMocks private EndorsementListService endorsementListService; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - - @Test - void testCreateEndorsementListEntry() { - // Mock data - UUID endorserId = UUID.randomUUID(); - UUID endorsementId = UUID.randomUUID(); - EndorsementListDRO endorsementListDRO = new EndorsementListDRO(); - endorsementListDRO.setEndorserId(endorserId); - endorsementListDRO.setEndorsementId(endorsementId); - endorsementListDRO.setDescription("Test Description"); - endorsementListDRO.setType(EndorsementType.POSITIVE); - - UserModel mockUser = new UserModel(); - mockUser.setId(endorserId); - - EndorsementModel mockEndorsement = EndorsementModel.builder().id(endorsementId).build(); - - // Mock the repository behavior - when(userRepository.findById(endorserId)).thenReturn(Optional.of(mockUser)); - when(endorsementRepository.findById(endorsementId)).thenReturn(Optional.of(mockEndorsement)); - - // Call the service method - EndorsementListModel result = - endorsementListService.createEndorsementListEntry(endorsementListDRO); - - // Verify the interactions - verify(endorsementListRepository, times(1)).save(any(EndorsementListModel.class)); - - // Assertions - assertNotNull(result); - assertEquals(endorserId, result.getEndorserId()); - assertEquals(endorsementId, result.getEndorsement().getId()); - assertEquals("Test Description", result.getDescription()); - assertEquals(EndorsementType.POSITIVE, result.getType()); - } - - @Test - void testCreateEndorsementListEntryWithInvalidEndorsement() { - UUID endorserId = UUID.randomUUID(); - UUID endorsementId = UUID.randomUUID(); - EndorsementListDRO endorsementListDRO = new EndorsementListDRO(); - endorsementListDRO.setEndorserId(endorserId); - endorsementListDRO.setEndorsementId(endorsementId); - - UserModel mockUser = new UserModel(); - mockUser.setId(endorserId); - - // Mock the repository behavior for an invalid endorsement - when(userRepository.findById(endorserId)).thenReturn(Optional.of(mockUser)); - when(endorsementRepository.findById(endorsementId)).thenReturn(Optional.empty()); - - // Assert that a NoEntityException is thrown - NoEntityException exception = - assertThrows( - NoEntityException.class, - () -> endorsementListService.createEndorsementListEntry(endorsementListDRO)); - assertEquals("Endorsement with id:" + endorsementId + " not found", exception.getMessage()); - - // Verify that save method is not called - verify(endorsementListRepository, never()).save(any(EndorsementListModel.class)); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/unit/EndorsementServiceTest.java b/skill-tree/src/test/java/com/RDS/skilltree/unit/EndorsementServiceTest.java deleted file mode 100644 index 2ee4dcef..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/unit/EndorsementServiceTest.java +++ /dev/null @@ -1,746 +0,0 @@ -package com.RDS.skilltree.unit; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -import com.RDS.skilltree.Common.Response.GenericResponse; -import com.RDS.skilltree.Endorsement.*; -import com.RDS.skilltree.Exceptions.EntityAlreadyExistsException; -import com.RDS.skilltree.Exceptions.InvalidParameterException; -import com.RDS.skilltree.Exceptions.NoEntityException; -import com.RDS.skilltree.Skill.SkillModel; -import com.RDS.skilltree.Skill.SkillRepository; -import com.RDS.skilltree.User.UserModel; -import com.RDS.skilltree.User.UserRepository; -import com.RDS.skilltree.User.UserRole; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.persistence.EntityNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.time.Instant; -import java.time.LocalDateTime; -import java.util.*; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentMatchers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.util.ReflectionTestUtils; - -@ExtendWith(MockitoExtension.class) -public class EndorsementServiceTest { - @Mock private EndorsementRepository endorsementRepository; - - @Mock private UserRepository userRepository; - - @Mock private SkillRepository skillRepository; - - @Mock private ObjectMapper objectMapper; - - @InjectMocks @Autowired private EndorsementServiceImpl endorsementService; - - @Mock private Authentication auth; - - @BeforeEach - public void setUp() { - ReflectionTestUtils.setField( - endorsementService, "dummyEndorsementDataPath", "dummy-data/endorsements.json"); - } - - @AfterEach - public void clearSecurityContext() { - SecurityContextHolder.clearContext(); - } - - private void setupUpdateEndorsementTests(Boolean useSuperUserRole) { - UserModel userModel = new UserModel(); - if (useSuperUserRole) { - userModel.setRole(UserRole.SUPERUSER); - } else { - userModel.setRole(UserRole.USER); - } - when(auth.getPrincipal()).thenReturn(userModel); - SecurityContextHolder.getContext().setAuthentication(auth); - } - - @Test - public void itShouldGetEndorsementsById() { - UUID endorsementId = UUID.randomUUID(); - UUID endorserId = UUID.randomUUID(); - UUID skillId = UUID.randomUUID(); - - SkillModel skillModel = SkillModel.builder().id(skillId).build(); - EndorsementModel endorsementModel = - EndorsementModel.builder() - .id(endorsementId) - .endorseeId(endorserId) - .skill(skillModel) - .build(); - endorsementModel.setCreatedAt(Instant.now()); - endorsementModel.setUpdatedAt(Instant.now()); - endorsementModel.setCreatedBy(endorsementId); - endorsementModel.setUpdatedBy(endorsementId); - - when(endorsementRepository.findById(endorsementId)).thenReturn(Optional.of(endorsementModel)); - - EndorsementDTO result = endorsementService.getEndorsementById(endorsementId); - - assertNotNull(result); - assertEquals( - endorsementId, - result.getId(), - "The Endorsement Id doesn't matches the expected endorsement Id"); - } - - @Test - @DisplayName("Get endorsements given a valid skillID") - public void itShouldReturnEndorsementsGivenSkillID() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData(pageRequest, skillID, null); - - assertEquals(new PageImpl<>(dummyEndorsements, pageRequest, dummyEndorsements.size()), result); - assertEquals(1, result.getTotalElements()); - } - - @Test - @DisplayName("Get endorsements given a valid userID") - void itShouldGetEndorsementsGivenUserID() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "PENDING", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData(pageRequest, null, userID); - - assertEquals(new PageImpl<>(dummyEndorsements, pageRequest, dummyEndorsements.size()), result); - assertEquals(1, result.getTotalElements()); - } - - @Test - @DisplayName("Get endorsements given an invalid userID") - public void itShouldThrowErrorIfInvalidUserIDIsGiven() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "PENDING", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - - assertThrows( - IllegalArgumentException.class, - () -> - endorsementService.getEndorsementsFromDummyData(pageRequest, null, "invalid-user-id")); - } - - @Test - @DisplayName("Get endorsements given an invalid skillID") - public void itShouldThrowIllegalArgumentExceptionIfInvalidSkillIDIsGiven() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "PENDING", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - - assertThrows( - IllegalArgumentException.class, - () -> - endorsementService.getEndorsementsFromDummyData(pageRequest, "invalid-skill-id", null)); - } - - @Test - @DisplayName("Get endorsements given a valid userID and invalid skillID") - public void itShouldThrowIllegalArgumentExceptionIfInvalidUserIDIsGiven() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "PENDING", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - - assertThrows( - IllegalArgumentException.class, - () -> - endorsementService.getEndorsementsFromDummyData( - pageRequest, "invalid-skill-id", UUID.randomUUID().toString())); - } - - @Test - @DisplayName( - "Return paginated result having 2 pages when number of endorsements with a given userID is 15") - public void itShouldReturnPaginatedResultOnSearch() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - for (int i = 0; i < 15; i++) { - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.randomUUID(), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - } - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData(pageRequest, null, userID); - - assertEquals(2, result.getTotalPages()); - assertEquals(15, result.getTotalElements()); - } - - @Test - @DisplayName("Return empty page when accessing out of bound page in paginated result") - public void itShouldReturnEmptyPaginatedResultOnSearch() throws IOException { - PageRequest pageRequest = PageRequest.of(10, 10); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - for (int i = 0; i < 15; i++) { - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.randomUUID(), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - } - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData(pageRequest, null, userID); - - assertEquals(Page.empty(pageRequest), result); - } - - @Test - @DisplayName( - "Return empty endorsement list given a valid userID but skillID which is not present") - public void itShouldReturnEmptyDataGivenUserIDAndSkillIDNotPresent() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.randomUUID(), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - List endorsementsResult = new ArrayList<>(); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData(pageRequest, skillID, userID); - - assertEquals( - new PageImpl<>(endorsementsResult, pageRequest, endorsementsResult.size()), result); - assertEquals(0, result.getTotalElements()); - } - - @Test - @DisplayName("Return empty page when no endorsements are present in dummy data") - public void itShouldReturnEmptyDataWhenNoEndorsementsArePresent() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData(pageRequest, skillID, userID); - - assertEquals(Page.empty(pageRequest), result); - assertEquals(0, result.getTotalElements()); - } - - @Test - @DisplayName("Return empty endorsement list given a userID which is not present") - public void itShouldReturnEmptyDataGivenUserIDNotPresent() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - List endorsementsResult = new ArrayList<>(); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData( - pageRequest, null, UUID.randomUUID().toString()); - - assertEquals( - new PageImpl<>(endorsementsResult, pageRequest, endorsementsResult.size()), result); - assertEquals(0, result.getTotalElements()); - } - - @Test - @DisplayName("Return empty endorsement list given empty userID and skillID") - public void itShouldReturnEmptyDataGivenEmptyUserIDAndSkillID() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData(pageRequest, "", ""); - - assertEquals(new PageImpl<>(dummyEndorsements, pageRequest, dummyEndorsements.size()), result); - assertEquals(1, result.getTotalElements()); - } - - @Test - @DisplayName("Return empty endorsement list given a skillID which is not present") - public void itShouldReturnEmptyDataGivenSkillIDNotPresent() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - List endorsementsResult = new ArrayList<>(); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData( - pageRequest, UUID.randomUUID().toString(), null); - - assertEquals( - new PageImpl<>(endorsementsResult, pageRequest, endorsementsResult.size()), result); - assertEquals(0, result.getTotalElements()); - } - - @Test - @DisplayName("Return empty endorsement list given a skillID and userID which is not present") - public void itShouldReturnEmptyDataGivenSkillIDAndUserIDNotPresent() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = UUID.randomUUID().toString(); - String userID = UUID.randomUUID().toString(); - - List dummyEndorsements = new ArrayList<>(); - dummyEndorsements.add( - new EndorsementModelFromJSON( - UUID.randomUUID(), - UUID.fromString(userID), - UUID.fromString(skillID), - "APPROVED", - LocalDateTime.now(), - UUID.randomUUID(), - LocalDateTime.now(), - UUID.randomUUID())); - List endorsementsResult = new ArrayList<>(); - - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenReturn(dummyEndorsements); - Page result = - endorsementService.getEndorsementsFromDummyData( - pageRequest, UUID.randomUUID().toString(), UUID.randomUUID().toString()); - - assertEquals( - new PageImpl<>(endorsementsResult, pageRequest, endorsementsResult.size()), result); - assertEquals(0, result.getTotalElements()); - } - - @Test - @DisplayName("Return IO exception on error reading data") - void itShouldReturnIOExceptionIfErrorReadingData() throws IOException { - PageRequest pageRequest = PageRequest.of(0, 10); - String skillID = null; - String userID = null; - when(objectMapper.readValue( - ArgumentMatchers.any(), - ArgumentMatchers.>>any())) - .thenThrow(new IOException("Error reading data")); - - assertThrows( - IOException.class, - () -> endorsementService.getEndorsementsFromDummyData(pageRequest, skillID, userID)); - } - - @Test - public void itShouldHandleEndorsementNotFound() { - UUID nonExistentEndorsementId = UUID.randomUUID(); - when(endorsementRepository.findById(nonExistentEndorsementId)).thenReturn(Optional.empty()); - - EntityNotFoundException exception = - assertThrows( - EntityNotFoundException.class, - () -> endorsementService.getEndorsementById(nonExistentEndorsementId)); - - // Verify the exception message - assertEquals( - "No endorsement with the id " + nonExistentEndorsementId + " found", - exception.getMessage()); - } - - @Test - void testCreateEndorsement() { - // Mock data - UUID endorserId = UUID.randomUUID(); - UUID skillId = UUID.randomUUID(); - UUID endorsementId = UUID.randomUUID(); - EndorsementDRO endorsementDRO = new EndorsementDRO(); - endorsementDRO.setEndorseeId(endorserId); - endorsementDRO.setSkillId(skillId); - - SkillModel mockSkill = SkillModel.builder().id(skillId).build(); - EndorsementModel mockEndorsement = - EndorsementModel.builder() - .id(endorsementId) - .endorseeId(endorserId) - .skill(mockSkill) - .build(); - mockEndorsement.setCreatedAt(Instant.now()); - mockEndorsement.setUpdatedAt(Instant.now()); - mockEndorsement.setCreatedBy(endorserId); - mockEndorsement.setUpdatedBy(endorserId); - - // Mock the repository behavior - when(skillRepository.findById(skillId)).thenReturn(Optional.of(mockSkill)); - when(endorsementRepository.save(any(EndorsementModel.class))).thenReturn(mockEndorsement); - - // Call the service method - EndorsementModel result = endorsementService.createEndorsement(endorsementDRO); - - // Verify the interactions - verify(endorsementRepository, times(1)).save(any(EndorsementModel.class)); - - // Assertions - assertNotNull(result); - assertEquals(endorserId, result.getEndorseeId()); - assertEquals(skillId, result.getSkill().getId()); - } - - @Test - void testCreateEndorsementWithInvalidSkill() { - UUID endorserId = UUID.randomUUID(); - UUID skillId = UUID.randomUUID(); - EndorsementDRO endorsementDRO = new EndorsementDRO(); - endorsementDRO.setEndorseeId(endorserId); - endorsementDRO.setSkillId(skillId); - - // Mock the repository behavior for an invalid skill - when(skillRepository.findById(skillId)).thenReturn(Optional.empty()); - - // Assert that a NoEntityException is thrown - NoEntityException exception = - assertThrows( - NoEntityException.class, () -> endorsementService.createEndorsement(endorsementDRO)); - assertEquals("Skill with id:" + skillId + " not found", exception.getMessage()); - - // Verify that save method is not called - verify(endorsementRepository, never()).save(any(EndorsementModel.class)); - } - - @Test - @DisplayName( - "Return unauthorized access, given user is not a super user to update endorsement status") - public void itShouldReturnUnauthorizedGivenUserIsNotSuperUser() { - setupUpdateEndorsementTests(false); - - UUID endorsementId = UUID.randomUUID(); - String status = EndorsementStatus.APPROVED.name(); - - AccessDeniedException exception = - assertThrows( - AccessDeniedException.class, - () -> endorsementService.updateEndorsementStatus(endorsementId, status)); - assertEquals("Unauthorized, Access is only available to super users", exception.getMessage()); - verify(endorsementRepository, never()).save(any(EndorsementModel.class)); - } - - @Test - @DisplayName("Return invalid status given status is pending") - public void itShouldReturnInvalidStatusGivenEndorsementStatusIsPending() { - setupUpdateEndorsementTests(true); - - UUID endorsementId = UUID.randomUUID(); - String status = EndorsementStatus.PENDING.name(); - - InvalidParameterException exception = - assertThrows( - InvalidParameterException.class, - () -> endorsementService.updateEndorsementStatus(endorsementId, status)); - assertEquals("Invalid parameter endorsement status: " + status, exception.getMessage()); - verify(endorsementRepository, never()).save(any(EndorsementModel.class)); - } - - @Test - @DisplayName("Return invalid status given status is invalid") - public void itShouldReturnInvalidStatusGivenInvalidEndorsementStatus() { - setupUpdateEndorsementTests(true); - - UUID endorsementId = UUID.randomUUID(); - String status = "invalid-status"; - - InvalidParameterException exception = - assertThrows( - InvalidParameterException.class, - () -> endorsementService.updateEndorsementStatus(endorsementId, status)); - assertEquals("Invalid parameter endorsement status: " + status, exception.getMessage()); - verify(endorsementRepository, never()).save(any(EndorsementModel.class)); - } - - @Test - @DisplayName("Return cannot modify status given status is already updated") - public void itShouldThrowEntityAlreadyExistsExceptionGivenEndorsementIsUpdated() { - setupUpdateEndorsementTests(true); - - UUID endorseeId = UUID.randomUUID(); - UUID skillId = UUID.randomUUID(); - UUID endorsementId = UUID.randomUUID(); - - SkillModel mockSkill = SkillModel.builder().id(skillId).build(); - EndorsementModel mockEndorsement = - EndorsementModel.builder() - .id(endorsementId) - .status(EndorsementStatus.APPROVED) - .endorseeId(endorseeId) - .skill(mockSkill) - .build(); - mockEndorsement.setCreatedAt(Instant.now()); - mockEndorsement.setUpdatedAt(Instant.now()); - mockEndorsement.setCreatedBy(endorseeId); - mockEndorsement.setUpdatedBy(endorseeId); - - when(endorsementRepository.findById(endorsementId)).thenReturn(Optional.of(mockEndorsement)); - - EntityAlreadyExistsException exception = - assertThrows( - EntityAlreadyExistsException.class, - () -> - endorsementService.updateEndorsementStatus( - endorsementId, EndorsementStatus.APPROVED.name())); - assertEquals("Endorsement is already updated. Cannot modify status", exception.getMessage()); - verify(endorsementRepository, never()).save(any(EndorsementModel.class)); - } - - @Test - @DisplayName("Return endorsement not found given an unknown endorsement id") - public void itShouldReturnEndorsementNotFoundGivenUnknownEndorsementId() { - setupUpdateEndorsementTests(true); - - UUID nonExistentEndorsementId = UUID.randomUUID(); - String status = EndorsementStatus.APPROVED.name(); - - when(endorsementRepository.findById(nonExistentEndorsementId)).thenReturn(Optional.empty()); - - NoEntityException exception = - assertThrows( - NoEntityException.class, - () -> endorsementService.updateEndorsementStatus(nonExistentEndorsementId, status)); - assertEquals( - "No endorsement with id " + nonExistentEndorsementId + " was found", - exception.getMessage()); - verify(endorsementRepository, never()).save(any(EndorsementModel.class)); - } - - @Test - @DisplayName( - "Update endorsement status given a valid endorsement id and status is approved or rejected") - public void itShouldUpdateEndorsementStatusGivenEndorsementIdAndStatusApprovedOrRejected() { - setupUpdateEndorsementTests(true); - - UUID endorseeId = UUID.randomUUID(); - UUID skillId = UUID.randomUUID(); - UUID endorsementId = UUID.randomUUID(); - EndorsementStatus status = EndorsementStatus.APPROVED; - - SkillModel mockSkill = SkillModel.builder().id(skillId).build(); - EndorsementModel mockEndorsement = - EndorsementModel.builder() - .id(endorsementId) - .status(EndorsementStatus.PENDING) - .endorseeId(endorseeId) - .skill(mockSkill) - .build(); - mockEndorsement.setCreatedAt(Instant.now()); - mockEndorsement.setUpdatedAt(Instant.now()); - mockEndorsement.setCreatedBy(endorseeId); - mockEndorsement.setUpdatedBy(endorseeId); - - when(endorsementRepository.findById(endorsementId)).thenReturn(Optional.of(mockEndorsement)); - - GenericResponse result = - endorsementService.updateEndorsementStatus(endorsementId, status.name()); - assertEquals("Successfully updated endorsement status", result.getMessage()); - - verify(endorsementRepository, times(1)).save(any(EndorsementModel.class)); - - EndorsementModel updatedMockEndorsement = - EndorsementModel.builder() - .id(endorsementId) - .endorseeId(endorseeId) - .skill(mockSkill) - .status(EndorsementStatus.APPROVED) - .build(); - mockEndorsement.setCreatedAt(Instant.now()); - mockEndorsement.setUpdatedAt(Instant.now()); - mockEndorsement.setCreatedBy(endorseeId); - mockEndorsement.setUpdatedBy(endorseeId); - - when(endorsementRepository.findById(endorsementId)) - .thenReturn(Optional.of(updatedMockEndorsement)); - Optional updatedEndorsement = endorsementRepository.findById(endorsementId); - assertTrue(updatedEndorsement.isPresent()); - assertEquals(EndorsementStatus.APPROVED, updatedEndorsement.get().getStatus()); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/unit/HealthCheckTest.java b/skill-tree/src/test/java/com/RDS/skilltree/unit/HealthCheckTest.java deleted file mode 100644 index a10ae257..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/unit/HealthCheckTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.RDS.skilltree.unit; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -import com.RDS.skilltree.Health.HealthCheckController; -import com.RDS.skilltree.metrics.MetricService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -class HealthCheckTest { - - @Mock private MetricService metricService; - - private HealthCheckController healthCheckController; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - healthCheckController = new HealthCheckController(metricService); - } - - @Test - void checkHealth() { - // Setup - when(metricService.getUptime()).thenReturn(123.0); - - // Execute - var result = healthCheckController.checkHealth(); - - // Assert - assertNotNull(result); - assertTrue(result.containsKey("uptimeInSeconds")); - assertEquals(123.0, result.get("uptimeInSeconds")); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/unit/SkillsServiceTest.java b/skill-tree/src/test/java/com/RDS/skilltree/unit/SkillsServiceTest.java deleted file mode 100644 index bf5e0bf8..00000000 --- a/skill-tree/src/test/java/com/RDS/skilltree/unit/SkillsServiceTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.RDS.skilltree.unit; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import com.RDS.skilltree.Skill.SkillDTO; -import com.RDS.skilltree.Skill.SkillModel; -import com.RDS.skilltree.Skill.SkillRepository; -import com.RDS.skilltree.Skill.SkillsServiceImpl; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; - -@ExtendWith(MockitoExtension.class) -public class SkillsServiceTest { - - @Mock private SkillRepository skillRepository; - - @InjectMocks @Autowired private SkillsServiceImpl skillService; - - @Test - public void testGetSkillById() { - UUID skillId = UUID.randomUUID(); - SkillModel skillModel = SkillModel.builder().id(skillId).build(); - - when(skillRepository.findById(skillId)).thenReturn(Optional.of(skillModel)); - - SkillDTO result = skillService.getSkillById(skillId); - assertNotNull(result); - assertEquals(skillId, result.getId(), "The skill Id doesn't matches the expected skillId"); - } - - @Test - public void testGetSkillsByName() { - String skillName = "Java"; - SkillModel skillModel = SkillModel.builder().name("Java").build(); - - when(skillRepository.findByName(skillName)).thenReturn(Optional.of(skillModel)); - - SkillDTO result = skillService.getSkillByName("Java"); - assertNotNull(result); - assertEquals( - skillName, result.getName(), "The skill name doesn't match the expected skill name"); - } - - @Test - public void testGetAllSkills() { - SkillModel skillJava = SkillModel.builder().name("Java").build(); - - SkillModel skillGo = SkillModel.builder().name("Go").build(); - - List skillModelList = Arrays.asList(skillJava, skillGo); - - when(skillRepository.findAll((Pageable) any(Pageable.class))) - .thenReturn(new PageImpl<>(skillModelList)); - - Pageable pageable = PageRequest.of(2, 1); - Page resultPage = skillService.getAllSkills(pageable); - assertNotNull(resultPage); - assertEquals( - skillModelList.size(), - resultPage.getTotalElements(), - "The number of elements returned is not equal to the expected size"); - assertEquals( - skillModelList.size(), - resultPage.getContent().size(), - "The content returned is not equal to the expected content"); - assertEquals( - "Java", - resultPage.getContent().get(0).getName(), - "The returned skill on page 0 doesn't match the actual skill"); - assertEquals( - "Go", - resultPage.getContent().get(1).getName(), - "The returned skill on page 0 doesn't match the actual skill"); - } -} diff --git a/skill-tree/src/test/java/com/RDS/skilltree/utils/UUIDValidationInterceptorTest.java b/skill-tree/src/test/java/com/RDS/skilltree/utils/UUIDValidationInterceptorTest.java index a02c5d16..f17e6cc1 100644 --- a/skill-tree/src/test/java/com/RDS/skilltree/utils/UUIDValidationInterceptorTest.java +++ b/skill-tree/src/test/java/com/RDS/skilltree/utils/UUIDValidationInterceptorTest.java @@ -1,9 +1,10 @@ package com.RDS.skilltree.utils; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; -import com.RDS.skilltree.Exceptions.InvalidParameterException; +import com.RDS.skilltree.exceptions.InvalidParameterException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.UUID;