diff --git a/.DS_Store b/.DS_Store
index 2f9f1cb..60c2602 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 461627c..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*.*
-/Status
-
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 73f69e0..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index f5cb871..0000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index a55e7a1..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index a02af30..5908545 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -8,44 +8,20 @@
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 215261b..6fc1291 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,16 +1,10 @@
-
-
-
-
-
-
-
-
+
+
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index e20a4a2..084e3f5 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -1,11 +1,6 @@
-
-
-
-
-
@@ -16,16 +11,16 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/.idea/jpa-buddy.xml b/.idea/jpa.xml
similarity index 77%
rename from .idea/jpa-buddy.xml
rename to .idea/jpa.xml
index d08f400..898e07a 100644
--- a/.idea/jpa-buddy.xml
+++ b/.idea/jpa.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7befe12..67e1e61 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,18 +5,8 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..0b89b12
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "customColor": "",
+ "associatedIndex": 6
+}
+
+
+
+
+
+
+ {
+ "keyToString": {
+ "Application.CourseCompositeServiceApplication.executor": "Run",
+ "Application.CourseServiceApplication.executor": "Run",
+ "Application.GatewayServiceApplication.executor": "Run",
+ "Application.ReviewServiceApplication.executor": "Run",
+ "Maven.course-service [compile].executor": "Run",
+ "Maven.course-service [package].executor": "Run",
+ "Maven.util [install].executor": "Run",
+ "Maven.util [package].executor": "Run",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "SHARE_PROJECT_CONFIGURATION_FILES": "true",
+ "git-widget-placeholder": "revamp",
+ "kotlin-language-version-configured": "true",
+ "last_opened_file_path": "/Users/maersk-mtc-79/Nasir/Personal/Codebase/microservices-example/spring-boot-update/spring-boot-based-microservices/microservices/review-service",
+ "settings.editor.selected.configurable": "terminal"
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1740978637962
+
+
+ 1740978637962
+
+
+
+ 1741156956411
+
+
+
+ 1741156956411
+
+
+
+ 1741173754129
+
+
+
+ 1741173754129
+
+
+
+ 1741240420942
+
+
+
+ 1741240420942
+
+
+
+ 1741325074203
+
+
+
+ 1741325074203
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$PROJECT_DIR$/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewController.java
+ 67
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100644
index c1dd12f..0000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index b7cb93e..d58dfb7 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,2 +1,19 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0be1c0c
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "java.configuration.updateBuildConfiguration": "automatic",
+ "java.compile.nullAnalysis.mode": "automatic"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index 830ebda..0000000
--- a/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# spring-boot-based-microservices
-
-Basic skeleton for Spring Boot Microservices. It includes spring security for basic Auth. Spring cloud gateway is also implemented as an Edge Service. Lots of the spring cloud component integrated.
-
-# How to run
-
-- Navigate to root of the project
-```
- cd spring-boot-based-microservices
-```
-- Build the project
-```
- mvn clean package -DskipTests
-```
-
-
-- Locate the docker directory from the root directory and run docker compose to startup the containers
-```
- cd docker && docker compose up --build
-```
-
-
-- Check if all our services are running and healthy
-```
- docker ps
-```
-
-
-- Let's check if all the services are up and running. We will reach to eureka server using gateway.
-Open the browser and hit http://localhost:8443/eureka/web You will need to authenticate yourself before accessing the endpoint.
-```
-username : user
-password : password
-```
-
-
-- Now, we have a look at our gateway endpoints configurations as well. Hit http://localhost:8443/actuator/gateway/routes in the browser again and, you should be able to find all the routes configured.
-
-
-
-- Coming to swagger/openapi specs, here is the address to access them - http://localhost:8443/openapi/swagger-ui.html
-
-
-- Please use the below curl to generate the access token with both read and write scope.
-```
-curl -k http://writer:secret-writer@localhost:8443/oauth2/token -d grant_type=client_credentials -d scope="course:read course:write"
-```
-
-
-
-## Note : Currently this project uses Spring Authorization server. Keep in mind, I'm planning to enhance the whole OAuth flow because it's in shaky state right now and, you may some face issues. We may also move to keycloak.
-## These instructions are basic starting point and, the project does lot of other stuff like elastic and mongo configuration etc. Please feel free to play around. I really want to maintain this and keep up-to-date, however my current role doesn't give me enough spare time. Therefore, contributors and their contributions are welcome :)
\ No newline at end of file
diff --git a/Tiltfile b/Tiltfile
new file mode 100644
index 0000000..35e4bb1
--- /dev/null
+++ b/Tiltfile
@@ -0,0 +1,82 @@
+# Define and build course-service
+docker_build(
+ "course-service",
+ context="./microservices/course-service",
+ dockerfile="./microservices/course-service/Dockerfile",
+ live_update=[
+ sync("./microservices/course-service/src", "/application/src"), # Sync only Java files
+ run("mvn package -DskipTests", trigger=["/application/src"]), # Rebuild JAR when code changes
+ ]
+)
+k8s_yaml([
+ "microservices/course-service/kubernetes/deployment.yml",
+ "microservices/course-service/kubernetes/service.yml"
+])
+k8s_resource(
+ "course-service",
+ port_forwards="9001:9001", # Maps host port 9001 to container port 9001
+ labels=["services"] # Optional: Group in Tilt UI
+)
+
+# Define and build review-service
+docker_build(
+ "review-service",
+ context="./microservices/review-service",
+ dockerfile="./microservices/review-service/Dockerfile",
+ live_update=[
+ sync("./microservices/review-service/src", "/application/src"), # Sync only Java files
+ run("mvn package -DskipTests", trigger=["/application/src"]), # Rebuild JAR when code changes
+ ]
+)
+k8s_yaml([
+ "microservices/review-service/kubernetes/deployment.yml",
+ "microservices/review-service/kubernetes/service.yml"
+])
+k8s_resource(
+ "review-service",
+ port_forwards="9002:9002",
+ labels=["services"]
+)
+
+# Define and build course-aggregate-service
+docker_build(
+ "course-composite-service",
+ context="./microservices/course-composite-service",
+ dockerfile="./microservices/course-composite-service/Dockerfile",
+ live_update=[
+ sync("./microservices/course-composite-service/src", "/application/src"), # Sync only Java files
+ run("mvn package -DskipTests", trigger=["/application/src"]), # Rebuild JAR when code changes
+ ]
+)
+k8s_yaml([
+ "microservices/course-composite-service/kubernetes/deployment.yml",
+ "microservices/course-composite-service/kubernetes/service.yml"
+])
+k8s_resource(
+ "course-composite-service",
+ port_forwards="5000:5000",
+ labels=["services"]
+)
+
+
+# Define and build gateway-service
+docker_build(
+ "gateway-service",
+ context="./spring-cloud/gateway-service",
+ dockerfile="./spring-cloud/gateway-service/Dockerfile",
+ live_update=[
+ sync("./spring-cloud/gateway-service/src", "/application/src"), # Sync only Java files
+ run("mvn package -DskipTests", trigger=["/application/src"]), # Rebuild JAR when code changes
+ ]
+)
+k8s_yaml([
+ "spring-cloud/gateway-service/kubernetes/deployment.yml",
+ "spring-cloud/gateway-service/kubernetes/service.yml",
+ "spring-cloud/gateway-service/kubernetes/ingress.yml"
+])
+
+k8s_resource(
+ "gateway-service",
+# port_forwards="9000:9000", # Maps host port 9000 to container port 9000
+ labels=["services"] # Optional: Group in Tilt UI
+)
\ No newline at end of file
diff --git a/api/pom.xml b/api/pom.xml
deleted file mode 100644
index b3db193..0000000
--- a/api/pom.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.4.3
-
-
- io.javatab.microservices.api
- api
- 1.0.0
- jar
- api
- Demo project for Spring Boot
-
- 17
-
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springdoc
- springdoc-openapi-common
- 1.5.10
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
- repackage
- none
-
- repackage
-
-
-
-
-
-
-
-
diff --git a/api/src/main/java/io/javatab/microservices/api/ApiApplication.java b/api/src/main/java/io/javatab/microservices/api/ApiApplication.java
deleted file mode 100644
index 1c3ed2e..0000000
--- a/api/src/main/java/io/javatab/microservices/api/ApiApplication.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.javatab.microservices.api;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class ApiApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(ApiApplication.class, args);
- }
-
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/composite/course/CourseAggregate.java b/api/src/main/java/io/javatab/microservices/api/composite/course/CourseAggregate.java
deleted file mode 100644
index df32535..0000000
--- a/api/src/main/java/io/javatab/microservices/api/composite/course/CourseAggregate.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package io.javatab.microservices.api.composite.course;
-
-import io.javatab.microservices.api.core.student.Student;
-
-import java.util.List;
-
-public record CourseAggregate(int courseId, int like, int dislike, int registeredUserNumber, List registerUserDetails) {
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/composite/course/CourseCompositeService.java b/api/src/main/java/io/javatab/microservices/api/composite/course/CourseCompositeService.java
deleted file mode 100644
index c31329c..0000000
--- a/api/src/main/java/io/javatab/microservices/api/composite/course/CourseCompositeService.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package io.javatab.microservices.api.composite.course;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.responses.ApiResponses;
-import io.swagger.v3.oas.annotations.security.SecurityRequirement;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-import reactor.core.publisher.Mono;
-
-@SecurityRequirement(name = "security_auth")
-@Tag(name = "CourseComposite", description = "REST API for composite course information.")
-public interface CourseCompositeService {
-
-// TODO:: Update API definitions
- /**
- * Sample usage: "curl $HOST:$PORT/course-composite/1".
- *
- * @param courseId of the course
- * @return the composite course info, if found, else null
- */
- @Operation(
- summary = "${open-api.course-composite.get-composite-course.description}",
- description = "${open-api.course-composite.get-composite-course.notes}")
- @ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "${open-api.responseCodes.ok.description}"),
- @ApiResponse(responseCode = "400", description = "${open-api.responseCodes.badRequest.description}"),
- @ApiResponse(responseCode = "404", description = "${open-api.responseCodes.notFound.description}"),
- @ApiResponse(responseCode = "422", description = "${open-api.responseCodes.unprocessableEntity.description}")
- })
- @ResponseStatus(HttpStatus.ACCEPTED)
- @PostMapping("/course-composite")
- Mono createProduct(@RequestBody CourseAggregate body);
-
-
- /**
- * Sample usage: "curl $HOST:$PORT/course-composite/1".
- *
- * @param courseId of the course
- * @return the composite course info, if found, else null
- */
- @Operation(
- summary = "${open-api.course-composite.get-composite-course.description}",
- description = "${open-api.course-composite.get-composite-course.notes}")
- @ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "${open-api.responseCodes.ok.description}"),
- @ApiResponse(responseCode = "400", description = "${open-api.responseCodes.badRequest.description}"),
- @ApiResponse(responseCode = "404", description = "${open-api.responseCodes.notFound.description}"),
- @ApiResponse(responseCode = "422", description = "${open-api.responseCodes.unprocessableEntity.description}")
- })
- @GetMapping("/course-composite/{courseId}")
- Mono getCourse(@PathVariable("courseId") int courseId);
-
- /**
- * Sample usage: "curl $HOST:$PORT/course-composite/1".
- *
- * @param courseId of the course
- * @return the composite course info, if found, else null
- */
- @Operation(
- summary = "${open-api.course-composite.get-composite-course.description}",
- description = "${open-api.course-composite.get-composite-course.notes}")
- @ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "${open-api.responseCodes.ok.description}"),
- @ApiResponse(responseCode = "400", description = "${open-api.responseCodes.badRequest.description}"),
- @ApiResponse(responseCode = "404", description = "${open-api.responseCodes.notFound.description}"),
- @ApiResponse(responseCode = "422", description = "${open-api.responseCodes.unprocessableEntity.description}")
- })
- @ResponseStatus(HttpStatus.ACCEPTED)
- @DeleteMapping("/course-composite/{courseId}")
- Mono deleteCourse(@PathVariable("courseId") int courseId);
-
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/course/Course.java b/api/src/main/java/io/javatab/microservices/api/core/course/Course.java
deleted file mode 100644
index 6db2670..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/course/Course.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.javatab.microservices.api.core.course;
-
-public record Course(int courseId, String courseName, String author, String content, int voteId) {
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/course/CourseService.java b/api/src/main/java/io/javatab/microservices/api/core/course/CourseService.java
deleted file mode 100644
index 4e9746b..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/course/CourseService.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.javatab.microservices.api.core.course;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import reactor.core.publisher.Mono;
-
-public interface CourseService {
-
- @GetMapping("/course/{courseId}")
- Mono getCourse(@PathVariable(value = "courseId", required = true) int courseId);
-
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/search/SearchRecord.java b/api/src/main/java/io/javatab/microservices/api/core/search/SearchRecord.java
deleted file mode 100644
index 7139b2b..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/search/SearchRecord.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.javatab.microservices.api.core.search;
-
-public record SearchRecord(int courseId, String courseName, int like, int dislike) {
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/search/SearchService.java b/api/src/main/java/io/javatab/microservices/api/core/search/SearchService.java
deleted file mode 100644
index 894d711..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/search/SearchService.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javatab.microservices.api.core.search;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import reactor.core.publisher.Mono;
-
-public interface SearchService {
-
- @GetMapping("/search")
- Mono getCourse(@RequestParam("courseName") String courseName);
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/student/Student.java b/api/src/main/java/io/javatab/microservices/api/core/student/Student.java
deleted file mode 100644
index 679f831..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/student/Student.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.javatab.microservices.api.core.student;
-
-public record Student(int studentId, String studentName, String email, String password) {
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/student/StudentService.java b/api/src/main/java/io/javatab/microservices/api/core/student/StudentService.java
deleted file mode 100644
index ecaff0d..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/student/StudentService.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javatab.microservices.api.core.student;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import reactor.core.publisher.Mono;
-
-public interface StudentService {
-
- @GetMapping("/student/{studentId}")
- Mono getStudent(@PathVariable("studentId") String studentId);
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/vote/Vote.java b/api/src/main/java/io/javatab/microservices/api/core/vote/Vote.java
deleted file mode 100644
index e14432a..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/vote/Vote.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.javatab.microservices.api.core.vote;
-
-public record Vote(int courseId, int studentId, int like, int dislike) {
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/core/vote/VoteService.java b/api/src/main/java/io/javatab/microservices/api/core/vote/VoteService.java
deleted file mode 100644
index bad21b3..0000000
--- a/api/src/main/java/io/javatab/microservices/api/core/vote/VoteService.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javatab.microservices.api.core.vote;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import reactor.core.publisher.Mono;
-
-public interface VoteService {
-
- @GetMapping("/vote/{courseId}")
- Mono getVote(@PathVariable("courseId") int courseId);
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/exceptions/InvalidInputException.java b/api/src/main/java/io/javatab/microservices/api/exceptions/InvalidInputException.java
deleted file mode 100644
index a6ad110..0000000
--- a/api/src/main/java/io/javatab/microservices/api/exceptions/InvalidInputException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package io.javatab.microservices.api.exceptions;
-
-public class InvalidInputException extends RuntimeException {
- public InvalidInputException() {}
-
- public InvalidInputException(String message) {
- super(message);
- }
-
- public InvalidInputException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public InvalidInputException(Throwable cause) {
- super(cause);
- }
-}
diff --git a/api/src/main/java/io/javatab/microservices/api/exceptions/NotFoundException.java b/api/src/main/java/io/javatab/microservices/api/exceptions/NotFoundException.java
deleted file mode 100644
index f459c0b..0000000
--- a/api/src/main/java/io/javatab/microservices/api/exceptions/NotFoundException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package io.javatab.microservices.api.exceptions;
-
-public class NotFoundException extends RuntimeException {
- public NotFoundException() {}
-
- public NotFoundException(String message) {
- super(message);
- }
-
- public NotFoundException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public NotFoundException(Throwable cause) {
- super(cause);
- }
-}
diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties
deleted file mode 100644
index 8b13789..0000000
--- a/api/src/main/resources/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/build-images.sh b/build-images.sh
new file mode 100644
index 0000000..80e23ca
--- /dev/null
+++ b/build-images.sh
@@ -0,0 +1,23 @@
+echo "Building Docker images for Kubernetes using Minikube..."
+
+# Define an array of services
+SERVICES=(
+ "microservices/course-composite-service:course-composite-service"
+ "microservices/course-service:course-service"
+ "microservices/review-service:review-service"
+ "spring-cloud/gateway-service:gateway-service"
+)
+
+eval $(minikube docker-env --profile microservice-deployment)
+
+# Iterate over services and build each one
+for SERVICE in "${SERVICES[@]}"; do
+ IFS=":" read -r DIR IMAGE <<< "$SERVICE"
+ echo "Building $IMAGE..."
+ cd "$DIR" || { echo "Failed to enter directory $DIR"; exit 1; }
+ docker build -t "$IMAGE" .
+ cd - >/dev/null || exit 1
+ echo "$IMAGE built successfully!"
+done
+
+echo "All images built successfully!"
diff --git a/config-repo/application.yml b/config-repo/application.yml
deleted file mode 100644
index f25c883..0000000
--- a/config-repo/application.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-app:
- eureka-username: user
- eureka-password: password
- eureka-server: localhost
- auth-server: localhost
-
-eureka:
- client:
- serviceUrl:
- defaultZone: "http://${app.eureka-username}:${app.eureka-password}@${app.eureka-server}:8761/eureka/"
- initialInstanceInfoReplicationIntervalSeconds: 5
- registryFetchIntervalSeconds: 5
- instance:
- leaseRenewalIntervalInSeconds: 5
- leaseExpirationDurationInSeconds: 5
-
-# WARNING: Exposing all management endpoints over http should only be used during development, must be locked down in production!
-management.endpoint.health.show-details: "ALWAYS"
-management.endpoints.web.exposure.include: "*"
-
----
-spring.config.activate.on-profile: docker
-app:
- eureka-server: eureka
- auth-server: auth-server
diff --git a/config-repo/auth-server.yml b/config-repo/auth-server.yml
deleted file mode 100644
index 880529e..0000000
--- a/config-repo/auth-server.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-server.port: 9999
-server.forward-headers-strategy: framework
\ No newline at end of file
diff --git a/config-repo/course-composite.yml b/config-repo/course-composite.yml
deleted file mode 100644
index 0d6e585..0000000
--- a/config-repo/course-composite.yml
+++ /dev/null
@@ -1,102 +0,0 @@
-server.port: 8016
-spring.application.name: course-composite
-
-springdoc:
- swagger-ui.path: /openapi/swagger-ui.html
- api-docs.path: /openapi/v3/api-docs
- packagesToScan: io.javatab.microservices.composite.course
- pathsToMatch: /**
- cache:
- disabled: true
- swagger-ui:
- oauth2-redirect-url: http://localhost:8443/webjars/swagger-ui/oauth2-redirect.html
- oauth:
- clientId: writer
- clientSecret: secret
- useBasicAuthenticationWithAccessCodeGrant: true
- oAuthFlow:
- authorizationUrl: http://localhost:8443/oauth2/authorize
- tokenUrl: http://localhost:8443/oauth2/token
-
-server.forward-headers-strategy: framework
-
-open-api:
- version: 1.0.0
- title: Course Composite API
- description: Composite API to fetch data from multiple microservices
- termsOfService:
- license:
- licenseUrl:
- externalDocDesc:
- externalDocUrl:
- contact:
- name: Nasruddin
- url:
- email: webo.geeky@mail.com
-
- responseCodes:
- ok.description: OK
- badRequest.description: Bad Request, invalid format of the request. See response message for more information
- notFound.description: Not found, the specified id does not exist
- unprocessableEntity.description: Unprocessable entity, input parameters caused the processing to fail. See response message for more information
-
- course-composite:
- get-composite-course:
- description: Returns a composite view of the specified course id
- notes: |
- # Normal response
- If the requested course id is found the method will return information regarding:
- 1. Base course information
- 1. Student subscribed
- 1. Vote
- 1. Service Addresses\n(technical information regarding the addresses of the microservices that created the response)
-
- # Expected partial and error responses
- In the following cases, only a partial response be created (used to simplify testing of error conditions)
-
- ## Course id 113
- 200 - Ok, but no students will be returned
-
- ## Non numerical course id
- 400 - A **Bad Request** error will be returned
-
- ## course id 13
- 404 - A **Not Found** error will be returned
-
- ## Negative course ids
- 422 - An **Unprocessable Entity** error will be returned
-
-app:
- eureka-username: user
- eureka-password: password
- eureka-server: localhost
- auth-server: localhost
-
-eureka:
- client:
- serviceUrl:
- defaultZone: "http://${app.eureka-username}:${app.eureka-password}@${app.eureka-server}:8761/eureka/"
-
- initialInstanceInfoReplicationIntervalSeconds: 5
- registryFetchIntervalSeconds: 5
- instance:
- leaseRenewalIntervalInSeconds: 5
- leaseExpirationDurationInSeconds: 5
-
-spring.security.oauth2.resourceserver.jwt.issuer-uri: http://${app.auth-server}:9999
-
-logging:
- level:
- root: INFO
- se.magnus: DEBUG
- org.springframework.web.server.adapter.HttpWebHandlerAdapter: TRACE
-
-management.endpoint.health.show-details: "ALWAYS"
-management.endpoints.web.exposure.include: "*"
-
----
-spring.config.activate.on-profile: docker
-server.port: 8080
-app:
- eureka-server: eureka
- auth-server: auth-server
diff --git a/config-repo/course.yml b/config-repo/course.yml
deleted file mode 100644
index 84070b8..0000000
--- a/config-repo/course.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-server.port: 8012
-spring.application.name: course
-logging:
- level:
- root: INFO
- io.javatab.microservices: DEBUG
-
-app:
- eureka-username: user
- eureka-password: password
- eureka-server: localhost
-
-eureka:
- client:
- serviceUrl:
- defaultZone: "http://${app.eureka-username}:${app.eureka-password}@${app.eureka-server}:8761/eureka/"
- initialInstanceInfoReplicationIntervalSeconds: 5
- registryFetchIntervalSeconds: 5
- instance:
- leaseRenewalIntervalInSeconds: 5
- leaseExpirationDurationInSeconds: 5
-
----
-spring.config.activate.on-profile: docker
-server.port: 8080
-app.eureka-server: eureka
-spring.data.mongodb.host: mongodb
\ No newline at end of file
diff --git a/config-repo/eureka-server.yml b/config-repo/eureka-server.yml
deleted file mode 100644
index 08ce63d..0000000
--- a/config-repo/eureka-server.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-server:
- port: 8761
-
-eureka:
- instance:
- hostname: localhost
- client:
- registerWithEureka: false
- fetchRegistry: false
- serviceUrl:
- defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- # from: https://github.com/spring-cloud-samples/eureka/blob/master/src/main/resources/application.yml
- server:
- waitTimeInMsWhenSyncEmpty: 0
- response-cache-update-interval-ms: 5000
-
-
diff --git a/config-repo/gateway.yml b/config-repo/gateway.yml
deleted file mode 100644
index cf2eb2b..0000000
--- a/config-repo/gateway.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-server.port: 8443
-test1.data: helloupdate
-logging:
- level:
- root: INFO
- org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator: INFO
- org.springframework.cloud.gateway: TRACE
- org.springframework.web.server.adapter.HttpWebHandlerAdapter: TRACE
-
-spring.cloud.gateway.routes:
- - id: course-composite
- uri: lb://course-composite
- predicates:
- - Path=/course-composite/**
-
- - id: oauth2-server
- uri: lb://auth-server
- predicates:
- - Path=/oauth2/**
-
- - id: oauth2-login
- uri: lb://auth-server
- predicates:
- - Path=/login/**
-
- - id: oauth2-error
- uri: lb://auth-server
- predicates:
- - Path=/error/**
-
- - id: course-composite-swagger-ui
- uri: lb://course-composite
- predicates:
- - Path=/openapi/**
-
- - id: course-composite-swagger-ui-webjars
- uri: lb://course-composite
- predicates:
- - Path=/webjars/**
-
- - id: eureka-api
- uri: http://${app.eureka-server}:8761
- predicates:
- - Path=/eureka/api/{segment}
- filters:
- - SetPath=/eureka/{segment}
-
- - id: eureka-web-start
- uri: http://${app.eureka-server}:8761
- predicates:
- - Path=/eureka/web
- filters:
- - SetPath=/
-
- - id: eureka-web-other
- uri: http://${app.eureka-server}:8761
- predicates:
- - Path=/eureka/**
- - id: config-server
- uri: ${spring.cloud.config.uri}
- predicates:
- - Path=/config/**
- filters:
- - RewritePath=/config/(?.*), /$\{segment}
-
-spring.security.oauth2.resourceserver.jwt.issuer-uri: http://${app.auth-server}:9999
-
-
-management.endpoint.gateway.enabled: true
-management.endpoints.web.exposure.include: "*"
-
-#server.ssl:
-# key-store-type: PKCS12
-# key-store: classpath:keystore/edge.p12
-# key-store-password: password
-# key-alias: localhost
\ No newline at end of file
diff --git a/config-repo/search.yml b/config-repo/search.yml
deleted file mode 100644
index 874c517..0000000
--- a/config-repo/search.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-server.port: 8015
-
----
-spring.config.activate.on-profile: docker
-server.port: 8080
-spring.elasticsearch:
- uris: http://elasticsearch:9200
\ No newline at end of file
diff --git a/config-repo/student.yml b/config-repo/student.yml
deleted file mode 100644
index 45339d0..0000000
--- a/config-repo/student.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-server.port: 8013
-spring:
- application:
- name: student
- datasource:
- driver-class-name: org.postgresql.Driver
- username: user
- password: pwd
- url: jdbc:postgresql://localhost:5432/test
- jpa:
- database-platform: org.hibernate.dialect.PostgreSQLDialect
- hibernate:
- ddl-auto: update
-
-
----
-spring.config.activate.on-profile: docker
-server.port: 8080
-app.eureka-server: eureka
-spring.datasource.url: jdbc:postgresql://postgres:5432/test
diff --git a/config-repo/vote.yml b/config-repo/vote.yml
deleted file mode 100644
index a0223fd..0000000
--- a/config-repo/vote.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-server.port: 8014
-spring.redis:
- host: localhost
- port: 6379
- client-type: lettuce
-
----
-spring.config.activate.on-profile: docker
-server.port: 8080
-spring.redis:
- host: redis
- port: 6379
- client-type: lettuce
-
diff --git a/create-project.bash b/create-project.bash
deleted file mode 100644
index 80b646e..0000000
--- a/create-project.bash
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env bash
-
-mkdir spring-boot-based-microservices
-cd spring-boot-based-microservices
-
-spring init \
---boot-version=3.4.3 \
---build=maven \
---java-version=17 \
---packaging=jar \
---name=student-service \
---package-name=io.javatab.microservices.core.student \
---groupId=io.javatab.microservices.core.student \
---dependencies=actuator,webflux \
---version=1.0.0 \
-student-service
-
-spring init \
---boot-version=3.4.3 \
---build=maven \
---java-version=17 \
---packaging=jar \
---name=course-service \
---package-name=io.javatab.microservices.core.course \
---groupId=io.javatab.microservices.core.course \
---dependencies=actuator,webflux \
---version=1.0.0 \
-course-service
-
-spring init \
---boot-version=3.4.3 \
---build=maven \
---java-version=17 \
---packaging=jar \
---name=vote-service \
---package-name=io.javatab.microservices.core.vote \
---groupId=io.javatab.microservices.core.vote \
---dependencies=actuator,webflux \
---version=1.0.0 \
-vote-service
-
-spring init \
---boot-version=3.4.3 \
---build=maven \
---java-version=17 \
---packaging=jar \
---name=search-service \
---package-name=io.javatab.microservices.core.search \
---groupId=io.javatab.microservices.core.search \
---dependencies=actuator,webflux \
---version=1.0.0 \
-search-service
-
-spring init \
---boot-version=3.4.3 \
---build=maven \
---java-version=17 \
---packaging=jar \
---name=course-composite-service \
---package-name=io.javatab.microservices.composite.course \
---groupId=io.javatab.microservices.composite.course \
---dependencies=actuator,webflux \
---version=1.0.0 \
-course-composite-service
-
-cd ..
diff --git a/create-project.sh b/create-project.sh
new file mode 100644
index 0000000..342d611
--- /dev/null
+++ b/create-project.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+# Common configuration
+SPRING_BOOT_VERSION="3.4.3"
+JAVA_VERSION="17"
+BUILD_TOOL="maven"
+PACKAGING="jar"
+BASE_VERSION="1.0.0"
+BASE_GROUP="io.javatab.microservices"
+DEPENDENCIES="actuator,webflux"
+
+# Function to initialize a Spring Boot microservice
+init_microservice() {
+ local service_name=$1
+ local package_suffix=$2
+
+ spring init \
+ --boot-version="$SPRING_BOOT_VERSION" \
+ --build="$BUILD_TOOL" \
+ --java-version="$JAVA_VERSION" \
+ --packaging="$PACKAGING" \
+ --name="${service_name}-service" \
+ --package-name="${BASE_GROUP}.${package_suffix}" \
+ --groupId="${BASE_GROUP}.${package_suffix}" \
+ --dependencies="$DEPENDENCIES" \
+ --version="$BASE_VERSION" \
+ "${service_name}-service"
+}
+
+# Main execution
+main() {
+ # Create and enter microservices directory
+ mkdir -p microservices
+ cd microservices || exit 1
+
+ # Initialize each microservice
+ init_microservice "course" "core.course"
+ init_microservice "review" "core.review"
+ init_microservice "course-composite" "composite.course"
+
+ # Return to parent directory
+ cd .. || exit 1
+}
+
+# Execute main function
+main
\ No newline at end of file
diff --git a/docker/.env b/docker/.env
deleted file mode 100644
index 293fc79..0000000
--- a/docker/.env
+++ /dev/null
@@ -1,3 +0,0 @@
-CONFIG_SERVER_ENCRYPT_KEY=my-very-secure-encrypt-key
-CONFIG_SERVER_USR=dev-user
-CONFIG_SERVER_PWD=dev-password
\ No newline at end of file
diff --git a/docker/docker-compose-base.yml b/docker/docker-compose-base.yml
new file mode 100644
index 0000000..df40e30
--- /dev/null
+++ b/docker/docker-compose-base.yml
@@ -0,0 +1,46 @@
+services:
+ course:
+ build: ../microservices/course-service
+ mem_limit: 512m
+ environment:
+ - SPRING_PROFILES_ACTIVE=docker
+ networks:
+ - shared-network
+ #depends_on:
+ # postgres:
+ # condition: service_healthy
+
+ review:
+ build: ../microservices/review-service
+ mem_limit: 512m
+ environment:
+ - SPRING_PROFILES_ACTIVE=docker
+ networks:
+ - shared-network
+ #depends_on:
+ # mongodb:
+ # condition: service_healthy
+
+ course-composite:
+ build: ../microservices/course-composite-service
+ mem_limit: 512m
+ ports:
+ - "8080:8080"
+ environment:
+ - SPRING_PROFILES_ACTIVE=docker
+ networks:
+ - shared-network
+
+ gateway-service:
+ build: ../spring-cloud/gateway-service
+ mem_limit: 512m
+ ports:
+ - "9000:9000"
+ environment:
+ - SPRING_PROFILES_ACTIVE=docker
+ networks:
+ - shared-network
+
+networks:
+ shared-network:
+ name: shared-network
\ No newline at end of file
diff --git a/docker/docker-compose-infra.yml b/docker/docker-compose-infra.yml
new file mode 100644
index 0000000..8a5daba
--- /dev/null
+++ b/docker/docker-compose-infra.yml
@@ -0,0 +1,48 @@
+services:
+ mongodb:
+ image: mongo:6.0.4
+ container_name: "mongodb"
+ mem_limit: 512m
+ ports:
+ - "27017:27017"
+ command: mongod
+ healthcheck:
+ test: "mongostat -n 1"
+ networks:
+ - shared-network
+
+ postgres:
+ image: "postgres:17.4"
+ container_name: "postgres"
+ ports:
+ - 5432:5432
+ environment:
+ - POSTGRES_USER=user
+ - POSTGRES_PASSWORD=pwd
+ - POSTGRES_DB=course_db
+ healthcheck:
+ test: [ "CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+ volumes:
+ - ./postgresql/init.sql:/docker-entrypoint-initdb.d/init.sql
+ networks:
+ - shared-network
+
+ keycloak:
+ image: quay.io/keycloak/keycloak:latest
+ container_name: keycloak
+ ports:
+ - "8081:8080"
+ environment:
+ KEYCLOAK_ADMIN: admin
+ KEYCLOAK_ADMIN_PASSWORD: admin
+ command: [ "start-dev" ]
+ networks:
+ - shared-network
+
+networks:
+ shared-network:
+ name: shared-network
\ No newline at end of file
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
deleted file mode 100644
index 3b13757..0000000
--- a/docker/docker-compose.yml
+++ /dev/null
@@ -1,165 +0,0 @@
-services:
- course:
- build: ../microservices/course-service
- image: javatab/course_service:v2
- mem_limit: 512m
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
- depends_on:
- mongodb:
- condition: service_healthy
-
- search:
- build: ../microservices/search-service
- image: javatab/search_service:v2
- mem_limit: 512m
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
- depends_on:
- elasticsearch:
- condition: service_healthy
-
- student:
- build: ../microservices/student-service
- image: javatab/student_service:v2
- mem_limit: 512m
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
- depends_on:
- postgres:
- condition: service_healthy
-
- vote:
- build: ../microservices/vote-service
- image: javatab/vote_service:v2
- mem_limit: 512m
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
- depends_on:
- redis:
- condition: service_healthy
-
- course-composite:
- build: ../microservices/course-composite-service
- image: javatab/course_composite_service:v2
- mem_limit: 512m
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
-
- mongodb:
- image: mongo:4.4.2
- mem_limit: 512m
- ports:
- - "27017:27017"
- command: mongod
- healthcheck:
- test: "mongo --eval 'db.stats().ok'"
- interval: 5s
- timeout: 2s
- retries: 60
-
- postgres:
- image: postgres:13.7-alpine3.16
- mem_limit: 512m
- ports:
- - "5432:5432"
- environment:
- - POSTGRES_DATABASE=test
- - POSTGRES_USER=user
- - POSTGRES_PASSWORD=pwd
- healthcheck:
- test: [ "CMD-SHELL", "pg_isready -U user" ]
- interval: 10s
- timeout: 5s
- retries: 5
-
- redis:
- image: redis:6.2.6-alpine
- restart: always
- ports:
- - "6379:6379"
- healthcheck:
- test: [ "CMD-SHELL", "redis-cli ping | grep PONG" ]
- interval: 5s
- timeout: 2s
- retries: 60
-
- elasticsearch:
- image: elasticsearch:7.12.1
- ports:
- - "9300:9300"
- - "9200:9200"
- environment:
- - node.name=elasticsearch
- - discovery.type=single-node
- - cluster.name=docker-cluster
- - bootstrap.memory_lock=true
- - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- ulimits:
- memlock:
- soft: -1
- hard: -1
- healthcheck:
- test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
- interval: 5s
- timeout: 2s
- retries: 60
-
- eureka:
- build: ../spring-cloud/eureka-server
- image: javatab/eureka:v2
- mem_limit: 512m
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
-
- gateway:
- build: ../spring-cloud/gateway
- image: javatab/gateway:v2
- mem_limit: 512m
- ports:
- - "8443:8443"
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
- #depends_on:
- # auth-server:
- # condition: service_healthy
-
- auth-server:
- environment:
- - SPRING_PROFILES_ACTIVE=docker
- - CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- - CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
- build: ../spring-cloud/authorization-server
- image: javatab/auth-server:v2
- mem_limit: 512m
- #healthcheck:
- # test: "curl --fail --silent http://localhost:9999/actuator/health | grep UP || exit 1"
- # interval: 5s
- # timeout: 2s
- # retries: 60
-
- config-server:
- build: ../spring-cloud/config-server
- image: javatab/config-server:v2
- mem_limit: 512m
- environment:
- - SPRING_PROFILES_ACTIVE=docker,native
- #- ENCRYPT_KEY=${CONFIG_SERVER_ENCRYPT_KEY}
- - SPRING_SECURITY_USER_NAME=${CONFIG_SERVER_USR}
- - SPRING_SECURITY_USER_PASSWORD=${CONFIG_SERVER_PWD}
- volumes:
- - $PWD/../config-repo:/config-repo
\ No newline at end of file
diff --git a/docker/postgresql/init.sql b/docker/postgresql/init.sql
new file mode 100644
index 0000000..2d597ca
--- /dev/null
+++ b/docker/postgresql/init.sql
@@ -0,0 +1,6 @@
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT FROM pg_database WHERE datname = 'course_db') THEN
+ CREATE DATABASE course_db;
+ END IF;
+END $$;
diff --git a/images/build.png b/images/build.png
deleted file mode 100644
index 3614bc8..0000000
Binary files a/images/build.png and /dev/null differ
diff --git a/images/docker-compose.png b/images/docker-compose.png
deleted file mode 100644
index f2f0206..0000000
Binary files a/images/docker-compose.png and /dev/null differ
diff --git a/images/docker-ps.png b/images/docker-ps.png
deleted file mode 100644
index af62936..0000000
Binary files a/images/docker-ps.png and /dev/null differ
diff --git a/images/eureka.png b/images/eureka.png
deleted file mode 100644
index 3290f86..0000000
Binary files a/images/eureka.png and /dev/null differ
diff --git a/images/gateway-routes.png b/images/gateway-routes.png
deleted file mode 100644
index a333b0c..0000000
Binary files a/images/gateway-routes.png and /dev/null differ
diff --git a/images/jwt-io.png b/images/jwt-io.png
deleted file mode 100644
index a51ea4f..0000000
Binary files a/images/jwt-io.png and /dev/null differ
diff --git a/images/oauth-endpoint.png b/images/oauth-endpoint.png
deleted file mode 100644
index 79ea653..0000000
Binary files a/images/oauth-endpoint.png and /dev/null differ
diff --git a/images/swagger-openapi.png b/images/swagger-openapi.png
deleted file mode 100644
index e9b9a31..0000000
Binary files a/images/swagger-openapi.png and /dev/null differ
diff --git a/kubernetes/infra/keycloak/course-management-realm.json b/kubernetes/infra/keycloak/course-management-realm.json
new file mode 100644
index 0000000..5f5dcc7
--- /dev/null
+++ b/kubernetes/infra/keycloak/course-management-realm.json
@@ -0,0 +1,2012 @@
+{
+ "id" : "a78874c4-602a-455c-aaa5-843e2afd59f8",
+ "realm" : "course-management-realm",
+ "notBefore" : 0,
+ "defaultSignatureAlgorithm" : "RS256",
+ "revokeRefreshToken" : false,
+ "refreshTokenMaxReuse" : 0,
+ "accessTokenLifespan" : 300,
+ "accessTokenLifespanForImplicitFlow" : 900,
+ "ssoSessionIdleTimeout" : 1800,
+ "ssoSessionMaxLifespan" : 36000,
+ "ssoSessionIdleTimeoutRememberMe" : 0,
+ "ssoSessionMaxLifespanRememberMe" : 0,
+ "offlineSessionIdleTimeout" : 2592000,
+ "offlineSessionMaxLifespanEnabled" : false,
+ "offlineSessionMaxLifespan" : 5184000,
+ "clientSessionIdleTimeout" : 0,
+ "clientSessionMaxLifespan" : 0,
+ "clientOfflineSessionIdleTimeout" : 0,
+ "clientOfflineSessionMaxLifespan" : 0,
+ "accessCodeLifespan" : 60,
+ "accessCodeLifespanUserAction" : 300,
+ "accessCodeLifespanLogin" : 1800,
+ "actionTokenGeneratedByAdminLifespan" : 43200,
+ "actionTokenGeneratedByUserLifespan" : 300,
+ "oauth2DeviceCodeLifespan" : 600,
+ "oauth2DevicePollingInterval" : 5,
+ "enabled" : true,
+ "sslRequired" : "external",
+ "registrationAllowed" : false,
+ "registrationEmailAsUsername" : false,
+ "rememberMe" : false,
+ "verifyEmail" : false,
+ "loginWithEmailAllowed" : true,
+ "duplicateEmailsAllowed" : false,
+ "resetPasswordAllowed" : false,
+ "editUsernameAllowed" : false,
+ "bruteForceProtected" : false,
+ "permanentLockout" : false,
+ "maxTemporaryLockouts" : 0,
+ "maxFailureWaitSeconds" : 900,
+ "minimumQuickLoginWaitSeconds" : 60,
+ "waitIncrementSeconds" : 60,
+ "quickLoginCheckMilliSeconds" : 1000,
+ "maxDeltaTimeSeconds" : 43200,
+ "failureFactor" : 30,
+ "roles" : {
+ "realm" : [ {
+ "id" : "40b0af37-ed44-44c5-9fd1-3948adbae9eb",
+ "name" : "default-roles-course-management-realm",
+ "description" : "${role_default-roles}",
+ "composite" : true,
+ "composites" : {
+ "realm" : [ "offline_access", "uma_authorization" ],
+ "client" : {
+ "account" : [ "manage-account", "view-profile" ]
+ }
+ },
+ "clientRole" : false,
+ "containerId" : "a78874c4-602a-455c-aaa5-843e2afd59f8",
+ "attributes" : { }
+ }, {
+ "id" : "99fbb22e-e4e3-49d2-b8c9-8a899363a3d1",
+ "name" : "offline_access",
+ "description" : "${role_offline-access}",
+ "composite" : false,
+ "clientRole" : false,
+ "containerId" : "a78874c4-602a-455c-aaa5-843e2afd59f8",
+ "attributes" : { }
+ }, {
+ "id" : "f686c5fa-3636-4171-aa92-1ce712a11001",
+ "name" : "ADMIN",
+ "description" : "",
+ "composite" : false,
+ "clientRole" : false,
+ "containerId" : "a78874c4-602a-455c-aaa5-843e2afd59f8",
+ "attributes" : { }
+ }, {
+ "id" : "6f7a4c20-90e8-4b6c-8d7e-d64570eff9db",
+ "name" : "GUEST",
+ "description" : "",
+ "composite" : false,
+ "clientRole" : false,
+ "containerId" : "a78874c4-602a-455c-aaa5-843e2afd59f8",
+ "attributes" : { }
+ }, {
+ "id" : "949e34e2-0572-4c01-993c-8de9bbe032ad",
+ "name" : "uma_authorization",
+ "description" : "${role_uma_authorization}",
+ "composite" : false,
+ "clientRole" : false,
+ "containerId" : "a78874c4-602a-455c-aaa5-843e2afd59f8",
+ "attributes" : { }
+ } ],
+ "client" : {
+ "realm-management" : [ {
+ "id" : "cea82b45-111f-4ef3-b679-222a8a4891b4",
+ "name" : "query-users",
+ "description" : "${role_query-users}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "7f81b325-bfb6-449f-aa9c-0f922dae4e8b",
+ "name" : "view-identity-providers",
+ "description" : "${role_view-identity-providers}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "5339c4bc-f303-4a0b-9636-72876828bfd8",
+ "name" : "manage-clients",
+ "description" : "${role_manage-clients}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "6572d862-4d23-457f-968b-ec2824365b69",
+ "name" : "manage-identity-providers",
+ "description" : "${role_manage-identity-providers}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "2a7dd2bf-5181-4964-9367-80d17c8e29ca",
+ "name" : "view-clients",
+ "description" : "${role_view-clients}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "realm-management" : [ "query-clients" ]
+ }
+ },
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "84aca58a-f164-4b50-aded-8461d04b3c57",
+ "name" : "view-events",
+ "description" : "${role_view-events}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "128f1b01-b265-4290-88bd-9e3bcfab4ca2",
+ "name" : "view-users",
+ "description" : "${role_view-users}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "realm-management" : [ "query-users", "query-groups" ]
+ }
+ },
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "191643c4-60bc-40a7-8569-98aa27e285d5",
+ "name" : "manage-events",
+ "description" : "${role_manage-events}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "2f14ecd3-1d7c-47a3-bd04-15c3a57dd542",
+ "name" : "manage-users",
+ "description" : "${role_manage-users}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "36fb8936-0ad2-4e1d-a147-c851c01188f8",
+ "name" : "view-authorization",
+ "description" : "${role_view-authorization}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "80eeb77f-ef7a-4897-8401-03a90602c1ee",
+ "name" : "manage-realm",
+ "description" : "${role_manage-realm}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "04c0efde-1c84-4311-bba3-3cfb814cde36",
+ "name" : "query-clients",
+ "description" : "${role_query-clients}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "31252ab9-3194-47ef-b551-1be9f2ab1ebd",
+ "name" : "impersonation",
+ "description" : "${role_impersonation}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "31fd5385-eccf-4cca-906c-467ce3ac9bf9",
+ "name" : "view-realm",
+ "description" : "${role_view-realm}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "9aced5dc-e82a-4fff-9b2c-e7eb7a0e8262",
+ "name" : "manage-authorization",
+ "description" : "${role_manage-authorization}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "f40ae944-8138-4451-999d-cd130e99d24e",
+ "name" : "query-groups",
+ "description" : "${role_query-groups}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "4d611917-0bdc-4213-a781-523a370e122e",
+ "name" : "realm-admin",
+ "description" : "${role_realm-admin}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "realm-management" : [ "view-identity-providers", "query-users", "manage-clients", "manage-identity-providers", "view-clients", "view-events", "view-users", "manage-events", "manage-users", "view-authorization", "manage-realm", "query-clients", "impersonation", "view-realm", "manage-authorization", "query-groups", "query-realms", "create-client" ]
+ }
+ },
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "ddf182c8-094f-4bea-afe0-22225b6cc122",
+ "name" : "create-client",
+ "description" : "${role_create-client}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ }, {
+ "id" : "7c11d15e-fd0c-41d2-9b79-2fc403a04eeb",
+ "name" : "query-realms",
+ "description" : "${role_query-realms}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "attributes" : { }
+ } ],
+ "review-app" : [ {
+ "id" : "2c28f39b-fc50-4db8-a8f2-1b789822608a",
+ "name" : "REVIEW-READ",
+ "description" : "",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "79389924-4a61-4b88-8247-4f40f82a49e9",
+ "attributes" : { }
+ }, {
+ "id" : "d8493fde-208e-449e-bc90-a1055093d197",
+ "name" : "REVIEW-WRITE",
+ "description" : "",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "79389924-4a61-4b88-8247-4f40f82a49e9",
+ "attributes" : { }
+ } ],
+ "security-admin-console" : [ ],
+ "admin-cli" : [ ],
+ "course-app" : [ {
+ "id" : "7041c0c4-cb75-4bb0-9388-1b83d532f44d",
+ "name" : "COURSE-READ",
+ "description" : "",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "b2fb4a65-fb97-4f5c-9154-f66d7ee47383",
+ "attributes" : { }
+ }, {
+ "id" : "fc8b0ca2-2973-4830-b5f3-597755e3ed83",
+ "name" : "COURSE-WRITE",
+ "description" : "",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "b2fb4a65-fb97-4f5c-9154-f66d7ee47383",
+ "attributes" : { }
+ } ],
+ "account-console" : [ ],
+ "broker" : [ {
+ "id" : "03b5f674-16fc-483a-9165-f507a77771a2",
+ "name" : "read-token",
+ "description" : "${role_read-token}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "0d2d9171-c4f2-4430-afe1-b8ae645a1da4",
+ "attributes" : { }
+ } ],
+ "account" : [ {
+ "id" : "01265230-fb3b-4547-aa16-8526ab81906c",
+ "name" : "manage-account-links",
+ "description" : "${role_manage-account-links}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ }, {
+ "id" : "c03a1c7f-3cd3-4add-a677-2b1a8789f23f",
+ "name" : "manage-consent",
+ "description" : "${role_manage-consent}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "account" : [ "view-consent" ]
+ }
+ },
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ }, {
+ "id" : "51cc80f1-cf87-4099-a693-abd55734feec",
+ "name" : "view-groups",
+ "description" : "${role_view-groups}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ }, {
+ "id" : "e5ae84b5-0138-40db-ae21-fe9f0b362f85",
+ "name" : "delete-account",
+ "description" : "${role_delete-account}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ }, {
+ "id" : "c7f1033e-d611-4a41-95cb-45627cfbcc36",
+ "name" : "manage-account",
+ "description" : "${role_manage-account}",
+ "composite" : true,
+ "composites" : {
+ "client" : {
+ "account" : [ "manage-account-links" ]
+ }
+ },
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ }, {
+ "id" : "1441c577-25c4-4c3a-b6bb-64277faeef08",
+ "name" : "view-applications",
+ "description" : "${role_view-applications}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ }, {
+ "id" : "766727da-5390-48b6-8fce-295563dfe145",
+ "name" : "view-consent",
+ "description" : "${role_view-consent}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ }, {
+ "id" : "f96ee918-5078-4996-b5c1-918a04ac3da2",
+ "name" : "view-profile",
+ "description" : "${role_view-profile}",
+ "composite" : false,
+ "clientRole" : true,
+ "containerId" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "attributes" : { }
+ } ]
+ }
+ },
+ "groups" : [ ],
+ "defaultRole" : {
+ "id" : "40b0af37-ed44-44c5-9fd1-3948adbae9eb",
+ "name" : "default-roles-course-management-realm",
+ "description" : "${role_default-roles}",
+ "composite" : true,
+ "clientRole" : false,
+ "containerId" : "a78874c4-602a-455c-aaa5-843e2afd59f8"
+ },
+ "requiredCredentials" : [ "password" ],
+ "otpPolicyType" : "totp",
+ "otpPolicyAlgorithm" : "HmacSHA1",
+ "otpPolicyInitialCounter" : 0,
+ "otpPolicyDigits" : 6,
+ "otpPolicyLookAheadWindow" : 1,
+ "otpPolicyPeriod" : 30,
+ "otpPolicyCodeReusable" : false,
+ "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ],
+ "localizationTexts" : { },
+ "webAuthnPolicyRpEntityName" : "keycloak",
+ "webAuthnPolicySignatureAlgorithms" : [ "ES256" ],
+ "webAuthnPolicyRpId" : "",
+ "webAuthnPolicyAttestationConveyancePreference" : "not specified",
+ "webAuthnPolicyAuthenticatorAttachment" : "not specified",
+ "webAuthnPolicyRequireResidentKey" : "not specified",
+ "webAuthnPolicyUserVerificationRequirement" : "not specified",
+ "webAuthnPolicyCreateTimeout" : 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister" : false,
+ "webAuthnPolicyAcceptableAaguids" : [ ],
+ "webAuthnPolicyExtraOrigins" : [ ],
+ "webAuthnPolicyPasswordlessRpEntityName" : "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ],
+ "webAuthnPolicyPasswordlessRpId" : "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout" : 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ],
+ "webAuthnPolicyPasswordlessExtraOrigins" : [ ],
+ "users" : [ {
+ "id" : "9cbbddf1-4c46-48da-a7f5-5ded141a95a9",
+ "username" : "courseuser",
+ "firstName" : "courseuser",
+ "lastName" : "courseuser",
+ "email" : "courseuser@gmail.com",
+ "emailVerified" : false,
+ "createdTimestamp" : 1741346097568,
+ "enabled" : true,
+ "totp" : false,
+ "credentials" : [ {
+ "id" : "f74c8fcc-e148-4a01-a648-5ac2c4746821",
+ "type" : "password",
+ "userLabel" : "My password",
+ "createdDate" : 1741346185177,
+ "secretData" : "{\"value\":\"38jFthXU2moP4OGYCSF8tmBVzOmmMQyIcTY+bLxfuIzNq9M7TTW+VIDtzIIx4ZOpn7j0RoZqtw17ge67rDTmIA==\",\"salt\":\"/l2134dG5z9g0qN/EKSGtg==\",\"additionalParameters\":{}}",
+ "credentialData" : "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}"
+ } ],
+ "disableableCredentialTypes" : [ ],
+ "requiredActions" : [ ],
+ "realmRoles" : [ "default-roles-course-management-realm" ],
+ "clientRoles" : {
+ "course-app" : [ "COURSE-READ", "COURSE-WRITE" ]
+ },
+ "notBefore" : 0,
+ "groups" : [ ]
+ }, {
+ "id" : "37666d56-ca2d-4d44-94f1-d498e7afea95",
+ "username" : "nasruddin",
+ "firstName" : "Nasruddin",
+ "lastName" : "Nasruddin",
+ "email" : "nasruddin@gmail.com",
+ "emailVerified" : false,
+ "createdTimestamp" : 1741342003413,
+ "enabled" : true,
+ "totp" : false,
+ "credentials" : [ {
+ "id" : "ed039ec4-9004-47f9-8d31-afeba8d69d7c",
+ "type" : "password",
+ "userLabel" : "My password",
+ "createdDate" : 1741342019437,
+ "secretData" : "{\"value\":\"zAONDzi9aK+o9QB/M00uifBkrDIdd2Zr+yn1RPnFkugmkaNkGb2sGjqyO88gORVWlzos3E01G1Eju7NeEaTHEA==\",\"salt\":\"8yho3Lv5Am4M+pkcN6V9dQ==\",\"additionalParameters\":{}}",
+ "credentialData" : "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}"
+ } ],
+ "disableableCredentialTypes" : [ ],
+ "requiredActions" : [ ],
+ "realmRoles" : [ "default-roles-course-management-realm", "ADMIN", "GUEST" ],
+ "clientRoles" : {
+ "review-app" : [ "REVIEW-READ", "REVIEW-WRITE" ],
+ "course-app" : [ "COURSE-READ", "COURSE-WRITE" ]
+ },
+ "notBefore" : 0,
+ "groups" : [ ]
+ }, {
+ "id" : "fd942f7f-9f47-4bba-9a36-5ec752964ea3",
+ "username" : "reviewuser",
+ "firstName" : "reviewuser",
+ "lastName" : "reviewuser",
+ "email" : "reviewuser@gmail.com",
+ "emailVerified" : false,
+ "createdTimestamp" : 1741346127607,
+ "enabled" : true,
+ "totp" : false,
+ "credentials" : [ {
+ "id" : "65a6136c-326d-494d-90ca-63351d722024",
+ "type" : "password",
+ "userLabel" : "My password",
+ "createdDate" : 1741346157562,
+ "secretData" : "{\"value\":\"5LCPSgxpfRo4mcqnDd1CbJvrSnHjkIBKvSCz98UtEkwQjYX+CvG/CSC7OQcWbeuBuncFxz9wHMuTntafAXTh7w==\",\"salt\":\"GLPFJwJ5ab1bMSgonzEoDg==\",\"additionalParameters\":{}}",
+ "credentialData" : "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}"
+ } ],
+ "disableableCredentialTypes" : [ ],
+ "requiredActions" : [ ],
+ "realmRoles" : [ "default-roles-course-management-realm" ],
+ "clientRoles" : {
+ "review-app" : [ "REVIEW-WRITE", "REVIEW-READ" ]
+ },
+ "notBefore" : 0,
+ "groups" : [ ]
+ } ],
+ "scopeMappings" : [ {
+ "clientScope" : "offline_access",
+ "roles" : [ "offline_access" ]
+ }, {
+ "clientScope" : "roles",
+ "roles" : [ "GUEST", "ADMIN" ]
+ } ],
+ "clientScopeMappings" : {
+ "review-app" : [ {
+ "clientScope" : "roles",
+ "roles" : [ "REVIEW-READ", "REVIEW-WRITE" ]
+ } ],
+ "course-app" : [ {
+ "clientScope" : "roles",
+ "roles" : [ "COURSE-WRITE", "COURSE-READ" ]
+ } ],
+ "account" : [ {
+ "client" : "account-console",
+ "roles" : [ "manage-account", "view-groups" ]
+ } ]
+ },
+ "clients" : [ {
+ "id" : "7912e53f-3c58-4108-9ac6-ca68ac9e6a90",
+ "clientId" : "account",
+ "name" : "${client_account}",
+ "rootUrl" : "${authBaseUrl}",
+ "baseUrl" : "/realms/course-management-realm/account/",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ "/realms/course-management-realm/account/*" ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "post.logout.redirect.uris" : "+"
+ },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "2571f67b-9279-491f-9f94-3f2a561762de",
+ "clientId" : "account-console",
+ "name" : "${client_account-console}",
+ "rootUrl" : "${authBaseUrl}",
+ "baseUrl" : "/realms/course-management-realm/account/",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ "/realms/course-management-realm/account/*" ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "post.logout.redirect.uris" : "+",
+ "pkce.code.challenge.method" : "S256"
+ },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "protocolMappers" : [ {
+ "id" : "ed39f9e3-3a74-48d8-9b37-3869c1e1edf2",
+ "name" : "audience resolve",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-audience-resolve-mapper",
+ "consentRequired" : false,
+ "config" : { }
+ } ],
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "0a2c6ba4-66ed-4631-b24b-f4e7e7bf72ba",
+ "clientId" : "admin-cli",
+ "name" : "${client_admin-cli}",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : false,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : true,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : { },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "0d2d9171-c4f2-4430-afe1-b8ae645a1da4",
+ "clientId" : "broker",
+ "name" : "${client_broker}",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : true,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : false,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : { },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "b2fb4a65-fb97-4f5c-9154-f66d7ee47383",
+ "clientId" : "course-app",
+ "name" : "",
+ "description" : "",
+ "rootUrl" : "",
+ "adminUrl" : "",
+ "baseUrl" : "",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "secret" : "v1sCIPjANbvyJ87RsTkYeI9xHonDqZh7",
+ "redirectUris" : [ "/*" ],
+ "webOrigins" : [ "/*" ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : true,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : false,
+ "frontchannelLogout" : true,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "access.token.lifespan" : "86400",
+ "client.secret.creation.time" : "1741341917",
+ "oauth2.device.authorization.grant.enabled" : "false",
+ "backchannel.logout.revoke.offline.tokens" : "false",
+ "use.refresh.tokens" : "true",
+ "oidc.ciba.grant.enabled" : "false",
+ "client.use.lightweight.access.token.enabled" : "false",
+ "backchannel.logout.session.required" : "true",
+ "client_credentials.use_refresh_token" : "false",
+ "tls.client.certificate.bound.access.tokens" : "false",
+ "require.pushed.authorization.requests" : "false",
+ "acr.loa.map" : "{}",
+ "display.on.consent.screen" : "false",
+ "token.response.type.bearer.lower-case" : "false"
+ },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : -1,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "dee6d3c3-acf5-491d-9052-66aa73c52675",
+ "clientId" : "realm-management",
+ "name" : "${client_realm-management}",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ ],
+ "webOrigins" : [ ],
+ "notBefore" : 0,
+ "bearerOnly" : true,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : false,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : { },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "79389924-4a61-4b88-8247-4f40f82a49e9",
+ "clientId" : "review-app",
+ "name" : "",
+ "description" : "",
+ "rootUrl" : "",
+ "adminUrl" : "",
+ "baseUrl" : "",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "secret" : "TRj0xwAnqoot5pKF3NDKBOqNBbY8lYAE",
+ "redirectUris" : [ "/*" ],
+ "webOrigins" : [ "/*" ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : true,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : false,
+ "frontchannelLogout" : true,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "oidc.ciba.grant.enabled" : "false",
+ "oauth2.device.authorization.grant.enabled" : "false",
+ "client.secret.creation.time" : "1741342379",
+ "backchannel.logout.session.required" : "true",
+ "backchannel.logout.revoke.offline.tokens" : "false"
+ },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : -1,
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ }, {
+ "id" : "2dee8491-2aff-475b-8477-cf9d3279b5be",
+ "clientId" : "security-admin-console",
+ "name" : "${client_security-admin-console}",
+ "rootUrl" : "${authAdminUrl}",
+ "baseUrl" : "/admin/course-management-realm/console/",
+ "surrogateAuthRequired" : false,
+ "enabled" : true,
+ "alwaysDisplayInConsole" : false,
+ "clientAuthenticatorType" : "client-secret",
+ "redirectUris" : [ "/admin/course-management-realm/console/*" ],
+ "webOrigins" : [ "+" ],
+ "notBefore" : 0,
+ "bearerOnly" : false,
+ "consentRequired" : false,
+ "standardFlowEnabled" : true,
+ "implicitFlowEnabled" : false,
+ "directAccessGrantsEnabled" : false,
+ "serviceAccountsEnabled" : false,
+ "publicClient" : true,
+ "frontchannelLogout" : false,
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "post.logout.redirect.uris" : "+",
+ "pkce.code.challenge.method" : "S256"
+ },
+ "authenticationFlowBindingOverrides" : { },
+ "fullScopeAllowed" : false,
+ "nodeReRegistrationTimeout" : 0,
+ "protocolMappers" : [ {
+ "id" : "0c7bbb0a-d254-4f28-a716-7daade67ca2b",
+ "name" : "locale",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "locale",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "locale",
+ "jsonType.label" : "String"
+ }
+ } ],
+ "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ],
+ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+ } ],
+ "clientScopes" : [ {
+ "id" : "24c0f44f-b566-45fd-9912-95443bed8dda",
+ "name" : "offline_access",
+ "description" : "OpenID Connect built-in scope: offline_access",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "consent.screen.text" : "${offlineAccessScopeConsentText}",
+ "display.on.consent.screen" : "true"
+ }
+ }, {
+ "id" : "81ed88d3-6333-453d-93e7-1975743df3e4",
+ "name" : "phone",
+ "description" : "OpenID Connect built-in scope: phone",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${phoneScopeConsentText}"
+ },
+ "protocolMappers" : [ {
+ "id" : "3643f14b-de9a-4fe5-81af-1ee319f53d6e",
+ "name" : "phone number",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "phoneNumber",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "phone_number",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "ad6e06d4-233d-4d78-8b85-4246b7033af9",
+ "name" : "phone number verified",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "phoneNumberVerified",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "phone_number_verified",
+ "jsonType.label" : "boolean"
+ }
+ } ]
+ }, {
+ "id" : "e15bf781-b701-4d16-99eb-fa6446a55156",
+ "name" : "web-origins",
+ "description" : "OpenID Connect scope for add allowed web origins to the access token",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "false",
+ "display.on.consent.screen" : "false",
+ "consent.screen.text" : ""
+ },
+ "protocolMappers" : [ {
+ "id" : "7db982ba-ef0d-4c97-91e4-150b98a7a6e1",
+ "name" : "allowed web origins",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-allowed-origins-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "access.token.claim" : "true"
+ }
+ } ]
+ }, {
+ "id" : "3775f404-7f57-41df-806a-9a49d97e9fce",
+ "name" : "roles",
+ "description" : "OpenID Connect scope for add user roles to the access token",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "false",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${rolesScopeConsentText}"
+ },
+ "protocolMappers" : [ {
+ "id" : "1b515df6-7661-4606-8bbc-a3b791491337",
+ "name" : "realm roles",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-realm-role-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "multivalued" : "true",
+ "userinfo.token.claim" : "false",
+ "user.attribute" : "foo",
+ "id.token.claim" : "true",
+ "lightweight.claim" : "false",
+ "access.token.claim" : "true",
+ "claim.name" : "realm_access.roles",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "53dcf569-832a-4df2-878c-5fb9d4ea5c60",
+ "name" : "client roles",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-client-role-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "multivalued" : "true",
+ "userinfo.token.claim" : "false",
+ "user.attribute" : "foo",
+ "id.token.claim" : "true",
+ "lightweight.claim" : "false",
+ "access.token.claim" : "true",
+ "claim.name" : "resource_access.${client_id}.roles",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "413e69ac-87db-4e42-ba43-863994c3755b",
+ "name" : "audience resolve",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-audience-resolve-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "access.token.claim" : "true"
+ }
+ } ]
+ }, {
+ "id" : "d0adcccd-f536-4cd5-b7db-3c61cab09890",
+ "name" : "microprofile-jwt",
+ "description" : "Microprofile - JWT built-in scope",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "false"
+ },
+ "protocolMappers" : [ {
+ "id" : "5fc7c7a0-e1cf-4ee8-ae9d-9f368a82b906",
+ "name" : "upn",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "username",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "upn",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "3a4a141a-a6c7-4a4b-b481-637ee447d3c6",
+ "name" : "groups",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-realm-role-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "multivalued" : "true",
+ "user.attribute" : "foo",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "groups",
+ "jsonType.label" : "String"
+ }
+ } ]
+ }, {
+ "id" : "aa80d37e-ca16-4d51-8dce-3c77488fb815",
+ "name" : "acr",
+ "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "false",
+ "display.on.consent.screen" : "false"
+ },
+ "protocolMappers" : [ {
+ "id" : "5534cbee-e3c6-4df9-930d-81b6e3cd99f2",
+ "name" : "acr loa level",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-acr-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "id.token.claim" : "true",
+ "introspection.token.claim" : "true",
+ "access.token.claim" : "true"
+ }
+ } ]
+ }, {
+ "id" : "0fe09494-b02d-4992-a9ac-9efceeecbe43",
+ "name" : "address",
+ "description" : "OpenID Connect built-in scope: address",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${addressScopeConsentText}"
+ },
+ "protocolMappers" : [ {
+ "id" : "ed41a8bd-4071-4a3b-8293-aff80ed83928",
+ "name" : "address",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-address-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "user.attribute.formatted" : "formatted",
+ "user.attribute.country" : "country",
+ "introspection.token.claim" : "true",
+ "user.attribute.postal_code" : "postal_code",
+ "userinfo.token.claim" : "true",
+ "user.attribute.street" : "street",
+ "id.token.claim" : "true",
+ "user.attribute.region" : "region",
+ "access.token.claim" : "true",
+ "user.attribute.locality" : "locality"
+ }
+ } ]
+ }, {
+ "id" : "56514616-243d-46ed-8e36-143268fc58d0",
+ "name" : "email",
+ "description" : "OpenID Connect built-in scope: email",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${emailScopeConsentText}"
+ },
+ "protocolMappers" : [ {
+ "id" : "d61cee0d-1b12-45dc-8c33-3f98dfc3759b",
+ "name" : "email verified",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "emailVerified",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "email_verified",
+ "jsonType.label" : "boolean"
+ }
+ }, {
+ "id" : "a27eab0d-a386-4170-bd09-195419898c65",
+ "name" : "email",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "email",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "email",
+ "jsonType.label" : "String"
+ }
+ } ]
+ }, {
+ "id" : "ad8ae47f-c053-4a2c-9fa6-45ce4d084918",
+ "name" : "role_list",
+ "description" : "SAML role list",
+ "protocol" : "saml",
+ "attributes" : {
+ "consent.screen.text" : "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen" : "true"
+ },
+ "protocolMappers" : [ {
+ "id" : "1e348d7d-b6c5-4ded-b4b1-7b9396e9dc23",
+ "name" : "role list",
+ "protocol" : "saml",
+ "protocolMapper" : "saml-role-list-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "single" : "false",
+ "attribute.nameformat" : "Basic",
+ "attribute.name" : "Role"
+ }
+ } ]
+ }, {
+ "id" : "91142502-882d-4400-a156-31b2cd675883",
+ "name" : "profile",
+ "description" : "OpenID Connect built-in scope: profile",
+ "protocol" : "openid-connect",
+ "attributes" : {
+ "include.in.token.scope" : "true",
+ "display.on.consent.screen" : "true",
+ "consent.screen.text" : "${profileScopeConsentText}"
+ },
+ "protocolMappers" : [ {
+ "id" : "61045aee-4861-4b8b-8c89-3722b95e3a1f",
+ "name" : "zoneinfo",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "zoneinfo",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "zoneinfo",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "600f53e5-c004-4703-b2c2-59f0e0750333",
+ "name" : "birthdate",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "birthdate",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "birthdate",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "9ab1cd6e-ab96-4e1b-ade4-19b786e89cbf",
+ "name" : "picture",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "picture",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "picture",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "67fe30ec-1e04-4690-af02-f42ed432f200",
+ "name" : "gender",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "gender",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "gender",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "3d7f8de5-59f3-4f4d-b54f-400bad91aa9f",
+ "name" : "profile",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "profile",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "profile",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "c5789ff0-9256-4699-bbef-3730dcc7ad79",
+ "name" : "username",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "username",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "preferred_username",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "eb603ab3-b397-4954-837f-f7ea244ed34b",
+ "name" : "family name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "lastName",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "family_name",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "7906a882-15f2-4424-bb36-4d9993592cbb",
+ "name" : "locale",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "locale",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "locale",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "8210672c-e444-48d1-9f67-cce498840684",
+ "name" : "full name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-full-name-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "id.token.claim" : "true",
+ "introspection.token.claim" : "true",
+ "access.token.claim" : "true",
+ "userinfo.token.claim" : "true"
+ }
+ }, {
+ "id" : "4f4b1f35-d8b1-4f84-b79a-5b0fb6aa3781",
+ "name" : "updated at",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "updatedAt",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "updated_at",
+ "jsonType.label" : "long"
+ }
+ }, {
+ "id" : "b9f6da7e-dc3c-447d-8808-77c1054d8c67",
+ "name" : "given name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "firstName",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "given_name",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "dd746558-75ce-475c-a947-b5b4324251fb",
+ "name" : "nickname",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "nickname",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "nickname",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "ad1e4f87-aff2-4ac2-b01c-2b9871f220be",
+ "name" : "middle name",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "middleName",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "middle_name",
+ "jsonType.label" : "String"
+ }
+ }, {
+ "id" : "7423f684-4ccf-4303-9cf4-0ae65087098e",
+ "name" : "website",
+ "protocol" : "openid-connect",
+ "protocolMapper" : "oidc-usermodel-attribute-mapper",
+ "consentRequired" : false,
+ "config" : {
+ "introspection.token.claim" : "true",
+ "userinfo.token.claim" : "true",
+ "user.attribute" : "website",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true",
+ "claim.name" : "website",
+ "jsonType.label" : "String"
+ }
+ } ]
+ } ],
+ "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ],
+ "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ],
+ "browserSecurityHeaders" : {
+ "contentSecurityPolicyReportOnly" : "",
+ "xContentTypeOptions" : "nosniff",
+ "referrerPolicy" : "no-referrer",
+ "xRobotsTag" : "none",
+ "xFrameOptions" : "SAMEORIGIN",
+ "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+ "xXSSProtection" : "1; mode=block",
+ "strictTransportSecurity" : "max-age=31536000; includeSubDomains"
+ },
+ "smtpServer" : { },
+ "eventsEnabled" : false,
+ "eventsListeners" : [ "jboss-logging" ],
+ "enabledEventTypes" : [ ],
+ "adminEventsEnabled" : false,
+ "adminEventsDetailsEnabled" : false,
+ "identityProviders" : [ ],
+ "identityProviderMappers" : [ ],
+ "components" : {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ {
+ "id" : "056807c0-ad53-468f-8d34-a349c07c4280",
+ "name" : "Full Scope Disabled",
+ "providerId" : "scope",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : { }
+ }, {
+ "id" : "3a190cb2-fe17-428a-b2b8-6f4bb6113098",
+ "name" : "Consent Required",
+ "providerId" : "consent-required",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : { }
+ }, {
+ "id" : "5b9cccbc-6cd5-4049-a6ce-1ad9bc79b6dc",
+ "name" : "Allowed Protocol Mapper Types",
+ "providerId" : "allowed-protocol-mappers",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-address-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper" ]
+ }
+ }, {
+ "id" : "784f4895-a639-4bd1-8b05-e9f66071546f",
+ "name" : "Allowed Protocol Mapper Types",
+ "providerId" : "allowed-protocol-mappers",
+ "subType" : "authenticated",
+ "subComponents" : { },
+ "config" : {
+ "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ]
+ }
+ }, {
+ "id" : "5a68aba8-d7fe-4127-932e-f00e974f19d5",
+ "name" : "Allowed Client Scopes",
+ "providerId" : "allowed-client-templates",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "allow-default-scopes" : [ "true" ]
+ }
+ }, {
+ "id" : "f38a4b7f-5bac-4a81-bac7-bbbb4cd3534f",
+ "name" : "Allowed Client Scopes",
+ "providerId" : "allowed-client-templates",
+ "subType" : "authenticated",
+ "subComponents" : { },
+ "config" : {
+ "allow-default-scopes" : [ "true" ]
+ }
+ }, {
+ "id" : "6b3aac01-0523-4a93-ab27-705f020f6058",
+ "name" : "Trusted Hosts",
+ "providerId" : "trusted-hosts",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "host-sending-registration-request-must-match" : [ "true" ],
+ "client-uris-must-match" : [ "true" ]
+ }
+ }, {
+ "id" : "ef6ed5f6-bd6a-4fd3-a6fa-b60d0705cf25",
+ "name" : "Max Clients Limit",
+ "providerId" : "max-clients",
+ "subType" : "anonymous",
+ "subComponents" : { },
+ "config" : {
+ "max-clients" : [ "200" ]
+ }
+ } ],
+ "org.keycloak.keys.KeyProvider" : [ {
+ "id" : "5eadc3ec-7dec-4222-ba44-8ab5a3c757fc",
+ "name" : "rsa-generated",
+ "providerId" : "rsa-generated",
+ "subComponents" : { },
+ "config" : {
+ "privateKey" : [ "MIIEowIBAAKCAQEAidPZ4KxFqYXTxbLkdz1GWTTZuN5Bir5cXCMbwvdKTasycOutXwj9nyd+NmB9boa8ynLCOtGJZ2XXaeRoPTKHoAF6vvGcDY4JrNatlbADLbIlc6L9Ie21wRbiewhD8Vi8/Ae/tBk24yBqH3MbuxKG4T+awJPPN4M+DQtbSUI+Ai3dZdp7eDnx9T3n9IwPkSJ1Xw96B9mnPE9IKj8t6bRveN2kqt3kghPbHGQIdNvQVImu418S6M2ZUozS1h6Mrt1KHjiwdlT5+cneSmKD7VcIjCRyD/AE8RlSUtWiTgWIKPl493nRG0W2WQbvT1cImxUM3vfCvmFgomOiZV6IWvhzZwIDAQABAoIBAAwUdVQrxS9Ge3koZL3OsQvAhhSd5kCyzoWEzjoYUnrcNl+PSHK75v68B2PcsSGuzX0w8bUWCGT3mBm/2yMB+sEbYBa8NmE5wb9UUYJ9Gso2gvHPp0ACBZwW1WLTJqFQlb/BLhwq2ZSukp6PCC8tQIUdfgLC8bZ72kRqamXRWH34Ri3+ZCjeENEbgl/yem/+FAtX89VYQvK+mOJ2ycvlDqgx1I+tRXMy1OdzwzxUExpBF3VuNfOiHFEf6MM9WYM4i1nnUoQoMLXApH+PdZ2ozaFsVIdL78t5GlnzcUhF98Vn/KuuBTKQ5zrKh97XmKQpVz+Or8/eemP8QuGMqJ9XW4ECgYEAwbzeH3zf3kZkmbz7OCYywonv9FAIHQruG42PIfERtX9AGx4pUF6/9uJFGZgs8qUyKAS+aYoh2YLGuq1vyGmdbDvTPK7YTjndzaD1Pu6tMNR328Qrq6XjX3P34uMFOPWQZaiLs3shPqt53nVHIJnMq0r2Qa1F0yU0/ljoLT4HWLUCgYEAth8nww6BlBOO/SCH0lDirtjd9F4kmhp95Irf/Zb+PruN/m319GJsfRVd7AFwI6R1Zn1k97pM6QbZnsr+xxjyNA8zsgUMf6Ey6tvhRdYWcUogjkpR8COYGIdBFHeDhC0A3xbVHlABFuOq5aYHuo5i+FtoN67G81nxuNNvfBfzeSsCgYAaKjrJf+DwMQuXI8m97JLAaUap9UETQw2jVPv2tosQuw3aW7lyrEk6u5ZffYjcgGkEtJdOw6QMrzfNsGy/YVwsaOv/bxGxhukow0s745oqVReW66iGNxI9KvlYgBaNecS1pgywAbocr6X24Fnp0jutCqO1fQRIHudITpOUwKLXXQKBgEcManenDhgVdT/809/RnE1330b4/zB7S0fAgdetcyxIAQIUJdKcHxTBJC3w9a2B+CbunlgifuSdvoI7UR+c9MgochNt8s1LttB2zTBwb7rsnzzDc6nKSQiwo0v3v//tipx9jNDRh0tFsCL9HaDC8YtQJZ0rewPuv+VJ3wE7WsmXAoGBAI6BjMh+JeHW0/D/v7H9ffldx57L3zHuxD9HsopQRTicHDknZRfcRSoE5LiknkVn+Voz4FpFCC8NHv1cj0thNLwlXfGZkUXQzqh/EP5jR0+3f8jeepzcjuxZMoVn0QsaQ5Db2oo2iyZyQ4ww+HqCSlJVZt0XH6BXn16B7fJO+osb" ],
+ "keyUse" : [ "SIG" ],
+ "certificate" : [ "MIICvTCCAaUCBgGVcBCquTANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdjb3Vyc2UtbWFuYWdlbWVudC1yZWFsbTAeFw0yNTAzMDcxMDAzMTVaFw0zNTAzMDcxMDA0NTVaMCIxIDAeBgNVBAMMF2NvdXJzZS1tYW5hZ2VtZW50LXJlYWxtMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAidPZ4KxFqYXTxbLkdz1GWTTZuN5Bir5cXCMbwvdKTasycOutXwj9nyd+NmB9boa8ynLCOtGJZ2XXaeRoPTKHoAF6vvGcDY4JrNatlbADLbIlc6L9Ie21wRbiewhD8Vi8/Ae/tBk24yBqH3MbuxKG4T+awJPPN4M+DQtbSUI+Ai3dZdp7eDnx9T3n9IwPkSJ1Xw96B9mnPE9IKj8t6bRveN2kqt3kghPbHGQIdNvQVImu418S6M2ZUozS1h6Mrt1KHjiwdlT5+cneSmKD7VcIjCRyD/AE8RlSUtWiTgWIKPl493nRG0W2WQbvT1cImxUM3vfCvmFgomOiZV6IWvhzZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAodMC83G53C72JRQ+Z7ezOhh1fda2jou28INh0BMdCFK5Qgizuz4K6wXZ9bP7QPmxbHfnItxn1KfpLmiYwt7GhzlEXuP8/NWp+YRl3IHvFX8UqqwRQribCiUwZVJ3zNBeFoz0y7n8t0enp4ifCNq5QkKqB5ZSyqtGxPiWCGR2oFuMZkx9woZqDT57prSZBKrI4cT4iFYBx7Ha9AC2De3hHTVezpcTMWDr/IhwAGW954t7FCX9hGY+8a3Zfwxvs3MT4Vdi2osFa0LuGaN4I0+QbD445RgTokx6o2fOg53C/pxdKHhlTVVwM30g6nS8l3l1ylsP3nDmbW39/SQTGodwT" ],
+ "priority" : [ "100" ]
+ }
+ }, {
+ "id" : "80348257-ca20-4fc1-b0df-b2edfc047c57",
+ "name" : "rsa-enc-generated",
+ "providerId" : "rsa-enc-generated",
+ "subComponents" : { },
+ "config" : {
+ "privateKey" : [ "MIIEowIBAAKCAQEAuMHwCZIbENAKMn3CNExyQVYTyY95cYz9dVzamp8Kg6dLpDa/gqDOds4Dbm/PXIEUG48Xlcs6r06OCYZ2bpOjccQMoJQkCjR1TA61F813VOikPkgDZKrk+TBwB7Esa5l1mtZazMsmTss+BK7M425S7EOYZBBGyHIEpZCPQA+aTGYiEFXSJyFBcmdyfpcFRuTGCNZ409I84HRgARdrQCzFp+m6mNdh+oyeu8DqbuAQibQ3r3yRgbeiAKaXtPqnFlFkRAoTtF7WsbRQSWto8qzjMCTgwZsOkOy+1d/RGYlnb1lDzuK6766/KL0qjbitg43rzHy94T/Am/ZzSW6EDz13GwIDAQABAoIBACqdMMxEUtHR6kgzEXQWndPMEsxOt2glaOCFBjbbsAUltSCvMATlKjnk0o3KZD2l2AlzO7FlKqf8qm4wAWkHzI8U9QSkpptj9hoQ2lwfHAMNlbR3z7CMaju/nFdp2iEHSETqZA8QXEmYPzm9R6y9aYrtHF0YE/tvsPqvGRthfelY0E0MHBuCENpy8uHgY+u4dpUy6ctfCgnQNdeId86UUaP4dgBKHHu5AIXE3UIpcaVBSflo2RHFATa65dov0NjnzoTsuEp78a5JLpRIALVb/6ZBnkdheWK7KwQWPIbcrKuKpkbKiKP35TBqqadTxi6reJj9FJgemMNuYArGhwsNC6ECgYEA9APMr6p38HhUjQw8OVV8vNR83rdDZqTjgHKfHFiZVsu2uH/Q9+lftEOoodHPr6rWi8iRRyN7uyadW3UbCyLqvnAy0H7SQWlomQr1K6WnodffPDBRL/oemgVUm0LpndJSI47eEHVQw2BqdW/sNPkFZTJuacRDnUl8UzVtzdOtxd8CgYEAwdUMITTRNfBSe/UK9xfgtu/E2xsfCZE48u9wQgyyY0Vvmsb8lBqzuErjDeWjc4WZVQKT/u0RgxIxZZ86IUGiAAxrQouUGYvaxh9kVq5sNQuQ1AnOoeNYYJON14WVXPNGo07XFawmJNRQ6T6hObPCurAt8Alrw10x66Av1HypHkUCgYEAsEEWVkTBwbaJDCCsV5WCNjsdvxmM29V6ZstkVmtAx0r6PqZEYO33LRQ5+Di+1VqHAgzrswDUXMh1nRwnXpHoEYqliagxYwIqhXE+yjorrWFqY+Van1cBR6tWA5f/evnyTrDhge3bWmSIQS+I5HKDDSeyTT9wv2nXHZtYJvYhbO0CgYAd0w4+vcXDKutGL3o0cvQcsd/0ZsPr0PCWUMlRIGsCY1i81AlhMIqevMcbCE6SI+hn8DGddBTRuibnoAxtuY+XulF2t8td8usCuSnTPJthvhnWxnuI5VZf5dRZaTCGUNh5oFPv/cEQwaqBeSWNn19DIoCvAoQWV2yq+MbjRxsukQKBgCbVEm4pGpUmD98P3tLjEqFExhF9XvOselF1CCEqTT2gd0k1rAP0vhDEBxIlMSg9XOdpqWyAvUlcv2rmTdKVbN6T7v6IHmh6uSkr/ArauSJERucp1PTwCigk0Rfyfsr9EO++XD3CtjEG5f9XwY+4Q9atMdQ3+xdsFxb9P+mHG8OQ" ],
+ "keyUse" : [ "ENC" ],
+ "certificate" : [ "MIICvTCCAaUCBgGVcBCrbDANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdjb3Vyc2UtbWFuYWdlbWVudC1yZWFsbTAeFw0yNTAzMDcxMDAzMTVaFw0zNTAzMDcxMDA0NTVaMCIxIDAeBgNVBAMMF2NvdXJzZS1tYW5hZ2VtZW50LXJlYWxtMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMHwCZIbENAKMn3CNExyQVYTyY95cYz9dVzamp8Kg6dLpDa/gqDOds4Dbm/PXIEUG48Xlcs6r06OCYZ2bpOjccQMoJQkCjR1TA61F813VOikPkgDZKrk+TBwB7Esa5l1mtZazMsmTss+BK7M425S7EOYZBBGyHIEpZCPQA+aTGYiEFXSJyFBcmdyfpcFRuTGCNZ409I84HRgARdrQCzFp+m6mNdh+oyeu8DqbuAQibQ3r3yRgbeiAKaXtPqnFlFkRAoTtF7WsbRQSWto8qzjMCTgwZsOkOy+1d/RGYlnb1lDzuK6766/KL0qjbitg43rzHy94T/Am/ZzSW6EDz13GwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCs80MMiCnswWt7I9Zc7J7VPADb5ihFDeEYAUSgaHBqZIhUcipoCZiy9gpyVnd8AKOp/Ba6pOyf6O9mJbmtyn2yWY0xkQ+MMPI2GajKxt2Q/XHLAhl73uR37zBiQR9U8lGOOa1zyJYVV3M7ZUQP7ummifcdXCxCn2MQZWVjQdX5GES4z/qtoihZ3vuLKjyTzahuhvnQmQZ8bs2Wzj6iFnJ3CJiKLO39rYOSeA7Sre3rx1HVT2WLz2QEYhipWZBjP/8rVVwMxTbTu31NWFH2ajO2kOtVQLakOBfL6PsXLaaOicsEyISOzTa4MQGH0x/p3sPzN23Ub5Pp5a2FjCV4Z47E" ],
+ "priority" : [ "100" ],
+ "algorithm" : [ "RSA-OAEP" ]
+ }
+ }, {
+ "id" : "56fc4025-725c-4c72-9f5d-79ef71c7599c",
+ "name" : "hmac-generated-hs512",
+ "providerId" : "hmac-generated",
+ "subComponents" : { },
+ "config" : {
+ "kid" : [ "af37dab2-b140-4eca-a3c6-9bad3bd76487" ],
+ "secret" : [ "qJobUjOEpg01gbiknucGfXXwC_XS5n8ML7fWrChIHjHUP3bv4ngmv5_koqJLtRAKawTz3OxkC96W1BiULTJc_wU2aqMvZpP6NhueVleFjolHRf8CtiXwCEC_KyIXNndVozDGDbp-m4AUpJDYYs33L6qw-0Xx6H6A9xvA78EPtHI" ],
+ "priority" : [ "100" ],
+ "algorithm" : [ "HS512" ]
+ }
+ }, {
+ "id" : "e26face5-98a9-4669-a375-921212bf3e20",
+ "name" : "aes-generated",
+ "providerId" : "aes-generated",
+ "subComponents" : { },
+ "config" : {
+ "kid" : [ "a266992e-30e4-4cb9-b418-88cc0b60f395" ],
+ "secret" : [ "IgHSVnPC_d0XBtc-ZtYxSA" ],
+ "priority" : [ "100" ]
+ }
+ } ]
+ },
+ "internationalizationEnabled" : false,
+ "supportedLocales" : [ ],
+ "authenticationFlows" : [ {
+ "id" : "38d48421-ff4c-47fe-811d-c457925d7a8e",
+ "alias" : "Account verification options",
+ "description" : "Method with which to verity the existing account",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "idp-email-verification",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Verify Existing Account by Re-authentication",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "12dcdc35-b589-45e0-9362-73c41fa7217b",
+ "alias" : "Browser - Conditional OTP",
+ "description" : "Flow to determine if the OTP is required for the authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "auth-otp-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "faea3b92-4c6d-4eef-bbfc-a3fc502014b5",
+ "alias" : "Direct Grant - Conditional OTP",
+ "description" : "Flow to determine if the OTP is required for the authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "direct-grant-validate-otp",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "0afc17e3-c251-4b42-b78d-5cb2073a0e2a",
+ "alias" : "First broker login - Conditional OTP",
+ "description" : "Flow to determine if the OTP is required for the authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "auth-otp-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "4e615b8f-db82-4b1b-b4c0-74262710613b",
+ "alias" : "Handle Existing Account",
+ "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "idp-confirm-link",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Account verification options",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "10fe0187-11b4-47d8-bf7a-ef80e9f420fb",
+ "alias" : "Reset - Conditional OTP",
+ "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "conditional-user-configured",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "reset-otp",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "6ef4e983-71f0-4160-9794-b7afc9077024",
+ "alias" : "User creation or linking",
+ "description" : "Flow for the existing/non-existing user alternatives",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticatorConfig" : "create unique user config",
+ "authenticator" : "idp-create-user-if-unique",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Handle Existing Account",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "004fb2d5-c267-46ac-ac13-92d3294168d1",
+ "alias" : "Verify Existing Account by Re-authentication",
+ "description" : "Reauthentication of existing account",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "idp-username-password-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "First broker login - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "8b456769-9ca4-40fc-8321-c754ac07a715",
+ "alias" : "browser",
+ "description" : "browser based authentication",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "auth-cookie",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "auth-spnego",
+ "authenticatorFlow" : false,
+ "requirement" : "DISABLED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "identity-provider-redirector",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 25,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 30,
+ "autheticatorFlow" : true,
+ "flowAlias" : "forms",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "acfe8656-cde0-4efe-8916-fe7bb6e3b3d4",
+ "alias" : "clients",
+ "description" : "Base authentication for clients",
+ "providerId" : "client-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "client-secret",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "client-jwt",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "client-secret-jwt",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 30,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "client-x509",
+ "authenticatorFlow" : false,
+ "requirement" : "ALTERNATIVE",
+ "priority" : 40,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "422cbfc8-c4cb-4154-9f12-54af031e1b4a",
+ "alias" : "direct grant",
+ "description" : "OpenID Connect Resource Owner Grant",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "direct-grant-validate-username",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "direct-grant-validate-password",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 30,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Direct Grant - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "7ff9ec97-27a1-4a9b-b997-df42c6549ff3",
+ "alias" : "docker auth",
+ "description" : "Used by Docker clients to authenticate against the IDP",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "docker-http-basic-authenticator",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "31a30a77-16c4-4ba4-89e4-0f9254ca22ad",
+ "alias" : "first broker login",
+ "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticatorConfig" : "review profile config",
+ "authenticator" : "idp-review-profile",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "User creation or linking",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "a3da8110-72aa-4802-8ce3-adce2f153b20",
+ "alias" : "forms",
+ "description" : "Username, password, otp and other auth forms.",
+ "providerId" : "basic-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "auth-username-password-form",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 20,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Browser - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "efc258cc-a03a-4ebb-bb78-40023cca87b9",
+ "alias" : "registration",
+ "description" : "registration flow",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "registration-page-form",
+ "authenticatorFlow" : true,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : true,
+ "flowAlias" : "registration form",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "0ba352b4-43f7-4304-bb6f-ff12f40170e8",
+ "alias" : "registration form",
+ "description" : "registration form",
+ "providerId" : "form-flow",
+ "topLevel" : false,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "registration-user-creation",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "registration-password-action",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 50,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "registration-recaptcha-action",
+ "authenticatorFlow" : false,
+ "requirement" : "DISABLED",
+ "priority" : 60,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "registration-terms-and-conditions",
+ "authenticatorFlow" : false,
+ "requirement" : "DISABLED",
+ "priority" : 70,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "ae8a3ab5-8c0e-46f4-96e8-a68cc4d076fc",
+ "alias" : "reset credentials",
+ "description" : "Reset credentials for a user if they forgot their password or something",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "reset-credentials-choose-user",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "reset-credential-email",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 20,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticator" : "reset-password",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 30,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ }, {
+ "authenticatorFlow" : true,
+ "requirement" : "CONDITIONAL",
+ "priority" : 40,
+ "autheticatorFlow" : true,
+ "flowAlias" : "Reset - Conditional OTP",
+ "userSetupAllowed" : false
+ } ]
+ }, {
+ "id" : "a266d7d9-e280-467f-a8e2-37ba27ff378d",
+ "alias" : "saml ecp",
+ "description" : "SAML ECP Profile Authentication Flow",
+ "providerId" : "basic-flow",
+ "topLevel" : true,
+ "builtIn" : true,
+ "authenticationExecutions" : [ {
+ "authenticator" : "http-basic-authenticator",
+ "authenticatorFlow" : false,
+ "requirement" : "REQUIRED",
+ "priority" : 10,
+ "autheticatorFlow" : false,
+ "userSetupAllowed" : false
+ } ]
+ } ],
+ "authenticatorConfig" : [ {
+ "id" : "22da0698-ed7a-4f57-8d2c-67cb907662df",
+ "alias" : "create unique user config",
+ "config" : {
+ "require.password.update.after.registration" : "false"
+ }
+ }, {
+ "id" : "20949bd1-49f2-486e-9df2-28740a22269d",
+ "alias" : "review profile config",
+ "config" : {
+ "update.profile.on.first.login" : "missing"
+ }
+ } ],
+ "requiredActions" : [ {
+ "alias" : "CONFIGURE_TOTP",
+ "name" : "Configure OTP",
+ "providerId" : "CONFIGURE_TOTP",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 10,
+ "config" : { }
+ }, {
+ "alias" : "TERMS_AND_CONDITIONS",
+ "name" : "Terms and Conditions",
+ "providerId" : "TERMS_AND_CONDITIONS",
+ "enabled" : false,
+ "defaultAction" : false,
+ "priority" : 20,
+ "config" : { }
+ }, {
+ "alias" : "UPDATE_PASSWORD",
+ "name" : "Update Password",
+ "providerId" : "UPDATE_PASSWORD",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 30,
+ "config" : { }
+ }, {
+ "alias" : "UPDATE_PROFILE",
+ "name" : "Update Profile",
+ "providerId" : "UPDATE_PROFILE",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 40,
+ "config" : { }
+ }, {
+ "alias" : "VERIFY_EMAIL",
+ "name" : "Verify Email",
+ "providerId" : "VERIFY_EMAIL",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 50,
+ "config" : { }
+ }, {
+ "alias" : "delete_account",
+ "name" : "Delete Account",
+ "providerId" : "delete_account",
+ "enabled" : false,
+ "defaultAction" : false,
+ "priority" : 60,
+ "config" : { }
+ }, {
+ "alias" : "webauthn-register",
+ "name" : "Webauthn Register",
+ "providerId" : "webauthn-register",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 70,
+ "config" : { }
+ }, {
+ "alias" : "webauthn-register-passwordless",
+ "name" : "Webauthn Register Passwordless",
+ "providerId" : "webauthn-register-passwordless",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 80,
+ "config" : { }
+ }, {
+ "alias" : "VERIFY_PROFILE",
+ "name" : "Verify Profile",
+ "providerId" : "VERIFY_PROFILE",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 90,
+ "config" : { }
+ }, {
+ "alias" : "delete_credential",
+ "name" : "Delete Credential",
+ "providerId" : "delete_credential",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 100,
+ "config" : { }
+ }, {
+ "alias" : "update_user_locale",
+ "name" : "Update User Locale",
+ "providerId" : "update_user_locale",
+ "enabled" : true,
+ "defaultAction" : false,
+ "priority" : 1000,
+ "config" : { }
+ } ],
+ "browserFlow" : "browser",
+ "registrationFlow" : "registration",
+ "directGrantFlow" : "direct grant",
+ "resetCredentialsFlow" : "reset credentials",
+ "clientAuthenticationFlow" : "clients",
+ "dockerAuthenticationFlow" : "docker auth",
+ "firstBrokerLoginFlow" : "first broker login",
+ "attributes" : {
+ "cibaBackchannelTokenDeliveryMode" : "poll",
+ "cibaExpiresIn" : "120",
+ "cibaAuthRequestedUserHint" : "login_hint",
+ "oauth2DeviceCodeLifespan" : "600",
+ "oauth2DevicePollingInterval" : "5",
+ "parRequestUriLifespan" : "60",
+ "cibaInterval" : "5",
+ "realmReusableOtpCode" : "false"
+ },
+ "keycloakVersion" : "24.0.5",
+ "userManagedAccessAllowed" : false,
+ "clientProfiles" : {
+ "profiles" : [ ]
+ },
+ "clientPolicies" : {
+ "policies" : [ ]
+ }
+}
\ No newline at end of file
diff --git a/kubernetes/infra/keycloak/keycloak.yml b/kubernetes/infra/keycloak/keycloak.yml
new file mode 100644
index 0000000..9c82f8b
--- /dev/null
+++ b/kubernetes/infra/keycloak/keycloak.yml
@@ -0,0 +1,54 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: keycloak
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: keycloak
+ template:
+ metadata:
+ labels:
+ app: keycloak
+ spec:
+ containers:
+ - name: keycloak
+ image: quay.io/keycloak/keycloak:24.0
+ args: ["start-dev"] # Runs Keycloak in standalone mode with H2
+ env:
+ - name: KEYCLOAK_ADMIN
+ value: admin
+ - name: KEYCLOAK_ADMIN_PASSWORD
+ value: admin
+ ports:
+ - containerPort: 8080
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: keycloak
+spec:
+ ports:
+ - name: http
+ port: 8080
+ targetPort: 8080
+ selector:
+ app: keycloak
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: keycloak-ingress
+spec:
+ rules:
+ - host: keycloak.local
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: keycloak
+ port:
+ number: 8080
diff --git a/kubernetes/infra/mongodb/mongo.yml b/kubernetes/infra/mongodb/mongo.yml
new file mode 100644
index 0000000..646559a
--- /dev/null
+++ b/kubernetes/infra/mongodb/mongo.yml
@@ -0,0 +1,40 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: review-mongodb
+ labels:
+ app: review-mongodb
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: review-mongodb
+ template:
+ metadata:
+ labels:
+ app: review-mongodb
+ spec:
+ containers:
+ - name: review-mongodb
+ image: mongo:6.0.4 # Latest stable MongoDB version
+ ports:
+ - containerPort: 27017
+ volumeMounts:
+ - name: mongodb-storage
+ mountPath: /data/db
+ volumes:
+ - name: mongodb-storage
+ emptyDir: {} # Use PersistentVolumeClaim for production
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: review-mongodb
+spec:
+ selector:
+ app: review-mongodb
+ ports:
+ - protocol: TCP
+ port: 27017
+ targetPort: 27017
+ type: ClusterIP # Change to LoadBalancer or NodePort if needed
diff --git a/kubernetes/infra/postgres/postgres.yml b/kubernetes/infra/postgres/postgres.yml
new file mode 100644
index 0000000..934d4e0
--- /dev/null
+++ b/kubernetes/infra/postgres/postgres.yml
@@ -0,0 +1,47 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: course-postgres
+ labels:
+ db: course-postgres
+spec:
+ selector:
+ matchLabels:
+ db: course-postgres
+ template:
+ metadata:
+ labels:
+ db: course-postgres
+ spec:
+ containers:
+ - name: course-postgres
+ image: postgres:17.4
+ env:
+ - name: POSTGRES_USER
+ value: user
+ - name: POSTGRES_PASSWORD
+ value: pwd
+ - name: POSTGRES_DB
+ value: course_db
+ resources:
+ requests:
+ cpu: 100m
+ memory: 60Mi
+ limits:
+ cpu: 200m
+ memory: 120Mi
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: course-postgres
+ labels:
+ db: course-postgres
+spec:
+ type: ClusterIP
+ selector:
+ db: course-postgres
+ ports:
+ - protocol: TCP
+ port: 5432
+ targetPort: 5432
\ No newline at end of file
diff --git a/microservices/course-composite-service/.gitattributes b/microservices/course-composite-service/.gitattributes
new file mode 100644
index 0000000..3b41682
--- /dev/null
+++ b/microservices/course-composite-service/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/microservices/course-composite-service/.mvn/wrapper/maven-wrapper.properties b/microservices/course-composite-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..d58dfb7
--- /dev/null
+++ b/microservices/course-composite-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/microservices/course-composite-service/Dockerfile b/microservices/course-composite-service/Dockerfile
index f5249bc..9316c78 100644
--- a/microservices/course-composite-service/Dockerfile
+++ b/microservices/course-composite-service/Dockerfile
@@ -1,20 +1,25 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
+FROM eclipse-temurin:17.0.5_8-jre-focal as builder
+WORKDIR extracted
+
+ADD ./target/*.jar app.jar
+
+RUN java -Djarmode=layertools -jar app.jar extract
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
+FROM eclipse-temurin:17.0.5_8-jre-focal
WORKDIR application
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
+# Copy the dependencies layer from the builder stage
+COPY --from=builder extracted/dependencies/ ./
+# Copy the Spring Boot loader layer from the builder stage
+COPY --from=builder extracted/spring-boot-loader/ ./
+# Copy the snapshot dependencies layer from the builder stage
+COPY --from=builder extracted/snapshot-dependencies/ ./
+# Copy the application layer from the builder stage
+COPY --from=builder extracted/application/ ./
+
+# Expose port 8080
EXPOSE 8080
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
+# Set the entry point to launch the application
+ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
+
diff --git a/microservices/course-composite-service/kubernetes/deployment.yml b/microservices/course-composite-service/kubernetes/deployment.yml
new file mode 100644
index 0000000..ce7dd9e
--- /dev/null
+++ b/microservices/course-composite-service/kubernetes/deployment.yml
@@ -0,0 +1,37 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: course-composite-service
+ labels:
+ app: course-composite-service
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: course-composite-service
+ template:
+ metadata:
+ labels:
+ app: course-composite-service
+ spec:
+ containers:
+ - name: course-composite-service
+ image: course-composite-service # Please rename these while building docker image
+ imagePullPolicy: IfNotPresent
+ lifecycle:
+ preStop:
+ exec:
+ command: [ "sh", "-c", "sleep 5" ]
+ ports:
+ - containerPort: 5000
+ env:
+ - name: COURSE_SERVICE_URL
+ value: http://course-service
+ - name: REVIEW_SERVICE_URL
+ value: http://review-service
+ - name: APP_KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+ - name: KEYCLOAK_ISSUER_URI
+ value: http://keycloak:8080/realms/course-management-realm
+ - name: KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
\ No newline at end of file
diff --git a/microservices/course-composite-service/kubernetes/service.yml b/microservices/course-composite-service/kubernetes/service.yml
new file mode 100644
index 0000000..1da12b0
--- /dev/null
+++ b/microservices/course-composite-service/kubernetes/service.yml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: course-composite-service
+ labels:
+ app: course-composite-service
+spec:
+ type: ClusterIP
+ selector:
+ app: course-composite-service
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 5000
\ No newline at end of file
diff --git a/microservices/course-composite-service/mvnw b/microservices/course-composite-service/mvnw
new file mode 100755
index 0000000..19529dd
--- /dev/null
+++ b/microservices/course-composite-service/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/microservices/course-composite-service/mvnw.cmd b/microservices/course-composite-service/mvnw.cmd
new file mode 100644
index 0000000..249bdf3
--- /dev/null
+++ b/microservices/course-composite-service/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/microservices/course-composite-service/pom.xml b/microservices/course-composite-service/pom.xml
index b87e1bf..c6d2224 100644
--- a/microservices/course-composite-service/pom.xml
+++ b/microservices/course-composite-service/pom.xml
@@ -13,9 +13,21 @@
1.0.0
course-composite-service
Demo project for Spring Boot
+
+
+
+
+
+
+
+
+
+
+
+
+
17
- 2024.0.0
@@ -26,16 +38,7 @@
org.springframework.boot
spring-boot-starter-webflux
-
-
- org.springdoc
- springdoc-openapi-starter-webflux-ui
- 2.0.4
-
+
org.springframework.boot
spring-boot-starter-security
@@ -43,49 +46,21 @@
org.springframework.boot
- spring-boot-configuration-processor
- true
-
-
- org.projectlombok
- lombok
- true
-
-
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-client
-
-
- org.springframework.security
- spring-security-oauth2-resource-server
-
-
- org.springframework.security
- spring-security-oauth2-jose
-
-
- org.springframework.cloud
- spring-cloud-starter-config
+ spring-boot-starter-oauth2-resource-server
- org.springframework.retry
- spring-retry
-
-
-
-
-
- io.javatab.microservices.api
- api
+ io.javatab.util
+ util
1.0.0
compile
+
- io.javatab.microservices.util
- util
- 1.0.0
- compile
+ org.projectlombok
+ lombok
+ provided
+
org.springframework.boot
spring-boot-starter-test
@@ -98,36 +73,11 @@
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
-
- repackage
-
-
-
+ org.springframework.boot
+ spring-boot-maven-plugin
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplication.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplication.java
index 4e0654d..49f6fcc 100644
--- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplication.java
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplication.java
@@ -1,22 +1,13 @@
package io.javatab.microservices.composite.course;
-import io.javatab.microservices.composite.course.configuration.OpenApiConfigProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;
-import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
-import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
@SpringBootApplication
-@ComponentScan("io.javatab")
-@EnableConfigurationProperties(OpenApiConfigProperties.class)
+@ComponentScan({"io.javatab"})
public class CourseCompositeServiceApplication {
public static void main(String[] args) {
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/config/SecurityConfig.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/config/SecurityConfig.java
new file mode 100644
index 0000000..e1760c3
--- /dev/null
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/config/SecurityConfig.java
@@ -0,0 +1,119 @@
+package io.javatab.microservices.composite.course.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+@Configuration
+@EnableWebFluxSecurity
+public class SecurityConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
+
+ private String jwkSetUri;
+
+ public SecurityConfig(@Value("${app.jwk-set-uri}") String jwkSetUri) {
+ this.jwkSetUri = jwkSetUri;
+ }
+
+ @Bean
+ public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
+ http
+ .authorizeExchange(exchanges -> exchanges
+ .pathMatchers("/api/course-aggregate/**").hasAnyRole("COURSE-READ", "REVIEW-READ")
+ .anyExchange().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))
+ );
+
+ // Add filter to log roles
+ http.addFilterAt((exchange, chain) -> logRoles(exchange).then(chain.filter(exchange)),
+ SecurityWebFiltersOrder.AUTHORIZATION);
+
+ return http.build();
+ }
+
+ @Bean
+ public ReactiveJwtDecoder jwtDecoder() {
+ return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
+ }
+
+ @Bean
+ public Converter> grantedAuthoritiesExtractor() {
+ return new Converter>() {
+ @Override
+ public Mono convert(Jwt jwt) {
+ Collection authorities = new ArrayList<>();
+
+ // Extract realm roles
+ Map realmAccess = jwt.getClaim("realm_access");
+ if (realmAccess != null && realmAccess.containsKey("roles")) {
+ List roles = (List) realmAccess.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+
+ // Extract client roles (replace "my-resource-server" with your client ID)
+ /*Map resourceAccess = jwt.getClaim("resource_access");
+ if (resourceAccess != null) {
+ Map clientRoles = (Map) resourceAccess.get("my-resource-server");
+ if (clientRoles != null && clientRoles.containsKey("roles")) {
+ List roles = (List) clientRoles.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+ }*/
+ Map resourceAccess = jwt.getClaim("resource_access");
+ if (resourceAccess != null) {
+ resourceAccess.forEach((resource, access) -> {
+ if (access instanceof Map) {
+ Map clientRoles = (Map) access;
+ if (clientRoles.containsKey("roles")) {
+ List roles = (List) clientRoles.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+ }
+ });
+ }
+
+ return Mono.just(new JwtAuthenticationToken(jwt, authorities));
+ }
+ };
+ }
+
+ private Mono logRoles(ServerWebExchange exchange) {
+ return exchange.getPrincipal()
+ .cast(JwtAuthenticationToken.class)
+ .doOnNext(jwtAuth -> {
+ Collection extends GrantedAuthority> authorities = jwtAuth.getAuthorities();
+ logger.info("Roles in Resource Server: {}", authorities);
+ })
+ .then();
+ }
+}
\ No newline at end of file
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/OpenApi.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/OpenApi.java
deleted file mode 100644
index 918b0f1..0000000
--- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/OpenApi.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package io.javatab.microservices.composite.course.configuration;
-
-import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
-import io.swagger.v3.oas.annotations.security.OAuthFlow;
-import io.swagger.v3.oas.annotations.security.OAuthFlows;
-import io.swagger.v3.oas.annotations.security.OAuthScope;
-import io.swagger.v3.oas.annotations.security.SecurityScheme;
-import io.swagger.v3.oas.models.ExternalDocumentation;
-import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.info.Contact;
-import io.swagger.v3.oas.models.info.Info;
-import io.swagger.v3.oas.models.info.License;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@SecurityScheme(
- name = "security_auth", type = SecuritySchemeType.OAUTH2,
- flows = @OAuthFlows(
- authorizationCode = @OAuthFlow(
- authorizationUrl = "${springdoc.oAuthFlow.authorizationUrl}",
- tokenUrl = "${springdoc.oAuthFlow.tokenUrl}",
- scopes = {
- @OAuthScope(name = "course:read", description =
- "read scope"),
- @OAuthScope(name = "course:write", description =
- "write scope")
- }
- )))
-@Configuration
-public class OpenApi {
-
- private final OpenApiConfigProperties configuration;
-
- public OpenApi(OpenApiConfigProperties configuration) {
- this.configuration = configuration;
- }
-
- @Bean
- public OpenAPI getOpenApiDocumentation() {
- return new OpenAPI()
- .info(new Info().title(configuration.title())
- .description(configuration.description())
- .version(configuration.version())
- .contact(new Contact()
- .name(configuration.contactName())
- .url(configuration.contactUrl())
- .email(configuration.contactEmail()))
- .termsOfService(configuration.termsOfService())
- .license(new License()
- .name(configuration.license())
- .url(configuration.licenseUrl())))
- .externalDocs(new ExternalDocumentation()
- .description(configuration.externalDocDesc())
- .url(configuration.externalDocUrl()));
- }
-
-}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/OpenApiConfigProperties.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/OpenApiConfigProperties.java
deleted file mode 100644
index 0a78d52..0000000
--- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/OpenApiConfigProperties.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package io.javatab.microservices.composite.course.configuration;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties(prefix = "open-api")
-public record OpenApiConfigProperties(String version,
- String title,
- String description,
- String termsOfService,
- String license,
- String licenseUrl,
- String externalDocDesc,
- String externalDocUrl,
- String contactName,
- String contactUrl,
- String contactEmail) {
-}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/SecurityConfig.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/SecurityConfig.java
deleted file mode 100644
index 9ffdf0f..0000000
--- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/SecurityConfig.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package io.javatab.microservices.composite.course.configuration;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
-import org.springframework.security.config.web.server.ServerHttpSecurity;
-import org.springframework.security.web.server.SecurityWebFilterChain;
-
-import static org.springframework.http.HttpMethod.*;
-
-@Configuration
-@EnableWebFluxSecurity
-public class SecurityConfig {
-
- @Bean
- SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
- http
- .authorizeExchange()
- .pathMatchers("/openapi/**").permitAll()
- .pathMatchers("/webjars/**").permitAll()
- .pathMatchers("/actuator/**").permitAll()
- .pathMatchers(POST, "/course-composite/**").hasAuthority("SCOPE_course:write")
- .pathMatchers(DELETE, "/course-composite/**").hasAuthority("SCOPE_course:write")
- .pathMatchers(GET, "/course-composite/**").hasAuthority("SCOPE_course:read")
- .anyExchange().authenticated()
- .and()
- .oauth2ResourceServer()
- .jwt();
- return http.build();
- }
-}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/WebClients.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/WebClients.java
deleted file mode 100644
index ef0b6d5..0000000
--- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/configuration/WebClients.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.javatab.microservices.composite.course.configuration;
-
-import io.netty.resolver.DefaultAddressResolverGroup;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.cloud.client.loadbalancer.LoadBalanced;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.client.reactive.ReactorClientHttpConnector;
-import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
-import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
-import reactor.netty.http.client.HttpClient;
-
-@Configuration
-public class WebClients {
-
- private static final Logger logger = LoggerFactory.getLogger(WebClients.class);
-
- @Bean
- @LoadBalanced
- public WebClient.Builder loadBalancedWebClientBuilder() {
- //HttpClient httpClient = HttpClient.create().resolver(DefaultAddressResolverGroup.INSTANCE);
- return WebClient.builder();
- //.filter(logRequest())
- //.clientConnector(new ReactorClientHttpConnector(httpClient))
- //.build();
- }
-
-
- private static ExchangeFilterFunction logRequest() {
- return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
- logger.info("Request: {} {} {}", clientRequest.method(), clientRequest.url(), clientRequest.body());
- clientRequest.headers().forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
- return Mono.just(clientRequest);
- });
- }
-
-}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/service/CourseCompositeIntegration.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/service/CourseCompositeIntegration.java
deleted file mode 100644
index cc0e177..0000000
--- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/service/CourseCompositeIntegration.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package io.javatab.microservices.composite.course.service;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.javatab.microservices.api.core.course.Course;
-import io.javatab.microservices.api.core.course.CourseService;
-import io.javatab.microservices.api.core.student.Student;
-import io.javatab.microservices.api.core.student.StudentService;
-import io.javatab.microservices.api.core.vote.Vote;
-import io.javatab.microservices.api.core.vote.VoteService;
-import io.javatab.microservices.api.exceptions.InvalidInputException;
-import io.javatab.microservices.api.exceptions.NotFoundException;
-import io.javatab.microservices.util.http.HttpErrorInfo;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatusCode;
-import org.springframework.stereotype.Component;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.reactive.function.client.WebClientResponseException;
-import reactor.core.publisher.Mono;
-
-import java.io.IOException;
-
-import static java.util.logging.Level.FINE;
-import static org.springframework.http.HttpStatus.NOT_FOUND;
-import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
-
-@Component
-public class CourseCompositeIntegration implements CourseService, StudentService, VoteService {
-
- private static final Logger LOG = LoggerFactory.getLogger(CourseCompositeIntegration.class);
-
- private WebClient webClient;
- private final ObjectMapper objectMapper;
-
- private static final String COURSE_SERVICE_URL = "http://course";
- private static final String STUDENT_SERVICE_URL = "http://student";
- private static final String VOTE_SERVICE_URL = "http://vote";
-
- public CourseCompositeIntegration(
- WebClient.Builder webClient,
- ObjectMapper objectMapper) {
- this.webClient = webClient.build();
- this.objectMapper = objectMapper;
- }
-
- private String getErrorMessage(WebClientResponseException ex) {
- try {
- return objectMapper.readValue(ex.getResponseBodyAsString(), HttpErrorInfo.class).getMessage();
- } catch (IOException ioex) {
- return ex.getMessage();
- }
- }
-
- private Throwable handleException(Throwable ex) {
-
- if (!(ex instanceof WebClientResponseException webClientResponseException)) {
- LOG.warn("Got a unexpected error: {}, will rethrow it", ex.toString());
- return ex;
- }
-
- HttpStatusCode statusCode = webClientResponseException.getStatusCode();
- if (NOT_FOUND.equals(statusCode)) {
- return new NotFoundException(getErrorMessage(webClientResponseException));
- } else if (UNPROCESSABLE_ENTITY.equals(statusCode)) {
- return new InvalidInputException(getErrorMessage(webClientResponseException));
- }
- LOG.warn("Got an unexpected HTTP error: {}, will rethrow it", webClientResponseException.getStatusCode());
- LOG.warn("Error body: {}", webClientResponseException.getResponseBodyAsString());
- return ex;
- }
- @Override
- public Mono getCourse(int courseId) {
- LOG.info("Fetching courses in Integration layer");
- var url = COURSE_SERVICE_URL + "/course/" + courseId;
- return webClient.get().uri(url).retrieve().bodyToMono(Course.class).map(course -> {
- System.out.println("Course" + course);
- return new Course(1, "d", "f", "r", 2);
- }).log(LOG.getName(), FINE).onErrorMap(WebClientResponseException.class, this::handleException);
- }
-
- @Override
- public Mono getStudent(String studentId) {
- LOG.info("Fetching students in Integration layer");
- var url = STUDENT_SERVICE_URL + "/student/" + studentId;
- return webClient.get().uri(url).retrieve().bodyToMono(Student.class).log(LOG.getName(), FINE).onErrorMap(WebClientResponseException.class, this::handleException);
- }
-
- @Override
- public Mono getVote(int courseId) {
- LOG.info("Fetching votes in Integration layer");
- var url = VOTE_SERVICE_URL + "/vote/" + courseId;
- return webClient.get().uri(url).retrieve().bodyToMono(Vote.class).log(LOG.getName(), FINE).onErrorMap(WebClientResponseException.class, this::handleException);
- }
-}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/service/CourseCompositeServiceImpl.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/service/CourseCompositeServiceImpl.java
deleted file mode 100644
index db5ed62..0000000
--- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/service/CourseCompositeServiceImpl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package io.javatab.microservices.composite.course.service;
-
-import io.javatab.microservices.api.composite.course.CourseAggregate;
-import io.javatab.microservices.api.composite.course.CourseCompositeService;
-import io.javatab.microservices.api.core.student.Student;
-import io.javatab.microservices.util.http.ServiceUtil;
-import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Mono;
-
-import java.util.List;
-
-@RestController
-public class CourseCompositeServiceImpl implements CourseCompositeService {
-
- private final CourseCompositeIntegration integration;
- private final ServiceUtil serviceUtil;
-
- public CourseCompositeServiceImpl(final CourseCompositeIntegration integration, ServiceUtil serviceUtil) {
- this.integration = integration;
- this.serviceUtil = serviceUtil;
- }
-
- @Override
- public Mono createProduct(CourseAggregate body) {
- return Mono.just("CREATED");
- }
-
- @Override
- public Mono getCourse(int courseId) {
- // Call integration apis
- integration.getCourse(1).subscribe(course -> System.out.println("Course " + course));
- integration.getStudent("name").subscribe(course -> System.out.println("student " + course));
- integration.getVote(1).subscribe(course -> System.out.println("vote " + course));
- return Mono.just(new CourseAggregate(1, 1, 3, 3,
- List.of(new Student(1, "Student Name", "email", "password"))));
- }
-
- @Override
- public Mono deleteCourse(int courseId) {
- return Mono.just("DELETED");
- }
-}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Course.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Course.java
new file mode 100644
index 0000000..db854b7
--- /dev/null
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Course.java
@@ -0,0 +1,12 @@
+package io.javatab.microservices.composite.course.web;
+
+import lombok.Data;
+
+@Data
+public class Course {
+ private Long id;
+ private String title;
+ private String author;
+ private Double price;
+ private String publisher;
+}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseAggregate.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseAggregate.java
new file mode 100644
index 0000000..f6a128d
--- /dev/null
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseAggregate.java
@@ -0,0 +1,14 @@
+package io.javatab.microservices.composite.course.web;
+
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Builder
+public class CourseAggregate {
+ private Course course;
+ private List reviews;
+}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseAggregateController.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseAggregateController.java
new file mode 100644
index 0000000..6f1c38b
--- /dev/null
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseAggregateController.java
@@ -0,0 +1,35 @@
+package io.javatab.microservices.composite.course.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/course-aggregate")
+public class CourseAggregateController {
+
+ private static final Logger logger = LoggerFactory.getLogger(CourseAggregateController.class);
+
+
+ private final CourseCompositeIntegration integration;
+ //private final NetworkUtility utility;
+
+ public CourseAggregateController(CourseCompositeIntegration integration) {
+ this.integration = integration;
+ }
+
+ @GetMapping("/{id}/with-details")
+ public Mono getCourses(@PathVariable Long id, @AuthenticationPrincipal Jwt jwt) {
+ logger.info("Fetching course and review details for course id : {}", id);
+ return integration.getCourseDetails(id, jwt);
+ }
+}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseCompositeIntegration.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseCompositeIntegration.java
new file mode 100644
index 0000000..1a7d763
--- /dev/null
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/CourseCompositeIntegration.java
@@ -0,0 +1,59 @@
+package io.javatab.microservices.composite.course.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+@Service
+public class CourseCompositeIntegration {
+
+ private static final Logger logger = LoggerFactory.getLogger(CourseCompositeIntegration.class);
+
+ private final String courseServiceUrl;
+ private final String reviewServiceUrl;
+ private final WebClient webClient;
+
+ public CourseCompositeIntegration(
+ @Value("${app.course-service.uri}") String courseServiceUrl,
+ @Value("${app.review-service.uri}") String reviewServiceUrl,
+ WebClient.Builder webClient
+ ) {
+ this.webClient = webClient.build();
+ this.courseServiceUrl = courseServiceUrl;
+ this.reviewServiceUrl = reviewServiceUrl;
+ }
+
+ public Mono getCourseDetails(Long id, Jwt jwt) {
+ logger.info("JWT ===> {}", jwt.getTokenValue());
+ String courseUrl = courseServiceUrl + "/api/courses/" + id;
+ String reviewUrl = reviewServiceUrl + "/api/reviews?course=" + id;
+ logger.info("Course URL ===> {}", courseUrl);
+ logger.info("Review URL ===> {}", reviewUrl);
+ Mono courseMono = webClient.get()
+ .uri(courseUrl)
+ .header("Authorization", "Bearer " + jwt.getTokenValue())
+ .retrieve()
+ .bodyToMono(Course.class);
+
+ Mono> reviewsMono = webClient.get()
+ .uri(reviewUrl)
+ .header("Authorization", "Bearer " + jwt.getTokenValue())
+ .retrieve()
+ .bodyToFlux(Review.class)
+ .collectList();
+
+ return Mono.zip(courseMono, reviewsMono)
+ .map(tuple -> CourseAggregate
+ .builder()
+ .course(tuple.getT1())
+ .reviews(tuple.getT2())
+ .build());
+ }
+}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Review.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Review.java
new file mode 100644
index 0000000..8b21b30
--- /dev/null
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Review.java
@@ -0,0 +1,16 @@
+package io.javatab.microservices.composite.course.web;
+
+import lombok.Data;
+import lombok.Builder;
+
+@Data
+@Builder
+public class Review {
+
+ private String id;
+ private int courseId;
+ private String author;
+ private String content;
+ private String email;
+
+}
diff --git a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Vote.java b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Vote.java
new file mode 100644
index 0000000..2cdf572
--- /dev/null
+++ b/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/web/Vote.java
@@ -0,0 +1,13 @@
+package io.javatab.microservices.composite.course.web;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Data
+@Builder
+public class Vote {
+ private AtomicInteger likes;
+ private AtomicInteger dislikes;
+}
diff --git a/microservices/course-composite-service/src/main/resources/application.yml b/microservices/course-composite-service/src/main/resources/application.yml
index e82e6c9..480365f 100644
--- a/microservices/course-composite-service/src/main/resources/application.yml
+++ b/microservices/course-composite-service/src/main/resources/application.yml
@@ -1,19 +1,45 @@
-spring.config.import: "configserver:"
+app:
+ jwk-set-uri: ${APP_KEYCLOAK_JWK_SET_URI:http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs}
+ course-service:
+ uri: ${COURSE_SERVICE_URL:http://localhost:9001}
+ review-service:
+ uri: ${REVIEW_SERVICE_URL:http://localhost:9002}
spring:
- application.name: course-composite
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
+ application:
+ name: course-composite-service
+server:
+ port: 5000
+logging:
+ level:
+ root: INFO
+ io.javatab.microservices.composite.course: DEBUG
+
+# Security related properties
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: ${KEYCLOAK_ISSUER_URI:http://localhost:8081/realms/course-management-realm}
+ jwk-set-uri: ${KEYCLOAK_JWK_SET_URI:http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs}
---
-spring.config.activate.on-profile: docker
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
+spring:
+ config:
+ activate:
+ on-profile: docker
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: http://keycloak:8080/realms/course-management-realm
+ jwk-set-uri: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+server:
+ port: 8080
+app:
+ jwk-set-uri: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+ course-service:
+ uri: http://course:8080
+ review-service:
+ uri: http://review:8080
\ No newline at end of file
diff --git a/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplicationTests.java b/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplicationTests.java
index 67f9319..975f73b 100644
--- a/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplicationTests.java
+++ b/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplicationTests.java
@@ -1,17 +1,13 @@
package io.javatab.microservices.composite.course;
+import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(
- webEnvironment = RANDOM_PORT,
- classes = {TestSecurityConfig.class},
- properties = {
- "spring.security.oauth2.resourceserver.jwt.issuer-uri=",
- "spring.main.allow-bean-definition-overriding=true",
- "eureka.client.enabled=false",
- "spring.cloud.config.enabled=false"})
+@SpringBootTest
class CourseCompositeServiceApplicationTests {
+ @Test
+ void contextLoads() {
+ }
+
}
diff --git a/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/TestSecurityConfig.java b/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/TestSecurityConfig.java
deleted file mode 100644
index 6b2fe44..0000000
--- a/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/TestSecurityConfig.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package io.javatab.microservices.composite.course;
-
-import org.springframework.boot.test.context.TestConfiguration;
-import org.springframework.context.annotation.Bean;
-import org.springframework.security.config.web.server.ServerHttpSecurity;
-import org.springframework.security.web.server.SecurityWebFilterChain;
-
-@TestConfiguration
-public class TestSecurityConfig {
-
- @Bean
- public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
- http.csrf().disable().authorizeExchange().anyExchange().permitAll();
- return http.build();
- }
-}
diff --git a/microservices/course-service/.gitattributes b/microservices/course-service/.gitattributes
new file mode 100644
index 0000000..3b41682
--- /dev/null
+++ b/microservices/course-service/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/microservices/course-service/.mvn/wrapper/maven-wrapper.properties b/microservices/course-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..d58dfb7
--- /dev/null
+++ b/microservices/course-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/microservices/course-service/Dockerfile b/microservices/course-service/Dockerfile
index f2dbab8..9316c78 100644
--- a/microservices/course-service/Dockerfile
+++ b/microservices/course-service/Dockerfile
@@ -1,22 +1,25 @@
-# stage 1
-# Start with a base image containing Java runtime
-# TODO :: Upgrade to slim jre version for 17 once available
-FROM eclipse-temurin:17-jre-alpine AS builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
+FROM eclipse-temurin:17.0.5_8-jre-focal as builder
+WORKDIR extracted
+
+ADD ./target/*.jar app.jar
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
+RUN java -Djarmode=layertools -jar app.jar extract
+
+FROM eclipse-temurin:17.0.5_8-jre-focal
WORKDIR application
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
+# Copy the dependencies layer from the builder stage
+COPY --from=builder extracted/dependencies/ ./
+# Copy the Spring Boot loader layer from the builder stage
+COPY --from=builder extracted/spring-boot-loader/ ./
+# Copy the snapshot dependencies layer from the builder stage
+COPY --from=builder extracted/snapshot-dependencies/ ./
+# Copy the application layer from the builder stage
+COPY --from=builder extracted/application/ ./
+# Expose port 8080
EXPOSE 8080
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
+# Set the entry point to launch the application
+ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
+
diff --git a/microservices/course-service/kubernetes/deployment.yml b/microservices/course-service/kubernetes/deployment.yml
new file mode 100644
index 0000000..929459d
--- /dev/null
+++ b/microservices/course-service/kubernetes/deployment.yml
@@ -0,0 +1,35 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: course-service
+ labels:
+ app: course-service
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: course-service
+ template:
+ metadata:
+ labels:
+ app: course-service
+ spec:
+ containers:
+ - name: course-service
+ image: course-service
+ imagePullPolicy: IfNotPresent
+ lifecycle:
+ preStop:
+ exec:
+ command: [ "sh", "-c", "sleep 5" ]
+ ports:
+ - containerPort: 9001
+ env:
+ - name: SPRING_DATASOURCE_URL
+ value: jdbc:postgresql://course-postgres/course_db
+ - name: APP_KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+ - name: KEYCLOAK_ISSUER_URI
+ value: http://keycloak:8080/realms/course-management-realm
+ - name: KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
\ No newline at end of file
diff --git a/microservices/course-service/kubernetes/service.yml b/microservices/course-service/kubernetes/service.yml
new file mode 100644
index 0000000..0363c33
--- /dev/null
+++ b/microservices/course-service/kubernetes/service.yml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: course-service
+ labels:
+ app: course-service
+spec:
+ type: ClusterIP
+ selector:
+ app: course-service
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 9001
\ No newline at end of file
diff --git a/microservices/course-service/mvnw b/microservices/course-service/mvnw
new file mode 100755
index 0000000..19529dd
--- /dev/null
+++ b/microservices/course-service/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/microservices/course-service/mvnw.cmd b/microservices/course-service/mvnw.cmd
new file mode 100644
index 0000000..249bdf3
--- /dev/null
+++ b/microservices/course-service/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/microservices/course-service/pom.xml b/microservices/course-service/pom.xml
index a7a2c82..2ec6903 100644
--- a/microservices/course-service/pom.xml
+++ b/microservices/course-service/pom.xml
@@ -13,10 +13,21 @@
1.0.0
course-service
Demo project for Spring Boot
+
+
+
+
+
+
+
+
+
+
+
+
+
17
- 1.16.2
- 2024.0.0
@@ -27,38 +38,51 @@
org.springframework.boot
spring-boot-starter-webflux
+
org.springframework.boot
- spring-boot-starter-data-mongodb
+ spring-boot-starter-data-jpa
+
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-client
+ org.postgresql
+ postgresql
+ runtime
+
- org.mapstruct
- mapstruct
- 1.3.1.Final
+ org.flywaydb
+ flyway-database-postgresql
+
+
- org.springframework.cloud
- spring-cloud-starter-config
+ org.springframework.boot
+ spring-boot-starter-validation
+
- org.springframework.retry
- spring-retry
+ org.springframework.boot
+ spring-boot-starter-security
-
+
- io.javatab.microservices.api
- api
- 1.0.0
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
- io.javatab.microservices.util
+ io.javatab.util
util
1.0.0
+ compile
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
org.springframework.boot
spring-boot-starter-test
@@ -69,47 +93,13 @@
reactor-test
test
-
- org.testcontainers
- mongodb
- test
-
-
- org.testcontainers
- junit-jupiter
- test
-
-
-
-
- org.testcontainers
- testcontainers-bom
- ${testcontainers.version}
- pom
- import
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
org.springframework.boot
spring-boot-maven-plugin
-
-
- true
-
-
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/CourseServiceApplication.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/CourseServiceApplication.java
index 3421539..d4b0593 100644
--- a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/CourseServiceApplication.java
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/CourseServiceApplication.java
@@ -1,18 +1,25 @@
package io.javatab.microservices.core.course;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
+
@SpringBootApplication
-@ComponentScan("io.javatab") // To enable Spring Boot's autoconfiguration feature to detect Spring Beans in the api and util projects, we also need to add a @ComponentScan annotation to the main application class, which includes the packages of the api and util projects:
+@ComponentScan({"io.javatab"})
public class CourseServiceApplication {
+ private static final Logger LOG = LoggerFactory.getLogger(CourseServiceApplication.class);
+
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(CourseServiceApplication.class, args);
- String mongoUri = ctx.getEnvironment().getProperty("spring.data.mongodb.host");
- System.out.println("Connected to Mongo: " + mongoUri);
+
+ String postgresUri = ctx.getEnvironment().getProperty("spring.datasource.url");
+ LOG.info("Connected to Postgres: " + postgresUri);
}
}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/config/DatabaseConfig.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/config/DatabaseConfig.java
new file mode 100644
index 0000000..579d6a3
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/config/DatabaseConfig.java
@@ -0,0 +1,9 @@
+package io.javatab.microservices.core.course.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+@Configuration
+@EnableJpaAuditing
+public class DatabaseConfig {
+}
\ No newline at end of file
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/config/SecurityConfig.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/config/SecurityConfig.java
new file mode 100644
index 0000000..66f2a28
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/config/SecurityConfig.java
@@ -0,0 +1,107 @@
+package io.javatab.microservices.core.course.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+@Configuration
+@EnableWebFluxSecurity
+public class SecurityConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
+
+ private String jwkSetUri;
+
+ public SecurityConfig(@Value("${app.jwk-set-uri}") String jwkSetUri) {
+ this.jwkSetUri = jwkSetUri;
+ }
+
+ @Bean
+ public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
+ http
+ .authorizeExchange(exchanges -> exchanges
+ .pathMatchers("/api/courses/**").hasAnyRole("COURSE-READ", "COURSE-WRITE")
+ .anyExchange().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))
+ );
+
+ // Add filter to log roles
+ http.addFilterAt((exchange, chain) -> logRoles(exchange).then(chain.filter(exchange)),
+ SecurityWebFiltersOrder.AUTHORIZATION);
+
+ return http.build();
+ }
+
+ @Bean
+ public ReactiveJwtDecoder jwtDecoder() {
+ return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
+ }
+
+ @Bean
+ public Converter> grantedAuthoritiesExtractor() {
+ return new Converter>() {
+ @Override
+ public Mono convert(Jwt jwt) {
+ Collection authorities = new ArrayList<>();
+
+ // Extract realm roles
+ Map realmAccess = jwt.getClaim("realm_access");
+ if (realmAccess != null && realmAccess.containsKey("roles")) {
+ List roles = (List) realmAccess.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+
+ Map resourceAccess = jwt.getClaim("resource_access");
+ if (resourceAccess != null) {
+ resourceAccess.forEach((resource, access) -> {
+ if (access instanceof Map) {
+ Map clientRoles = (Map) access;
+ if (clientRoles.containsKey("roles")) {
+ List roles = (List) clientRoles.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+ }
+ });
+ }
+
+ return Mono.just(new JwtAuthenticationToken(jwt, authorities));
+ }
+ };
+ }
+
+ private Mono logRoles(ServerWebExchange exchange) {
+ return exchange.getPrincipal()
+ .cast(JwtAuthenticationToken.class)
+ .doOnNext(jwtAuth -> {
+ Collection extends GrantedAuthority> authorities = jwtAuth.getAuthorities();
+ logger.info("Roles in Resource Server: {}", authorities);
+ })
+ .then();
+ }
+}
\ No newline at end of file
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/Course.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/Course.java
new file mode 100644
index 0000000..bda4dbb
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/Course.java
@@ -0,0 +1,134 @@
+package io.javatab.microservices.core.course.domain;
+
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+
+import java.time.Instant;
+import java.util.Objects;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+@Entity
+@EntityListeners(AuditingEntityListener.class)
+public class Course {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotBlank(message = "The book title must be defined.")
+ private String title;
+
+ @NotBlank(message = "The book author must be defined.")
+ private String author;
+
+ @NotNull(message = "The book price must be defined.")
+ @Positive(message = "The book price must be greater than zero.")
+ private Double price;
+
+ private String publisher;
+
+ @CreatedDate
+ private Instant createdDate;
+
+ @LastModifiedDate
+ private Instant lastModifiedDate;
+
+ @Version
+ private int version;
+
+ public Course() {}
+
+ public Course(Long id, String title, String author, Double price, String publisher, Instant createdDate, Instant lastModifiedDate, int version) {
+ this.id = id;
+ this.title = title;
+ this.author = author;
+ this.price = price;
+ this.publisher = publisher;
+ this.createdDate = createdDate;
+ this.lastModifiedDate = lastModifiedDate;
+ this.version = version;
+ }
+
+ public static Course of(String title, String author, Double price, String publisher) {
+ return new Course(null, title, author, price, publisher, null, null, 0);
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public Double getPrice() {
+ return price;
+ }
+
+ public void setPrice(Double price) {
+ this.price = price;
+ }
+
+ public String getPublisher() {
+ return publisher;
+ }
+
+ public void setPublisher(String publisher) {
+ this.publisher = publisher;
+ }
+
+ public Instant getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(Instant createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public Instant getLastModifiedDate() {
+ return lastModifiedDate;
+ }
+
+ public void setLastModifiedDate(Instant lastModifiedDate) {
+ this.lastModifiedDate = lastModifiedDate;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ Course course = (Course) o;
+ return getVersion() == course.getVersion() && Objects.equals(getId(), course.getId()) && Objects.equals(getTitle(), course.getTitle()) && Objects.equals(getAuthor(), course.getAuthor()) && Objects.equals(getPrice(), course.getPrice()) && Objects.equals(getPublisher(), course.getPublisher()) && Objects.equals(getCreatedDate(), course.getCreatedDate()) && Objects.equals(getLastModifiedDate(), course.getLastModifiedDate());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getId(), getTitle(), getAuthor(), getPrice(), getPublisher(), getCreatedDate(), getLastModifiedDate(), getVersion());
+ }
+}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseAlreadyExitsException.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseAlreadyExitsException.java
new file mode 100644
index 0000000..747c7a2
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseAlreadyExitsException.java
@@ -0,0 +1,7 @@
+package io.javatab.microservices.core.course.domain;
+
+public class CourseAlreadyExitsException extends RuntimeException {
+ public CourseAlreadyExitsException(String title) {
+ super("A course with title " + title + " already exists.");
+ }
+}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseNotFoundException.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseNotFoundException.java
new file mode 100644
index 0000000..43b526d
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseNotFoundException.java
@@ -0,0 +1,7 @@
+package io.javatab.microservices.core.course.domain;
+
+public class CourseNotFoundException extends RuntimeException {
+ public CourseNotFoundException(String title) {
+ super("The course with title " + title + " was not found.");
+ }
+}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseRepository.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseRepository.java
new file mode 100644
index 0000000..86be6f5
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseRepository.java
@@ -0,0 +1,18 @@
+package io.javatab.microservices.core.course.domain;
+
+import jakarta.transaction.Transactional;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+
+public interface CourseRepository extends CrudRepository {
+
+ Optional findByTitle(String title);
+ Optional findById(Long id);
+ boolean existsByTitle(String title);
+
+
+ @Transactional
+ void deleteById(Long id);
+
+}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseService.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseService.java
new file mode 100644
index 0000000..23d32a1
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/domain/CourseService.java
@@ -0,0 +1,50 @@
+package io.javatab.microservices.core.course.domain;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class CourseService {
+
+ private final CourseRepository courseRepository;
+
+ public CourseService(CourseRepository courseRepository) {
+ this.courseRepository = courseRepository;
+ }
+
+ public Iterable viewCourses() {
+ return courseRepository.findAll();
+ }
+
+ public Course viewCourseDetails(String title) {
+ return courseRepository.findByTitle(title)
+ .orElseThrow(() -> new CourseNotFoundException(title));
+ }
+
+ public Course viewCourseDetailsById(Long id) {
+ return courseRepository.findById(id)
+ .orElseThrow(() -> new CourseNotFoundException(String.valueOf(id)));
+ }
+
+ public Course addCourse(Course course) {
+ if (courseRepository.existsByTitle(course.getTitle())) {
+ throw new CourseAlreadyExitsException(course.getTitle());
+ }
+ return courseRepository.save(course);
+ }
+
+ public void removeCourse(Long id) {
+ courseRepository.deleteById(id);
+ }
+
+ public Course editCourseDetails(Long id, Course course) {
+ return courseRepository.findById(id)
+ .map(existingCourse -> {
+ existingCourse.setTitle(course.getTitle());
+ existingCourse.setAuthor(course.getAuthor());
+ existingCourse.setPrice(course.getPrice());
+ existingCourse.setPublisher(course.getPublisher());
+ return courseRepository.save(existingCourse);
+ }).orElseGet(() -> addCourse(course));
+
+ }
+}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/persistence/CourseEntity.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/persistence/CourseEntity.java
deleted file mode 100644
index 3a2c5d0..0000000
--- a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/persistence/CourseEntity.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package io.javatab.microservices.core.course.persistence;
-
-import org.apache.commons.lang3.builder.EqualsBuilder;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
-import org.springframework.data.mongodb.core.index.Indexed;
-import org.springframework.data.mongodb.core.mapping.Document;
-import org.springframework.data.annotation.Id;
-import org.springframework.data.annotation.Version;
-
-@Document(collection = "courses")
-public class CourseEntity {
-
- @Id
- private String id;
-
- @Version
- private Integer version;
-
- @Indexed(unique = true)
- private int courseId;
-
- private String courseName;
-
- private String author;
-
- private int voteId;
-
- private String content;
-
- public CourseEntity() {
- }
-
- public CourseEntity(int courseId, String courseName, String author, int voteId, String content) {
- this.courseId = courseId;
- this.courseName = courseName;
- this.author = author;
- this.voteId = voteId;
- this.content = content;
- }
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public Integer getVersion() {
- return version;
- }
-
- public void setVersion(Integer version) {
- this.version = version;
- }
-
- public int getCourseId() {
- return courseId;
- }
-
- public void setCourseId(int courseId) {
- this.courseId = courseId;
- }
-
- public String getCourseName() {
- return courseName;
- }
-
- public void setCourseName(String courseName) {
- this.courseName = courseName;
- }
-
- public String getAuthor() {
- return author;
- }
-
- public void setAuthor(String author) {
- this.author = author;
- }
-
- public int getVoteId() {
- return voteId;
- }
-
- public void setVoteId(int voteId) {
- this.voteId = voteId;
- }
-
- public String getContent() {
- return content;
- }
-
- public void setContent(String content) {
- this.content = content;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
-
- if (o == null || getClass() != o.getClass()) return false;
-
- CourseEntity entity = (CourseEntity) o;
-
- return new EqualsBuilder().append(courseId, entity.courseId).append(voteId, entity.voteId).append(id, entity.id).append(version, entity.version).append(courseName, entity.courseName).append(author, entity.author).append(content, entity.content).isEquals();
- }
-
- @Override
- public int hashCode() {
- return new HashCodeBuilder(17, 37).append(id).append(version).append(courseId).append(courseName).append(author).append(voteId).append(content).toHashCode();
- }
-}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/persistence/CourseRepository.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/persistence/CourseRepository.java
deleted file mode 100644
index 3194201..0000000
--- a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/persistence/CourseRepository.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.javatab.microservices.core.course.persistence;
-
-import org.springframework.data.repository.CrudRepository;
-
-public interface CourseRepository extends CrudRepository {
-}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/service/CourseServiceImpl.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/service/CourseServiceImpl.java
deleted file mode 100644
index 3fb4424..0000000
--- a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/service/CourseServiceImpl.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package io.javatab.microservices.core.course.service;
-
-import io.javatab.microservices.api.core.course.Course;
-import io.javatab.microservices.api.core.course.CourseService;
-import io.javatab.microservices.util.http.ServiceUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Mono;
-
-@RestController
-public class CourseServiceImpl implements CourseService {
-
- private static final Logger LOG = LoggerFactory.getLogger(CourseServiceImpl.class);
-
- private final ServiceUtil serviceUtil;
-
- public CourseServiceImpl(final ServiceUtil serviceUtil) {
- this.serviceUtil = serviceUtil;
- }
-
- @Override
- public Mono getCourse(int courseId) {
- LOG.info("In course service");
- return Mono.just(new Course(123, "Test", "Test", "Test content", 1));
- }
-}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/web/CourseController.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/web/CourseController.java
new file mode 100644
index 0000000..9c979b6
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/web/CourseController.java
@@ -0,0 +1,67 @@
+package io.javatab.microservices.core.course.web;
+
+import io.javatab.microservices.core.course.domain.Course;
+import io.javatab.microservices.core.course.domain.CourseService;
+import io.javatab.util.http.NetworkUtility;
+import jakarta.validation.Valid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+@RestController()
+@RequestMapping("/api/courses")
+public class CourseController {
+
+ private final Logger logger = LoggerFactory.getLogger(CourseController.class);
+
+ private final NetworkUtility utility;
+ private CourseService courseService;
+
+ public CourseController(NetworkUtility utility, CourseService courseService) {
+ this.utility = utility;
+ this.courseService = courseService;
+ }
+
+ @GetMapping
+ public Iterable get() {
+ logger.info("Fetching courses");
+ return courseService.viewCourses();
+ }
+
+ /*
+ * Make sure application is running in localhost mode to test and not in docker
+ * http GET ':9001/api/courses/Microservices with Spring Boot'
+ * */
+ @GetMapping("/title/{title}")
+ public Course getByTitle(@PathVariable String title) {
+ return courseService.viewCourseDetails(title);
+ }
+
+ @GetMapping("/{id}")
+ public Course getById(@PathVariable Long id) {
+ return courseService.viewCourseDetailsById(id);
+ }
+
+ /*
+ * http POST :9001/api/courses title="Microservices with Spring Boot" author="John Doe" price:=29.79 publisher="GitHub"
+ * http POST :9001/api/courses title="Spring Boot in Action" author="John Doe" price:=69.45 publisher="GitHub"
+ * */
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public Course post(@Valid @RequestBody Course course) {
+ return courseService.addCourse(course);
+ }
+
+ @DeleteMapping("/{id}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void delete(@PathVariable Long id) {
+ courseService.removeCourse(id);
+ }
+
+ @PutMapping("/{id}")
+ public Course put(@PathVariable Long id, @Valid @RequestBody Course course) {
+ return courseService.editCourseDetails(id, course);
+ }
+
+}
diff --git a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/web/CourseControllerAdvice.java b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/web/CourseControllerAdvice.java
new file mode 100644
index 0000000..8e2b275
--- /dev/null
+++ b/microservices/course-service/src/main/java/io/javatab/microservices/core/course/web/CourseControllerAdvice.java
@@ -0,0 +1,42 @@
+package io.javatab.microservices.core.course.web;
+
+import io.javatab.microservices.core.course.domain.CourseAlreadyExitsException;
+import io.javatab.microservices.core.course.domain.CourseNotFoundException;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestControllerAdvice
+public class CourseControllerAdvice {
+
+ @ExceptionHandler(CourseNotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ String courseNotFoundHandler(CourseNotFoundException ex) {
+ return ex.getMessage();
+ }
+
+ @ExceptionHandler(CourseAlreadyExitsException.class)
+ @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+ String courseAlreadyExistsHandler(CourseAlreadyExitsException ex) {
+ return ex.getMessage();
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Map handleValidationExceptions(MethodArgumentNotValidException ex) {
+ Map errorsMap = new HashMap<>();
+ ex.getBindingResult().getAllErrors().forEach(error -> {
+ String fieldName = ((FieldError) error).getField();
+ String errorMessage = error.getDefaultMessage();
+ errorsMap.put(fieldName, errorMessage);
+ });
+ return errorsMap;
+ }
+
+}
\ No newline at end of file
diff --git a/microservices/course-service/src/main/resources/application.yml b/microservices/course-service/src/main/resources/application.yml
index 968bed7..334a264 100644
--- a/microservices/course-service/src/main/resources/application.yml
+++ b/microservices/course-service/src/main/resources/application.yml
@@ -1,19 +1,61 @@
-spring.config.import: "configserver:"
-
+app:
+ jwk-set-uri: ${APP_KEYCLOAK_JWK_SET_URI:http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs}
+server:
+ port: 9001
spring:
- application.name: course
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
+ application:
+ name: course-service
+ datasource:
+ driver-class-name: org.postgresql.Driver
+ username: user
+ password: pwd
+ url: jdbc:postgresql://localhost:5432/course_db
+ hikari:
+ connection-timeout: 2000
+ maximum-pool-size: 5 #https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
+ jpa:
+ database-platform: org.hibernate.dialect.PostgreSQLDialect
+ sql:
+ init:
+ mode: always
+ # flyway: # Only in dev env. Never have them in prod
+ # validate-on-migrate: false
+ # outOfOrder: true
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: ${KEYCLOAK_ISSUER_URI:http://localhost:8081/realms/course-management-realm}
+ jwk-set-uri: ${KEYCLOAK_JWK_SET_URI:http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs}
+logging:
+ level:
+ org.springframework: INFO
+ com.javatab: DEBUG
+ pattern:
+ console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
+management:
+ endpoints:
+ web:
+ exposure:
+ include: "*"
+ endpoint:
+ health:
+ show-details: always
---
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
+app:
+ jwk-set-uri: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+spring:
+ config:
+ activate:
+ on-profile: docker
+ datasource:
+ url: jdbc:postgresql://postgres:5432/course_db
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: http://keycloak:8080/realms/course-management-realm
+ jwk-set-uri: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+server:
+ port: 8080
\ No newline at end of file
diff --git a/microservices/course-service/src/main/resources/db/migration/V1__Initial_schema.sql b/microservices/course-service/src/main/resources/db/migration/V1__Initial_schema.sql
new file mode 100644
index 0000000..1af0f08
--- /dev/null
+++ b/microservices/course-service/src/main/resources/db/migration/V1__Initial_schema.sql
@@ -0,0 +1,11 @@
+DROP TABLE IF EXISTS course;
+CREATE TABLE course (
+ id BIGSERIAL PRIMARY KEY NOT NULL,
+ author varchar(255) NOT NULL,
+ price float8 NOT NULL,
+ title varchar(255) NOT NULL,
+ publisher varchar(255) NOT NULL,
+ created_date timestamp NOT NULL,
+ last_modified_date timestamp NOT NULL,
+ version integer NOT NULL
+);
\ No newline at end of file
diff --git a/api/src/test/java/io/javatab/microservices/api/ApiApplicationTests.java b/microservices/course-service/src/test/java/io/javatab/microservices/core/course/CourseServiceApplicationTests.java
similarity index 57%
rename from api/src/test/java/io/javatab/microservices/api/ApiApplicationTests.java
rename to microservices/course-service/src/test/java/io/javatab/microservices/core/course/CourseServiceApplicationTests.java
index 5401095..54e3eab 100644
--- a/api/src/test/java/io/javatab/microservices/api/ApiApplicationTests.java
+++ b/microservices/course-service/src/test/java/io/javatab/microservices/core/course/CourseServiceApplicationTests.java
@@ -1,10 +1,10 @@
-package io.javatab.microservices.api;
+package io.javatab.microservices.core.course;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-@SpringBootTest()
-class ApiApplicationTests {
+@SpringBootTest
+class CourseServiceApplicationTests {
@Test
void contextLoads() {
diff --git a/microservices/course-service/src/test/java/io/javatab/microservices/core/course/CourseServiceImplApplicationTests.java b/microservices/course-service/src/test/java/io/javatab/microservices/core/course/CourseServiceImplApplicationTests.java
deleted file mode 100644
index 0c36fd2..0000000
--- a/microservices/course-service/src/test/java/io/javatab/microservices/core/course/CourseServiceImplApplicationTests.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javatab.microservices.core.course;
-
-import org.springframework.boot.test.context.SpringBootTest;
-
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(webEnvironment=RANDOM_PORT, properties = {
- "spring.cloud.config.enabled=false"})
-class CourseServiceImplApplicationTests extends MongoDbTestBase {
-
-}
diff --git a/microservices/course-service/src/test/java/io/javatab/microservices/core/course/MongoDbTestBase.java b/microservices/course-service/src/test/java/io/javatab/microservices/core/course/MongoDbTestBase.java
deleted file mode 100644
index b8bcbaa..0000000
--- a/microservices/course-service/src/test/java/io/javatab/microservices/core/course/MongoDbTestBase.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package io.javatab.microservices.core.course;
-
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.containers.MongoDBContainer;
-
-public abstract class MongoDbTestBase {
- private static MongoDBContainer database = new MongoDBContainer("mongo:4.4.2");
-
- static {
- database.start();
- }
-
- @DynamicPropertySource
- static void setProperties(DynamicPropertyRegistry registry) {
- registry.add("spring.data.mongodb.host", database::getContainerIpAddress);
- registry.add("spring.data.mongodb.port", () -> database.getMappedPort(27017));
- registry.add("spring.data.mongodb.database", () -> "test");
- }
-}
diff --git a/microservices/course-service/src/test/java/io/javatab/microservices/core/course/PersistenceTests.java b/microservices/course-service/src/test/java/io/javatab/microservices/core/course/PersistenceTests.java
deleted file mode 100644
index 5549968..0000000
--- a/microservices/course-service/src/test/java/io/javatab/microservices/core/course/PersistenceTests.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.javatab.microservices.core.course;
-
-
-import io.javatab.microservices.core.course.persistence.CourseEntity;
-import io.javatab.microservices.core.course.persistence.CourseRepository;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-@DataMongoTest(
- properties = {"spring.cloud.config.enabled=false"}
-)
-public class PersistenceTests extends MongoDbTestBase {
-
- @Autowired
- private CourseRepository repository;
- private CourseEntity savedEntity;
-
- @BeforeEach
- void setUp() {
- repository.deleteAll();
- CourseEntity entity = new CourseEntity(1, "Course Name", "Author", 4, "Content");
- savedEntity = repository.save(entity);
- assertEquals(entity, savedEntity);
- }
-
- @Test
- void create() {
- CourseEntity newEntity = new CourseEntity(1, "Course Name", "Author", 4, "Content");
- repository.save(newEntity);
-
- CourseEntity foundEntity = repository.findById(newEntity.getId()).get();
- assertEquals(newEntity.getId(), foundEntity.getId());
- assertEquals(2, repository.count());
- }
-}
diff --git a/microservices/course-service/src/test/resources/application.yml b/microservices/course-service/src/test/resources/application.yml
deleted file mode 100644
index e69de29..0000000
diff --git a/microservices/course-service/src/test/resources/logback-test.xml b/microservices/course-service/src/test/resources/logback-test.xml
deleted file mode 100644
index b6c7deb..0000000
--- a/microservices/course-service/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/microservices/review-service/.gitattributes b/microservices/review-service/.gitattributes
new file mode 100644
index 0000000..3b41682
--- /dev/null
+++ b/microservices/review-service/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/api/.gitignore b/microservices/review-service/.gitignore
similarity index 100%
rename from api/.gitignore
rename to microservices/review-service/.gitignore
diff --git a/microservices/review-service/.mvn/wrapper/maven-wrapper.properties b/microservices/review-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..d58dfb7
--- /dev/null
+++ b/microservices/review-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/microservices/review-service/Dockerfile b/microservices/review-service/Dockerfile
new file mode 100644
index 0000000..9316c78
--- /dev/null
+++ b/microservices/review-service/Dockerfile
@@ -0,0 +1,25 @@
+FROM eclipse-temurin:17.0.5_8-jre-focal as builder
+WORKDIR extracted
+
+ADD ./target/*.jar app.jar
+
+RUN java -Djarmode=layertools -jar app.jar extract
+
+FROM eclipse-temurin:17.0.5_8-jre-focal
+WORKDIR application
+
+# Copy the dependencies layer from the builder stage
+COPY --from=builder extracted/dependencies/ ./
+# Copy the Spring Boot loader layer from the builder stage
+COPY --from=builder extracted/spring-boot-loader/ ./
+# Copy the snapshot dependencies layer from the builder stage
+COPY --from=builder extracted/snapshot-dependencies/ ./
+# Copy the application layer from the builder stage
+COPY --from=builder extracted/application/ ./
+
+# Expose port 8080
+EXPOSE 8080
+
+# Set the entry point to launch the application
+ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
+
diff --git a/microservices/review-service/Tiltfile b/microservices/review-service/Tiltfile
new file mode 100644
index 0000000..50db954
--- /dev/null
+++ b/microservices/review-service/Tiltfile
@@ -0,0 +1,12 @@
+# Build the container image using Docker
+docker_build(
+ ref = "review-service",
+ context = ".",
+ dockerfile = "Dockerfile"
+)
+
+# Deploy Kubernetes resources
+k8s_yaml(["kubernetes/deployment.yml", "kubernetes/service.yml"])
+
+# Manage and enable port forwarding
+k8s_resource("review-service", port_forwards=["9002"])
diff --git a/microservices/review-service/kubernetes/deployment.yml b/microservices/review-service/kubernetes/deployment.yml
new file mode 100644
index 0000000..89bf59e
--- /dev/null
+++ b/microservices/review-service/kubernetes/deployment.yml
@@ -0,0 +1,35 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: review-service
+ labels:
+ app: review-service
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: review-service
+ template:
+ metadata:
+ labels:
+ app: review-service
+ spec:
+ containers:
+ - name: review-service
+ image: review-service
+ imagePullPolicy: IfNotPresent
+ lifecycle:
+ preStop:
+ exec:
+ command: [ "sh", "-c", "sleep 5" ]
+ ports:
+ - containerPort: 9002
+ env:
+ - name: SPRING_DATA_MONGODB_URI
+ value: mongodb://review-mongodb:27017/review-db
+ - name: APP_KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+ - name: KEYCLOAK_ISSUER_URI
+ value: http://keycloak:8080/realms/course-management-realm
+ - name: KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
\ No newline at end of file
diff --git a/microservices/review-service/kubernetes/service.yml b/microservices/review-service/kubernetes/service.yml
new file mode 100644
index 0000000..b1cdbd1
--- /dev/null
+++ b/microservices/review-service/kubernetes/service.yml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: review-service
+ labels:
+ app: review-service
+spec:
+ type: ClusterIP
+ selector:
+ app: review-service
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 9002
\ No newline at end of file
diff --git a/microservices/review-service/mvnw b/microservices/review-service/mvnw
new file mode 100755
index 0000000..19529dd
--- /dev/null
+++ b/microservices/review-service/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/microservices/review-service/mvnw.cmd b/microservices/review-service/mvnw.cmd
new file mode 100644
index 0000000..249bdf3
--- /dev/null
+++ b/microservices/review-service/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/spring-cloud/authorization-server/pom.xml b/microservices/review-service/pom.xml
similarity index 52%
rename from spring-cloud/authorization-server/pom.xml
rename to microservices/review-service/pom.xml
index 97db72d..bd7147c 100644
--- a/spring-cloud/authorization-server/pom.xml
+++ b/microservices/review-service/pom.xml
@@ -8,41 +8,68 @@
3.4.3
- io.javatab
- authorization-server
+ io.javatab.microservices.core.review
+ review-service
1.0.0
- authorization-server
+ review-service
Demo project for Spring Boot
+
+
+
+
+
+
+
+
+
+
+
+
+
17
- 2024.0.0
org.springframework.boot
- spring-boot-starter-web
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
- org.springframework.security
- spring-security-oauth2-authorization-server
- 1.1.6
+ org.springframework.boot
+ spring-boot-starter-validation
org.springframework.boot
spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-client
+ org.projectlombok
+ lombok
+ provided
+
- org.springframework.cloud
- spring-cloud-starter-config
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
- org.springframework.retry
- spring-retry
+ io.javatab.util
+ util
+ 1.0.0
+ compile
+
org.springframework.boot
spring-boot-starter-actuator
@@ -54,37 +81,17 @@
test
- org.springframework.security
- spring-security-test
+ io.projectreactor
+ reactor-test
test
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
-
- repackage
-
-
-
+ org.springframework.boot
+ spring-boot-maven-plugin
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/ReviewServiceApplication.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/ReviewServiceApplication.java
new file mode 100644
index 0000000..2eb3293
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/ReviewServiceApplication.java
@@ -0,0 +1,23 @@
+package io.javatab.microservices.core.review;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan({"io.javatab"})
+public class ReviewServiceApplication {
+
+ private static final Logger logger = LoggerFactory.getLogger(ReviewServiceApplication.class);
+
+ public static void main(String[] args) {
+ ConfigurableApplicationContext ctx = SpringApplication.run(ReviewServiceApplication.class, args);
+
+ String mongoDbUri = ctx.getEnvironment().getProperty("spring.data.mongodb.uri");
+ logger.info("Connected to MongoDb ===> {}", mongoDbUri);
+ }
+
+}
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/config/SecurityConfig.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/config/SecurityConfig.java
new file mode 100644
index 0000000..8ac3ffd
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/config/SecurityConfig.java
@@ -0,0 +1,107 @@
+package io.javatab.microservices.core.review.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+@Configuration
+@EnableWebFluxSecurity
+public class SecurityConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
+
+ private String jwkSetUri;
+
+ public SecurityConfig(@Value("${app.jwk-set-uri}") String jwkSetUri) {
+ this.jwkSetUri = jwkSetUri;
+ }
+
+ @Bean
+ public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
+ http
+ .authorizeExchange(exchanges -> exchanges
+ .pathMatchers("/api/reviews/**").hasAnyRole("REVIEW-READ", "REVIEW-WRITE")
+ .anyExchange().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))
+ );
+
+ // Add filter to log roles
+ http.addFilterAt((exchange, chain) -> logRoles(exchange).then(chain.filter(exchange)),
+ SecurityWebFiltersOrder.AUTHORIZATION);
+
+ return http.build();
+ }
+
+ @Bean
+ public ReactiveJwtDecoder jwtDecoder() {
+ return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
+ }
+
+ @Bean
+ public Converter> grantedAuthoritiesExtractor() {
+ return new Converter>() {
+ @Override
+ public Mono convert(Jwt jwt) {
+ Collection authorities = new ArrayList<>();
+
+ // Extract realm roles
+ Map realmAccess = jwt.getClaim("realm_access");
+ if (realmAccess != null && realmAccess.containsKey("roles")) {
+ List roles = (List) realmAccess.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+
+ Map resourceAccess = jwt.getClaim("resource_access");
+ if (resourceAccess != null) {
+ resourceAccess.forEach((resource, access) -> {
+ if (access instanceof Map) {
+ Map clientRoles = (Map) access;
+ if (clientRoles.containsKey("roles")) {
+ List roles = (List) clientRoles.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+ }
+ });
+ }
+
+ return Mono.just(new JwtAuthenticationToken(jwt, authorities));
+ }
+ };
+ }
+
+ private Mono logRoles(ServerWebExchange exchange) {
+ return exchange.getPrincipal()
+ .cast(JwtAuthenticationToken.class)
+ .doOnNext(jwtAuth -> {
+ Collection extends GrantedAuthority> authorities = jwtAuth.getAuthorities();
+ logger.info("Roles in Resource Server: {}", authorities);
+ })
+ .then();
+ }
+}
\ No newline at end of file
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/Review.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/Review.java
new file mode 100644
index 0000000..9c683ed
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/Review.java
@@ -0,0 +1,29 @@
+package io.javatab.microservices.core.review.domain;
+
+import jakarta.validation.constraints.*;
+import lombok.*;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Version;
+import org.springframework.data.mongodb.core.index.Indexed;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Document(collection = "reviews")
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Review {
+
+ @Id
+ private String id;
+
+ @Version
+ private int version;
+ private int courseId;
+ private String author;
+ private String content;
+ @Indexed()
+ private String email;
+
+}
\ No newline at end of file
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewNotFoundException.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewNotFoundException.java
new file mode 100644
index 0000000..1dd456f
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewNotFoundException.java
@@ -0,0 +1,7 @@
+package io.javatab.microservices.core.review.domain;
+
+public class ReviewNotFoundException extends RuntimeException {
+ public ReviewNotFoundException(String id) {
+ super("The review with id " + id + " was not found.");
+ }
+}
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewRepository.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewRepository.java
new file mode 100644
index 0000000..b46d9bd
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewRepository.java
@@ -0,0 +1,13 @@
+package io.javatab.microservices.core.review.domain;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface ReviewRepository extends MongoRepository {
+ List findByCourseIdAndEmail(Long courseId, String email);
+ List findByEmail(String email);
+ List findByCourseId(Long courseId);
+ Optional findById(String id);
+}
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewService.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewService.java
new file mode 100644
index 0000000..77bc8cb
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/domain/ReviewService.java
@@ -0,0 +1,72 @@
+package io.javatab.microservices.core.review.domain;
+
+import io.javatab.microservices.core.review.web.ReviewDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+public class ReviewService {
+
+ private static final Logger logger = LoggerFactory.getLogger(ReviewService.class);
+ private final ReviewRepository reviewRepository;
+
+ public ReviewService(ReviewRepository reviewRepository) {
+ this.reviewRepository = reviewRepository;
+ }
+
+ @Transactional
+ public Review addReview(ReviewDTO reviewDto) {
+ logger.info("Adding new review with email: {}", reviewDto.getEmail());
+ Review aReview = Review.builder()
+ .courseId(reviewDto.getCourseId())
+ .author(reviewDto.getAuthor())
+ .content(reviewDto.getContent())
+ .email(reviewDto.getEmail())
+ .build();
+ return reviewRepository.save(aReview);
+ }
+
+ @Transactional(readOnly = true)
+ public List getAllReviews() {
+ logger.info("Fetching all reviews");
+ return reviewRepository.findAll();
+ }
+
+ @Transactional(readOnly = true)
+ public List getReviewsByEmail(String email) {
+ logger.info("Fetching review with email: {}", email);
+ return reviewRepository.findByEmail(email);
+ }
+
+ @Transactional(readOnly = true)
+ public List getReviewsByCourseIdAndEmail(Long courseId, String email) {
+ logger.info("Fetching review with course Id: {} and by email {}", courseId, email);
+ return reviewRepository.findByCourseIdAndEmail(courseId, email);
+ }
+
+ @Transactional(readOnly = true)
+ public List getReviewsByCourseId(Long courseId) {
+ logger.info("Fetching review with course Id : {}", courseId);
+ return reviewRepository.findByCourseId(courseId);
+ }
+
+ @Transactional
+ public void deleteReview(String id) {
+ logger.info("Deleting review with id: {}", id);
+ if (!reviewRepository.existsById(id)) {
+ throw new ReviewNotFoundException("Review not found with id: " + id);
+ }
+ reviewRepository.deleteById(id);
+ }
+
+ @Transactional
+ public Review getReviewId(String id) {
+ logger.info("Fetching review with id: {}", id);
+ return reviewRepository.findById(id)
+ .orElseThrow(() -> new ReviewNotFoundException(id));
+ }
+}
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewController.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewController.java
new file mode 100644
index 0000000..b5379a6
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewController.java
@@ -0,0 +1,72 @@
+package io.javatab.microservices.core.review.web;
+
+import io.javatab.microservices.core.review.domain.Review;
+import io.javatab.microservices.core.review.domain.ReviewService;
+
+import jakarta.validation.Valid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController()
+@RequestMapping("/api/reviews")
+public class ReviewController {
+
+ private static final Logger logger = LoggerFactory.getLogger(ReviewController.class);
+ private final ReviewService reviewService;
+
+ public ReviewController(ReviewService reviewService) {
+ this.reviewService = reviewService;
+ }
+
+
+ /*
+ * http POST :9002/api/reviews courseId:=1 author="John Doe" content="Amazing book" email="abc@xyz.com"
+ * */
+ @PostMapping
+ public ResponseEntity addReview(@Valid @RequestBody ReviewDTO review) {
+ logger.info("Received request to add review to course id {} by email: {} and ", review.getCourseId(), review.getEmail());
+ Review addedReview = reviewService.addReview(review);
+ return new ResponseEntity<>(addedReview, HttpStatus.CREATED);
+ }
+
+ @GetMapping
+ public ResponseEntity> getAllReviews() {
+ logger.info("Received request to fetch all reviews");
+ return ResponseEntity.ok(reviewService.getAllReviews());
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity getReviewById(@PathVariable String id) {
+ logger.info("Received request to fetch review with id: {}", id);
+ return ResponseEntity.ok(reviewService.getReviewId(id));
+ }
+
+ @GetMapping(params = {"course"})
+ public ResponseEntity> getReviewByCourseId(@RequestParam("course") Long courseId) {
+ logger.info("Received request to fetch review with course id: {}", courseId);
+ return ResponseEntity.ok(reviewService.getReviewsByCourseId(courseId));
+ }
+
+ /*
+ * http :9002/api/reviews courseId==1 email==abc@xyz.com
+ * or
+ * http GET "http://localhost:9002/api/reviews?courseId=1&email=abc@xyz.com"
+ * */
+ @GetMapping(params = {"courseId", "email"})
+ public ResponseEntity> getReviewByCourseIdAndEmail(@RequestParam("courseId") Long courseId, @RequestParam("email") String email) {
+ logger.info("Received request to fetch review with course id: {} and email : {}", courseId, email);
+ return ResponseEntity.ok(reviewService.getReviewsByCourseIdAndEmail(courseId, email));
+ }
+
+ @DeleteMapping("/{id}")
+ public ResponseEntity deleteReview(@PathVariable String id) {
+ logger.info("Received request to delete review with id: {}", id);
+ reviewService.deleteReview(id);
+ return ResponseEntity.noContent().build();
+ }
+}
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewControllerAdvice.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewControllerAdvice.java
new file mode 100644
index 0000000..803c772
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewControllerAdvice.java
@@ -0,0 +1,35 @@
+/*
+package io.javatab.microservices.core.review.web;
+
+import io.javatab.microservices.core.review.domain.ReviewNotFoundException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.util.stream.Collectors;
+
+@ControllerAdvice
+public class ReviewControllerAdvice {
+ @ExceptionHandler(ReviewNotFoundException.class)
+ public ResponseEntity handleNotFound(ReviewNotFoundException ex) {
+ return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) {
+ String errorMessage = ex.getBindingResult()
+ .getFieldErrors()
+ .stream()
+ .map(error -> error.getField() + ": " + error.getDefaultMessage())
+ .collect(Collectors.joining(", "));
+ return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity handleGenericException(Exception ex) {
+ return new ResponseEntity<>("An unexpected error occurred", HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+}
+*/
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewDTO.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewDTO.java
new file mode 100644
index 0000000..a572752
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewDTO.java
@@ -0,0 +1,26 @@
+package io.javatab.microservices.core.review.web;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ReviewDTO {
+
+ @NotNull(message = "The course id must be defined.")
+ private int courseId;
+ @NotBlank(message = "Author is required")
+ @Size(min = 4, max = 40, message = "Author must be between 4 and 40 characters")
+ private String author;
+ @NotBlank(message = "Content is required")
+ @Size(min = 5, max = 500, message = "Content must be between 50 and 500 characters")
+ private String content;
+ @NotBlank(message = "Email is required")
+ @Email(message = "Email should be valid")
+ private String email;
+
+}
diff --git a/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewResponse.java b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewResponse.java
new file mode 100644
index 0000000..68548cc
--- /dev/null
+++ b/microservices/review-service/src/main/java/io/javatab/microservices/core/review/web/ReviewResponse.java
@@ -0,0 +1,12 @@
+package io.javatab.microservices.core.review.web;
+
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ReviewResponse {
+ private String id;
+ // Add other response related object data
+}
diff --git a/microservices/review-service/src/main/resources/application.yml b/microservices/review-service/src/main/resources/application.yml
new file mode 100644
index 0000000..02d43ad
--- /dev/null
+++ b/microservices/review-service/src/main/resources/application.yml
@@ -0,0 +1,55 @@
+app:
+ jwk-set-uri: ${APP_KEYCLOAK_JWK_SET_URI:http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs}
+server:
+ port: 9002
+ error:
+ include-message: always
+ include-binding-errors: always
+spring:
+ application:
+ name: review-service
+ data:
+ mongodb:
+ uri: mongodb://localhost:27017/review-db
+ #host: localhost
+ #port: 27017
+ #database: review-db
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: ${KEYCLOAK_ISSUER_URI:http://localhost:8081/realms/course-management-realm}
+ jwk-set-uri: ${KEYCLOAK_JWK_SET_URI:http://localhost:8081/realms/course-management-realm/protocol/openid-connect/certs}
+logging:
+ level:
+ org.springframework: INFO
+ com.javatab: DEBUG
+ pattern:
+ console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
+management:
+ endpoints:
+ web:
+ exposure:
+ include: "*"
+ endpoint:
+ health:
+ show-details: always
+
+---
+app:
+ jwk-set-uri: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+spring:
+ config:
+ activate:
+ on-profile: docker
+ data:
+ mongodb:
+ uri: mongodb://mongodb:27017/review-db
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: http://keycloak:8080/realms/course-management-realm
+ jwk-set-uri: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+server:
+ port: 8080
\ No newline at end of file
diff --git a/microservices/review-service/src/test/java/io/javatab/microservices/core/review/ReviewServiceApplicationTests.java b/microservices/review-service/src/test/java/io/javatab/microservices/core/review/ReviewServiceApplicationTests.java
new file mode 100644
index 0000000..7ac842d
--- /dev/null
+++ b/microservices/review-service/src/test/java/io/javatab/microservices/core/review/ReviewServiceApplicationTests.java
@@ -0,0 +1,13 @@
+package io.javatab.microservices.core.review;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ReviewServiceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/microservices/search-service/.gitignore b/microservices/search-service/.gitignore
deleted file mode 100644
index 549e00a..0000000
--- a/microservices/search-service/.gitignore
+++ /dev/null
@@ -1,33 +0,0 @@
-HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### IntelliJ IDEA ###
-.idea
-*.iws
-*.iml
-*.ipr
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
diff --git a/microservices/search-service/Dockerfile b/microservices/search-service/Dockerfile
deleted file mode 100644
index 61299b0..0000000
--- a/microservices/search-service/Dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
-
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
-WORKDIR application
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
-
-
-EXPOSE 8080
-
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
diff --git a/microservices/search-service/pom.xml b/microservices/search-service/pom.xml
deleted file mode 100644
index be8c6b4..0000000
--- a/microservices/search-service/pom.xml
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.4.3
-
-
- io.javatab.microservices.core.search
- search-service
- 1.0.0
- search-service
- Demo project for Spring Boot
-
- 17
- 1.16.2
- 2024.0.0
-
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springframework.boot
- spring-boot-starter-data-elasticsearch
-
-
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-client
-
-
- org.springframework.cloud
- spring-cloud-starter-config
-
-
- org.springframework.retry
- spring-retry
-
-
-
- io.javatab.microservices.api
- api
- 1.0.0
- compile
-
-
- io.javatab.microservices.util
- util
- 1.0.0
- compile
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- org.testcontainers
- elasticsearch
- test
-
-
- org.testcontainers
- junit-jupiter
- test
-
-
-
-
-
-
- org.testcontainers
- testcontainers-bom
- ${testcontainers.version}
- pom
- import
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
- true
-
-
-
-
-
-
diff --git a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/SearchServiceApplication.java b/microservices/search-service/src/main/java/io/javatab/microservices/core/search/SearchServiceApplication.java
deleted file mode 100644
index 5ca8e37..0000000
--- a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/SearchServiceApplication.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package io.javatab.microservices.core.search;
-
-import io.javatab.microservices.core.search.persistence.ElasticRepository;
-import io.javatab.microservices.core.search.persistence.SearchEntity;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.ComponentScan;
-
-@SpringBootApplication
-@ComponentScan("io.javatab")
-public class SearchServiceApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(SearchServiceApplication.class, args);
- }
-}
diff --git a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/persistence/ElasticRepository.java b/microservices/search-service/src/main/java/io/javatab/microservices/core/search/persistence/ElasticRepository.java
deleted file mode 100644
index 7932d9a..0000000
--- a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/persistence/ElasticRepository.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package io.javatab.microservices.core.search.persistence;
-
-
-import co.elastic.clients.elasticsearch.core.DeleteResponse;
-import co.elastic.clients.elasticsearch.core.IndexResponse;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
-import org.springframework.stereotype.Repository;
-import reactor.core.publisher.Mono;
-
-import java.util.Map;
-import java.util.UUID;
-
-@Repository
-public class ElasticRepository {
-
- public static final String COURSE_INDEX = "course-store";
- private final ReactiveElasticsearchClient client;
- private final ObjectMapper objectMapper;
-
- public ElasticRepository(ReactiveElasticsearchClient client, ObjectMapper objectMapper) {
- this.client = client;
- this.objectMapper = objectMapper;
- }
-
- public Mono createCourse(SearchEntity course) {
- String id = UUID.randomUUID().toString();
- Map documentMapper = objectMapper.convertValue(course,
- Map.class);
- // return client.index(indexRequest -> indexRequest.index(COURSE_INDEX).id(id).source(documentMapper));
- return client.index(indexRequest -> indexRequest.index(COURSE_INDEX).id(id).document(documentMapper));
- }
-
- public Mono deleteCourse() {
- return client.delete(deleteRequest -> deleteRequest.index(COURSE_INDEX));
- }
-}
diff --git a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/persistence/SearchEntity.java b/microservices/search-service/src/main/java/io/javatab/microservices/core/search/persistence/SearchEntity.java
deleted file mode 100644
index fd9f35f..0000000
--- a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/persistence/SearchEntity.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.javatab.microservices.core.search.persistence;
-
-
-public record SearchEntity(String courseName, int courseId) {
-}
diff --git a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/service/SearchServiceImpl.java b/microservices/search-service/src/main/java/io/javatab/microservices/core/search/service/SearchServiceImpl.java
deleted file mode 100644
index 3e2025b..0000000
--- a/microservices/search-service/src/main/java/io/javatab/microservices/core/search/service/SearchServiceImpl.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.javatab.microservices.core.search.service;
-
-import io.javatab.microservices.api.core.search.SearchRecord;
-import io.javatab.microservices.api.core.search.SearchService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import reactor.core.publisher.Mono;
-
-public class SearchServiceImpl implements SearchService {
- private static final Logger LOG = LoggerFactory.getLogger(SearchServiceImpl.class);
-
-
- @Override
- public Mono getCourse(String courseName) {
- LOG.info("In log service");
- return Mono.just(new SearchRecord(1, "course name", 1, 2));
- }
-}
diff --git a/microservices/search-service/src/main/resources/application.yml b/microservices/search-service/src/main/resources/application.yml
deleted file mode 100644
index d7039a6..0000000
--- a/microservices/search-service/src/main/resources/application.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-spring.config.import: "configserver:"
-
-spring:
- application.name: search
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
-
----
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
diff --git a/microservices/search-service/src/test/java/io/javatab/microservices/core/search/ElasticsearchTestBase.java b/microservices/search-service/src/test/java/io/javatab/microservices/core/search/ElasticsearchTestBase.java
deleted file mode 100644
index 9ff4bc1..0000000
--- a/microservices/search-service/src/test/java/io/javatab/microservices/core/search/ElasticsearchTestBase.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.javatab.microservices.core.search;
-
-import org.apache.http.HttpHost;
-import org.elasticsearch.client.Request;
-import org.elasticsearch.client.Response;
-import org.elasticsearch.client.RestClient;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.elasticsearch.ElasticsearchContainer;
-
-import java.io.IOException;
-import java.util.Collections;
-
-public abstract class ElasticsearchTestBase {
- private static ElasticsearchContainer database = new ElasticsearchContainer("elasticsearch:7.17.2");
-
- static {
- database.start();
- }
-
- @DynamicPropertySource
- static void setProperties(DynamicPropertyRegistry registry) {
- registry.add("spring.elasticsearch.uris", () -> Collections.singletonList("http://" + database.getContainerIpAddress() + "/9200"));
- }
-
- RestClient client = RestClient.builder(HttpHost.create(database.getHttpHostAddress()))
- .build();
-
- Response response;
-
- {
- try {
- response = client.performRequest(new Request("GET", "/_cluster/health"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
-}
diff --git a/microservices/search-service/src/test/java/io/javatab/microservices/core/search/PersistenceTests.java b/microservices/search-service/src/test/java/io/javatab/microservices/core/search/PersistenceTests.java
deleted file mode 100644
index 38708c5..0000000
--- a/microservices/search-service/src/test/java/io/javatab/microservices/core/search/PersistenceTests.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package io.javatab.microservices.core.search;
-
-import io.javatab.microservices.core.search.persistence.ElasticRepository;
-import io.javatab.microservices.core.search.persistence.SearchEntity;
-import org.junit.jupiter.api.BeforeEach;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class PersistenceTests extends ElasticsearchTestBase {
-
- @Autowired
- private ElasticRepository repository;
- private SearchEntity savedEntity;
-
- @BeforeEach
- void setUp() {
- repository.deleteCourse();
- SearchEntity entity = new SearchEntity("Course Name", 1);
- }
-}
diff --git a/microservices/search-service/src/test/java/io/javatab/microservices/core/search/SearchServiceApplicationTests.java b/microservices/search-service/src/test/java/io/javatab/microservices/core/search/SearchServiceApplicationTests.java
deleted file mode 100644
index 81f100c..0000000
--- a/microservices/search-service/src/test/java/io/javatab/microservices/core/search/SearchServiceApplicationTests.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.javatab.microservices.core.search;
-
-import org.springframework.boot.test.context.SpringBootTest;
-
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(webEnvironment=RANDOM_PORT, properties = {"eureka.client.enabled=false", "spring.cloud.config.enabled=false"})
-class SearchServiceApplicationTests extends ElasticsearchTestBase {
-
-}
diff --git a/microservices/search-service/src/test/resource/application.yml b/microservices/search-service/src/test/resource/application.yml
deleted file mode 100644
index e69de29..0000000
diff --git a/microservices/student-service/.gitignore b/microservices/student-service/.gitignore
deleted file mode 100644
index 549e00a..0000000
--- a/microservices/student-service/.gitignore
+++ /dev/null
@@ -1,33 +0,0 @@
-HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### IntelliJ IDEA ###
-.idea
-*.iws
-*.iml
-*.ipr
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
diff --git a/microservices/student-service/Dockerfile b/microservices/student-service/Dockerfile
deleted file mode 100644
index 61299b0..0000000
--- a/microservices/student-service/Dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
-
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
-WORKDIR application
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
-
-
-EXPOSE 8080
-
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
diff --git a/microservices/student-service/pom.xml b/microservices/student-service/pom.xml
deleted file mode 100644
index 8af4da0..0000000
--- a/microservices/student-service/pom.xml
+++ /dev/null
@@ -1,119 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.4.3
-
-
- io.javatab.microservices.core.student
- student-service
- 1.0.0
- student-service
- Demo project for Spring Boot
-
- 17
- 1.16.2
- 2024.0.0
-
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.postgresql
- postgresql
- runtime
-
-
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-client
-
-
- org.springframework.cloud
- spring-cloud-starter-config
-
-
- org.springframework.retry
- spring-retry
-
-
-
- io.javatab.microservices.api
- api
- 1.0.0
-
-
- io.javatab.microservices.util
- util
- 1.0.0
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- org.testcontainers
- junit-jupiter
- test
-
-
- org.testcontainers
- postgresql
- test
-
-
-
-
-
- org.testcontainers
- testcontainers-bom
- ${testcontainers.version}
- pom
- import
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
-
- repackage
-
-
-
-
-
-
-
-
diff --git a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/StudentServiceApplication.java b/microservices/student-service/src/main/java/io/javatab/microservices/core/student/StudentServiceApplication.java
deleted file mode 100644
index b4ecbdb..0000000
--- a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/StudentServiceApplication.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.javatab.microservices.core.student;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.annotation.ComponentScan;
-
-@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
-@ComponentScan("io.javatab")
-public class StudentServiceApplication {
-
- public static void main(String[] args) {
- ConfigurableApplicationContext ctx = SpringApplication.run(StudentServiceApplication.class, args);
- String postgresqlUri = ctx.getEnvironment().getProperty("spring.datasource.url");
- System.out.println("Connected to Postgres SQL: " + postgresqlUri);
- }
-
-}
diff --git a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/persistence/StudentEntity.java b/microservices/student-service/src/main/java/io/javatab/microservices/core/student/persistence/StudentEntity.java
deleted file mode 100644
index f8a0661..0000000
--- a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/persistence/StudentEntity.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package io.javatab.microservices.core.student.persistence;
-
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
-import org.apache.commons.lang3.builder.EqualsBuilder;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
-
-
-@Entity
-@Table(name = "student")
-public class StudentEntity {
-
- @Id
- @GeneratedValue
- private int id;
-
- private int studentId;
- private String studentName;
- private String email;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public StudentEntity() {
- }
-
- public StudentEntity(int studentId, String studentName, String email) {
- this.studentId = studentId;
- this.studentName = studentName;
- this.email = email;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
-
- if (o == null || getClass() != o.getClass()) return false;
-
- StudentEntity that = (StudentEntity) o;
-
- return new EqualsBuilder().append(studentId, that.studentId).append(studentName, that.studentName).append(email, that.email).isEquals();
- }
-
- @Override
- public int hashCode() {
- return new HashCodeBuilder(17, 37).append(studentId).append(studentName).append(email).toHashCode();
- }
-}
diff --git a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/persistence/StudentRepository.java b/microservices/student-service/src/main/java/io/javatab/microservices/core/student/persistence/StudentRepository.java
deleted file mode 100644
index cbaa867..0000000
--- a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/persistence/StudentRepository.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.javatab.microservices.core.student.persistence;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public interface StudentRepository extends JpaRepository {
-
-}
diff --git a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/service/StudentServiceImpl.java b/microservices/student-service/src/main/java/io/javatab/microservices/core/student/service/StudentServiceImpl.java
deleted file mode 100644
index fb92528..0000000
--- a/microservices/student-service/src/main/java/io/javatab/microservices/core/student/service/StudentServiceImpl.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package io.javatab.microservices.core.student.service;
-
-import io.javatab.microservices.api.core.student.Student;
-import io.javatab.microservices.api.core.student.StudentService;
-import io.javatab.microservices.util.http.ServiceUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Mono;
-
-@RestController
-public class StudentServiceImpl implements StudentService {
-
- private static final Logger LOG = LoggerFactory.getLogger(StudentServiceImpl.class);
-
- private final ServiceUtil serviceUtil;
-
- public StudentServiceImpl(final ServiceUtil serviceUtil) {
- this.serviceUtil = serviceUtil;
- }
-
- @Override
- public Mono getStudent(String studentId) {
- LOG.info("In student service");
- return Mono.just(new Student(1, "Nasir", "nasir@gmail.com", "pass00d"));
- }
-}
diff --git a/microservices/student-service/src/main/resources/application.yml b/microservices/student-service/src/main/resources/application.yml
deleted file mode 100644
index fffb97b..0000000
--- a/microservices/student-service/src/main/resources/application.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-spring.config.import: "configserver:"
-
-spring:
- application.name: student
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
-
----
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
diff --git a/microservices/student-service/src/test/java/io/javatab/microservices/core/student/PersistenceTests.java b/microservices/student-service/src/test/java/io/javatab/microservices/core/student/PersistenceTests.java
deleted file mode 100644
index 69b3030..0000000
--- a/microservices/student-service/src/test/java/io/javatab/microservices/core/student/PersistenceTests.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package io.javatab.microservices.core.student;
-
-import io.javatab.microservices.core.student.persistence.StudentEntity;
-import io.javatab.microservices.core.student.persistence.StudentRepository;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import org.springframework.transaction.annotation.Transactional;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.springframework.transaction.annotation.Propagation.NOT_SUPPORTED;
-
-@Transactional(propagation = NOT_SUPPORTED)
-@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
-@DataJpaTest(properties = {"spring.jpa.hibernate.ddl-auto=update", "spring.cloud.config.enabled=false"})
-public class PersistenceTests extends PostgresTestBase {
-
- @Autowired
- private StudentRepository repository;
-
- private StudentEntity savedEntity;
-
- @BeforeEach
- void setUp() {
- repository.deleteAll();
- StudentEntity entity = new StudentEntity(1, "Student Name", "student@email.com");
- savedEntity = repository.save(entity);
- assertEquals(entity, savedEntity);
- }
-
- @Test
- void create() {
- StudentEntity newEntity = new StudentEntity(1, "Student Name", "student@email.com");
- repository.save(newEntity);
-
- StudentEntity foundEntity = repository.findById(newEntity.getId()).get();
- assertEquals(newEntity.getId(), newEntity.getId());
- }
-}
diff --git a/microservices/student-service/src/test/java/io/javatab/microservices/core/student/PostgresTestBase.java b/microservices/student-service/src/test/java/io/javatab/microservices/core/student/PostgresTestBase.java
deleted file mode 100644
index 8cca0fb..0000000
--- a/microservices/student-service/src/test/java/io/javatab/microservices/core/student/PostgresTestBase.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package io.javatab.microservices.core.student;
-
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.containers.PostgreSQLContainer;
-
-public class PostgresTestBase {
-
- private static PostgreSQLContainer database = new PostgreSQLContainer("postgres:alpine3.15");
-
- static {
- database.start();
- }
-
- @DynamicPropertySource
- static void databaseProperties(DynamicPropertyRegistry registry) {
- registry.add("spring.datasource.url", database::getJdbcUrl);
- registry.add("spring.datasource.username", database::getUsername);
- registry.add("spring.datasource.password", database::getPassword);
- }
-}
diff --git a/microservices/student-service/src/test/java/io/javatab/microservices/core/student/StudentServiceApplicationTests.java b/microservices/student-service/src/test/java/io/javatab/microservices/core/student/StudentServiceApplicationTests.java
deleted file mode 100644
index ac9883b..0000000
--- a/microservices/student-service/src/test/java/io/javatab/microservices/core/student/StudentServiceApplicationTests.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javatab.microservices.core.student;
-
-import org.springframework.boot.test.context.SpringBootTest;
-
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(webEnvironment=RANDOM_PORT, properties = {"eureka.client.enabled=false", "spring.cloud.config.enabled=false"})
-class StudentServiceApplicationTests {
-
-
-}
diff --git a/microservices/student-service/src/test/resources/application.yml b/microservices/student-service/src/test/resources/application.yml
deleted file mode 100644
index e69de29..0000000
diff --git a/microservices/vote-service/.gitignore b/microservices/vote-service/.gitignore
deleted file mode 100644
index 549e00a..0000000
--- a/microservices/vote-service/.gitignore
+++ /dev/null
@@ -1,33 +0,0 @@
-HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### IntelliJ IDEA ###
-.idea
-*.iws
-*.iml
-*.ipr
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
diff --git a/microservices/vote-service/Dockerfile b/microservices/vote-service/Dockerfile
deleted file mode 100644
index 61299b0..0000000
--- a/microservices/vote-service/Dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
-
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
-WORKDIR application
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
-
-
-EXPOSE 8080
-
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
diff --git a/microservices/vote-service/pom.xml b/microservices/vote-service/pom.xml
deleted file mode 100644
index bab38d0..0000000
--- a/microservices/vote-service/pom.xml
+++ /dev/null
@@ -1,111 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.4.3
-
-
- io.javatab.microservices.core.vote
- vote-service
- 1.0.0
- vote-service
- Demo project for Spring Boot
-
- 17
- 1.16.2
- 2024.0.0
-
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springframework.boot
- spring-boot-starter-data-redis-reactive
-
-
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-client
-
-
- org.springframework.cloud
- spring-cloud-starter-config
-
-
- org.springframework.retry
- spring-retry
-
-
-
- io.javatab.microservices.api
- api
- 1.0.0
- compile
-
-
- io.javatab.microservices.util
- util
- 1.0.0
- compile
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- org.testcontainers
- junit-jupiter
- test
-
-
-
-
-
- org.testcontainers
- testcontainers-bom
- ${testcontainers.version}
- pom
- import
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
-
- repackage
-
-
-
-
-
-
-
-
diff --git a/microservices/vote-service/src/main/java/io/javatab/microservices/core/.DS_Store b/microservices/vote-service/src/main/java/io/javatab/microservices/core/.DS_Store
deleted file mode 100644
index a81c1d2..0000000
Binary files a/microservices/vote-service/src/main/java/io/javatab/microservices/core/.DS_Store and /dev/null differ
diff --git a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/VoteServiceApplication.java b/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/VoteServiceApplication.java
deleted file mode 100644
index 6e25d4c..0000000
--- a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/VoteServiceApplication.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package io.javatab.microservices.core.vote;
-
-import io.javatab.microservices.api.core.vote.Vote;
-import io.javatab.microservices.core.vote.persistence.RedisRepository;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class VoteServiceApplication implements CommandLineRunner {
-
- private final RedisRepository repository;
-
- public VoteServiceApplication(RedisRepository repository) {
- this.repository = repository;
- }
-
- public static void main(String[] args) {
- SpringApplication.run(VoteServiceApplication.class, args);
- }
-
- @Override
- public void run(String... args) throws Exception {
- Vote vote = new Vote(1, 1, 3, 9);
- repository.save(vote).subscribe(aLong -> System.out.println(repository.getVote(aLong).subscribe(vote1 -> System.out.println("Vote " + vote1.courseId()))));
- }
-}
diff --git a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/configuration/RedisConfig.java b/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/configuration/RedisConfig.java
deleted file mode 100644
index 2bbc52a..0000000
--- a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/configuration/RedisConfig.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package io.javatab.microservices.core.vote.configuration;
-
-import io.javatab.microservices.api.core.vote.Vote;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
-import org.springframework.data.redis.core.ReactiveRedisTemplate;
-import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
-import org.springframework.data.redis.serializer.RedisSerializationContext;
-
-@Configuration
-public class RedisConfig {
-
- @Bean
- public ReactiveRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
- return new ReactiveRedisTemplate(
- factory,
- RedisSerializationContext.fromSerializer(new Jackson2JsonRedisSerializer(Vote.class))
- );
- }
-}
diff --git a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/persistence/RedisRepository.java b/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/persistence/RedisRepository.java
deleted file mode 100644
index 425b41a..0000000
--- a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/persistence/RedisRepository.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package io.javatab.microservices.core.vote.persistence;
-
-import io.javatab.microservices.api.core.vote.Vote;
-import org.springframework.data.redis.core.ReactiveHashOperations;
-import org.springframework.data.redis.core.ReactiveRedisOperations;
-import org.springframework.stereotype.Repository;
-import reactor.core.publisher.Mono;
-
-@Repository
-public class RedisRepository {
-
- private final static String KEY = "VOTE";
-
- private final ReactiveRedisOperations redisOperations;
- private final ReactiveHashOperations hashOperations;
-
- public RedisRepository(ReactiveRedisOperations redisOperations) {
- this.redisOperations = redisOperations;
- this.hashOperations = redisOperations.opsForHash();
- }
-
- public Mono save(Vote post){
- return this.redisOperations.opsForList().rightPush(KEY, post);
- }
-
- public Mono getVote(Long id) {
- return this.redisOperations.opsForList().rightPop(KEY);
- }
-}
diff --git a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/persistence/VoteEntity.java b/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/persistence/VoteEntity.java
deleted file mode 100644
index 2537074..0000000
--- a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/persistence/VoteEntity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.javatab.microservices.core.vote.persistence;
-
-import nonapi.io.github.classgraph.json.Id;
-import org.springframework.data.annotation.Version;
-import org.springframework.data.redis.core.RedisHash;
-
-@RedisHash("vote")
-public class VoteEntity {
-
- @Id
- private String id;
-
- @Version
- private Integer version;
-
- private int courseId;
-
- private int studentId;
-
- private int like;
-
- private int dislike;
-}
diff --git a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/service/VoteServiceImpl.java b/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/service/VoteServiceImpl.java
deleted file mode 100644
index 9d0d77d..0000000
--- a/microservices/vote-service/src/main/java/io/javatab/microservices/core/vote/service/VoteServiceImpl.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package io.javatab.microservices.core.vote.service;
-
-import io.javatab.microservices.api.core.vote.Vote;
-import io.javatab.microservices.api.core.vote.VoteService;
-import io.javatab.microservices.util.http.ServiceUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Mono;
-
-@RestController
-@ComponentScan("io.javatab")
-public class VoteServiceImpl implements VoteService {
-
- private static final Logger LOG = LoggerFactory.getLogger(VoteServiceImpl.class);
-
- private final ServiceUtil serviceUtil;
-
- public VoteServiceImpl(ServiceUtil serviceUtil) {
- this.serviceUtil = serviceUtil;
- }
-
- @Override
- public Mono getVote(int courseId) {
- LOG.info("In vote service");
- return Mono.just(new Vote(1, 123, 1, 2));
- }
-}
diff --git a/microservices/vote-service/src/main/resources/application.yml b/microservices/vote-service/src/main/resources/application.yml
deleted file mode 100644
index 84fa0e1..0000000
--- a/microservices/vote-service/src/main/resources/application.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-spring.config.import: "configserver:"
-
-spring:
- application.name: vote
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
-
----
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
diff --git a/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/PersistenceTest.java b/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/PersistenceTest.java
deleted file mode 100644
index 2e2392b..0000000
--- a/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/PersistenceTest.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.javatab.microservices.core.vote;
-
-public class PersistenceTest {
-}
diff --git a/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/RedisTestBase.java b/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/RedisTestBase.java
deleted file mode 100644
index 2ae7485..0000000
--- a/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/RedisTestBase.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package io.javatab.microservices.core.vote;
-
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.utility.DockerImageName;
-
-public abstract class RedisTestBase {
-
- private static GenericContainer redisContainer = new GenericContainer(
- DockerImageName.parse("redis:6.2.6-alpine")
- ).withExposedPorts(6379);
-
- static {
- redisContainer.start();
- }
-
- @DynamicPropertySource
- static void setProperties(DynamicPropertyRegistry registry) {
- registry.add("redis.host", redisContainer::getContainerIpAddress);
- registry.add("redis.port", () -> redisContainer.getMappedPort(6379));
- registry.add("redis.database", () -> "test");
- }
-}
diff --git a/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/VoteServiceApplicationTests.java b/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/VoteServiceApplicationTests.java
deleted file mode 100644
index dc76397..0000000
--- a/microservices/vote-service/src/test/java/io/javatab/microservices/core/vote/VoteServiceApplicationTests.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package io.javatab.microservices.core.vote;
-
-import org.springframework.boot.test.context.SpringBootTest;
-
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT, properties = {
- "eureka.client.enabled=false",
- "spring.jpa.hibernate.ddl-auto=update",
- "spring.cloud.config.enabled=false"})
-class VoteServiceApplicationTests extends RedisTestBase {
-
-
-}
diff --git a/microservices/vote-service/src/test/resources/application.yml b/microservices/vote-service/src/test/resources/application.yml
deleted file mode 100644
index e69de29..0000000
diff --git a/mvnw b/mvnw
index 8a8fb22..19529dd 100755
--- a/mvnw
+++ b/mvnw
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
-# https://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@@ -19,298 +19,241 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven Start Up Batch script
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
+# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ] ; then
-
- if [ -f /usr/local/etc/mavenrc ] ; then
- . /usr/local/etc/mavenrc
- fi
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
-fi
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
-case "`uname`" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
- else
- export JAVA_HOME="/Library/Java/Home"
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
fi
fi
- ;;
-esac
-
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
fi
-fi
-
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
+}
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
done
+ printf %x\\n $h
+}
- saveddir=`pwd`
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
- M2_HOME=`dirname "$PRG"`/..
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
- if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
- else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
- fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
-if [ -z "$JAVACMD" ] ; then
- if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- else
- JAVACMD="`\\unset -f command; \\command -v java`"
- fi
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
fi
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+mkdir -p -- "${MAVEN_HOME%/*}"
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
fi
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
- if [ -z "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
- fi
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
- # end of workaround
- done
- echo "${basedir}"
-}
-
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
fi
-}
-
-BASE_DIR=`find_maven_basedir "$(pwd)"`
-if [ -z "$BASE_DIR" ]; then
- exit 1;
fi
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found .mvn/wrapper/maven-wrapper.jar"
- fi
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
- fi
- if [ -n "$MVNW_REPOURL" ]; then
- jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
- else
- jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
- fi
- while IFS="=" read key value; do
- case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
- esac
- done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Downloading from: $jarUrl"
- fi
- wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
- if $cygwin; then
- wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
- fi
-
- if command -v wget > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found wget ... using wget"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- else
- wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- fi
- elif command -v curl > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found curl ... using curl"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl -o "$wrapperJarPath" "$jarUrl" -f
- else
- curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
- fi
-
- else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Falling back to using Java to download"
- fi
- javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaClass=`cygpath --path --windows "$javaClass"`
- fi
- if [ -e "$javaClass" ]; then
- if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Compiling MavenWrapperDownloader.java ..."
- fi
- # Compiling the Java class
- ("$JAVA_HOME/bin/javac" "$javaClass")
- fi
- if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- # Running the downloader
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Running MavenWrapperDownloader.java ..."
- fi
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
- fi
- fi
- fi
-fi
-##########################################################################################
-# End of extension
-##########################################################################################
-
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-if [ "$MVNW_VERBOSE" = true ]; then
- echo $MAVEN_PROJECTBASEDIR
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
-fi
-
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
-export MAVEN_CMD_LINE_ARGS
-
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- $MAVEN_DEBUG_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" \
- "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index 1d8ab01..249bdf3 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -1,3 +1,4 @@
+<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@@ -7,7 +8,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
-@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,171 +19,131 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven Start Up Batch script
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
-if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
-
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %DOWNLOAD_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
-@REM End of extension
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% ^
- %JVM_CONFIG_MAVEN_PROPS% ^
- %MAVEN_OPTS% ^
- %MAVEN_DEBUG_OPTS% ^
- -classpath %WRAPPER_JAR% ^
- "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
- %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
-if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%"=="on" pause
-
-if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-
-cmd /C exit /B %ERROR_CODE%
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/pom.xml b/pom.xml
index e99c8af..aaf2007 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
4.0.0
io.javatab.microservices
- student-course-system
+ course-management-system
1.0.0
pom
- student-course-system
- Parent Pom for the student course registration system project
+ course-management-system
+ Parent Pom for the course management system project
org.springframework.boot
@@ -19,16 +19,10 @@
- api
util
microservices/course-composite-service
microservices/course-service
- microservices/search-service
- microservices/student-service
- microservices/vote-service
- spring-cloud/eureka-server
- spring-cloud/gateway
- spring-cloud/authorization-server
- spring-cloud/config-server
+ microservices/review-service
+ spring-cloud/gateway-service
\ No newline at end of file
diff --git a/run.sh b/run.sh
index e69de29..dd56376 100644
--- a/run.sh
+++ b/run.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+# Kill services running on ports 5000, 9001, and 9002
+kill_service_on_port() {
+ local port=$1
+ local pid=$(lsof -ti :$port)
+ if [[ -n "$pid" ]]; then
+ echo "Killing process on port $port (PID: $pid)"
+ kill -9 $pid
+ fi
+}
+
+# Build all services before running them
+mvn clean package -DskipTests
+
+# Function to find and run the latest JAR file in a given service directory
+run_service() {
+ kill_service_on_port 5000
+ kill_service_on_port 9000
+ kill_service_on_port 9001
+ kill_service_on_port 9002
+ local service_dir=$1
+ local jar_file=$(find "microservices/$service_dir/target" -type f -name "*.jar" | head -n 1)
+
+ if [[ -z "$jar_file" ]]; then
+ echo "No JAR file found for $service_dir!"
+ return 1
+ fi
+
+ echo "Starting $service_dir using $jar_file..."
+ java -jar "$jar_file" &
+}
+
+# Check if the first argument is 'docker'
+if [[ "$1" == "docker" ]]; then
+ echo "CD to docker dir..."
+ cd docker
+ echo "Stopping services using Docker Compose..."
+ docker compose -f docker-compose-base.yml down -v
+ echo "Starting services using Docker Compose..."
+ docker compose -f docker-compose-base.yml up --build
+else
+ echo "Starting services using local JAR files..."
+ # Run each service in the background
+ run_service "course-composite-service"
+ run_service "course-service"
+ run_service "review-service"
+
+ # Wait for all background processes to finish
+ wait
+
+ echo "All services started successfully."
+fi
diff --git a/spring-cloud/.DS_Store b/spring-cloud/.DS_Store
deleted file mode 100644
index b9ffbe5..0000000
Binary files a/spring-cloud/.DS_Store and /dev/null differ
diff --git a/spring-cloud/authorization-server/.gitignore b/spring-cloud/authorization-server/.gitignore
deleted file mode 100644
index 549e00a..0000000
--- a/spring-cloud/authorization-server/.gitignore
+++ /dev/null
@@ -1,33 +0,0 @@
-HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### IntelliJ IDEA ###
-.idea
-*.iws
-*.iml
-*.ipr
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
diff --git a/spring-cloud/authorization-server/Dockerfile b/spring-cloud/authorization-server/Dockerfile
deleted file mode 100644
index 5e372bc..0000000
--- a/spring-cloud/authorization-server/Dockerfile
+++ /dev/null
@@ -1,23 +0,0 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
-
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
-WORKDIR application
-# We install install curl for health check
-RUN apk --no-cache add curl
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
-
-
-EXPOSE 9999
-
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
diff --git a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/AuthorizationServerApplication.java b/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/AuthorizationServerApplication.java
deleted file mode 100644
index 4382221..0000000
--- a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/AuthorizationServerApplication.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.javatab.springcloud.auth;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class AuthorizationServerApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(AuthorizationServerApplication.class, args);
- }
-
-}
diff --git a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/configuration/AuthorizationServerConfig.java b/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/configuration/AuthorizationServerConfig.java
deleted file mode 100644
index 9b61801..0000000
--- a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/configuration/AuthorizationServerConfig.java
+++ /dev/null
@@ -1,200 +0,0 @@
-//CHECKSTYLE:OFF
-/*
- * Copyright 2020-2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.javatab.springcloud.auth.configuration;
-
-import java.time.Duration;
-import java.util.List;
-import java.util.UUID;
-import java.util.function.Consumer;
-
-import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.RSAKey;
-import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.proc.SecurityContext;
-import io.javatab.springcloud.auth.jose.Jwks;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
-import org.springframework.security.oauth2.server.authorization.authentication.*;
-import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
-import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
-import org.springframework.security.web.util.matcher.RequestMatcher;
-
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.Ordered;
-import org.springframework.core.annotation.Order;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.oidc.OidcScopes;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
-import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
-
-/**
- * @author Joe Grandja
- * @since 0.0.1
- */
-@Configuration(proxyBeanMethods = false)
-public class AuthorizationServerConfig {
-
- private static final Logger LOG = LoggerFactory.getLogger(AuthorizationServerConfig.class);
-
- @Bean
- @Order(Ordered.HIGHEST_PRECEDENCE)
- public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
-
- // Replaced this call with the implementation of applyDefaultSecurity() to be able to add a custom redirect_uri validator
- // OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
-
- OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
- new OAuth2AuthorizationServerConfigurer();
-
- // Register a custom redirect_uri validator, that allows redirect uris based on https://localhost during development
- authorizationServerConfigurer
- .authorizationEndpoint(authorizationEndpoint ->
- authorizationEndpoint
- .authenticationProviders(configureAuthenticationValidator())
- );
-
- RequestMatcher endpointsMatcher = authorizationServerConfigurer
- .getEndpointsMatcher();
-
- http
- .securityMatcher(endpointsMatcher)
- .authorizeHttpRequests(authorize ->
- authorize.anyRequest().authenticated()
- )
- .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
- .apply(authorizationServerConfigurer);
-
- http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
- .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
-
- http
- .exceptionHandling(exceptions ->
- exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
- )
- .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
-
- return http.build();
- }
-
- @Bean
- public RegisteredClientRepository registeredClientRepository() {
- RegisteredClient writerClient = RegisteredClient.withId(UUID.randomUUID().toString())
- .clientId("writer")
- .clientSecret("{noop}secret-writer")
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
- .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .redirectUri("http://my.redirect.uri")
- .redirectUri("http://localhost:8443/openapi/webjars/swagger-ui/oauth2-redirect.html")
- .scope(OidcScopes.OPENID)
- .scope("course:read")
- .scope("course:write")
- .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
- .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(1)).build())
- .build();
-
- RegisteredClient readerClient = RegisteredClient.withId(UUID.randomUUID().toString())
- .clientId("reader")
- .clientSecret("{noop}secret-reader")
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
- .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .redirectUri("http://my.redirect.uri")
- .redirectUri("http://localhost:8443/openapi/webjars/swagger-ui/oauth2-redirect.html")
- .scope(OidcScopes.OPENID)
- .scope("course:read")
- .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
- .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(1)).build())
- .build();
-
- return new InMemoryRegisteredClientRepository(writerClient, readerClient);
-
- }
-
- @Bean
- public JWKSource jwkSource() {
- RSAKey rsaKey = Jwks.generateRsa();
- JWKSet jwkSet = new JWKSet(rsaKey);
- return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
- }
-
- @Bean
- public JwtDecoder jwtDecoder(JWKSource jwkSource) {
- return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
- }
-
- @Bean
- public AuthorizationServerSettings authorizationServerSettings() {
- return AuthorizationServerSettings.builder().issuer("http://auth-server:9999").build();
- }
-
- private Consumer> configureAuthenticationValidator() {
- return (authenticationProviders) ->
- authenticationProviders.forEach((authenticationProvider) -> {
- if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
- Consumer authenticationValidator =
- // Override default redirect_uri validator
- new CustomRedirectUriValidator()
- // Reuse default scope validator
- .andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_SCOPE_VALIDATOR);
-
- ((OAuth2AuthorizationCodeRequestAuthenticationProvider) authenticationProvider)
- .setAuthenticationValidator(authenticationValidator);
- }
- });
- }
-
- static class CustomRedirectUriValidator implements Consumer {
-
- @Override
- public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
- OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
- authenticationContext.getAuthentication();
- RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
- String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
-
- LOG.trace("Will validate the redirect uri {}", requestedRedirectUri);
-
- // Use exact string matching when comparing client redirect URIs against pre-registered URIs
- if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
- LOG.trace("Redirect uri is invalid!");
- OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
- throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
- }
- LOG.trace("Redirect uri is OK!");
- }
- }
-}
-//CHECKSTYLE:ON
diff --git a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/configuration/DefaultSecurityConfig.java b/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/configuration/DefaultSecurityConfig.java
deleted file mode 100644
index 2def787..0000000
--- a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/configuration/DefaultSecurityConfig.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//CHECKSTYLE:OFF
-/*
- * Copyright 2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.javatab.springcloud.auth.configuration;
-
-import static org.springframework.security.config.Customizer.withDefaults;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.provisioning.InMemoryUserDetailsManager;
-import org.springframework.security.web.SecurityFilterChain;
-
-/**
- * @author Joe Grandja
- * @since 0.1.0
- */
-@Configuration
-@EnableWebSecurity
-public class DefaultSecurityConfig {
-
- private static final Logger LOG = LoggerFactory.getLogger(DefaultSecurityConfig.class);
-
- // formatter:off
- @Bean
- SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
- http
- .authorizeHttpRequests(authorizeRequests -> authorizeRequests
- .requestMatchers("/actuator/**").permitAll()
- .anyRequest().authenticated()
- )
- .formLogin(withDefaults());
- return http.build();
- }
- // formatter:on
-
- // @formatter:off
- @Bean
- UserDetailsService users() {
- UserDetails user = User.withDefaultPasswordEncoder()
- .username("u")
- .password("p")
- .roles("USER")
- .build();
- return new InMemoryUserDetailsManager(user);
- }
- // @formatter:on
-
-}
-//CHECKSTYLE:ON
\ No newline at end of file
diff --git a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/jose/Jwks.java b/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/jose/Jwks.java
deleted file mode 100644
index 2b4f96d..0000000
--- a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/jose/Jwks.java
+++ /dev/null
@@ -1,76 +0,0 @@
-//CHECKSTYLE:OFF
-/*
- * Copyright 2020-2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-
- */
-
-package io.javatab.springcloud.auth.jose;
-
-import com.nimbusds.jose.jwk.Curve;
-import com.nimbusds.jose.jwk.ECKey;
-import com.nimbusds.jose.jwk.OctetSequenceKey;
-import com.nimbusds.jose.jwk.RSAKey;
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.UUID;
-import javax.crypto.SecretKey;
-
-/**
- * @author Joe Grandja
- * @since 0.1.0
- */
-public final class Jwks {
-
- private Jwks() {
- }
-
- public static RSAKey generateRsa() {
- KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
- RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
- RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
- // @formatter:off
- return new RSAKey.Builder(publicKey)
- .privateKey(privateKey)
- .keyID(UUID.randomUUID().toString())
- .build();
- // @formatter:on
- }
-
- public static ECKey generateEc() {
- KeyPair keyPair = KeyGeneratorUtils.generateEcKey();
- ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
- ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
- Curve curve = Curve.forECParameterSpec(publicKey.getParams());
- // @formatter:off
- return new ECKey.Builder(curve, publicKey)
- .privateKey(privateKey)
- .keyID(UUID.randomUUID().toString())
- .build();
- // @formatter:on
- }
-
- public static OctetSequenceKey generateSecret() {
- SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
- // @formatter:off
- return new OctetSequenceKey.Builder(secretKey)
- .keyID(UUID.randomUUID().toString())
- .build();
- // @formatter:on
- }
-}
-//CHECKSTYLE:ON
\ No newline at end of file
diff --git a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/jose/KeyGeneratorUtils.java b/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/jose/KeyGeneratorUtils.java
deleted file mode 100644
index 028cc7e..0000000
--- a/spring-cloud/authorization-server/src/main/java/io/javatab/springcloud/auth/jose/KeyGeneratorUtils.java
+++ /dev/null
@@ -1,103 +0,0 @@
-//CHECKSTYLE:OFF
-/*
- * Copyright 2020-2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//CHECKSTYLE:OFF
-/*
- * Copyright 2020-2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.javatab.springcloud.auth.jose;
-
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.spec.ECFieldFp;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.EllipticCurve;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-
-/**
- * @author Joe Grandja
- * @since 0.1.0
- */
-public final class KeyGeneratorUtils {
-
- private KeyGeneratorUtils() {
- }
-
- static SecretKey generateSecretKey() {
- SecretKey hmacKey;
- try {
- hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey();
- } catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- return hmacKey;
- }
-
- static KeyPair generateRsaKey() {
- KeyPair keyPair;
- try {
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
- keyPairGenerator.initialize(2048);
- keyPair = keyPairGenerator.generateKeyPair();
- } catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- return keyPair;
- }
-
- static KeyPair generateEcKey() {
- EllipticCurve ellipticCurve = new EllipticCurve(
- new ECFieldFp(
- new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
- new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
- new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
- ECPoint ecPoint = new ECPoint(
- new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
- new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
- ECParameterSpec ecParameterSpec = new ECParameterSpec(
- ellipticCurve,
- ecPoint,
- new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
- 1);
-
- KeyPair keyPair;
- try {
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
- keyPairGenerator.initialize(ecParameterSpec);
- keyPair = keyPairGenerator.generateKeyPair();
- } catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- return keyPair;
- }
-}
-//CHECKSTYLE:ON
\ No newline at end of file
diff --git a/spring-cloud/authorization-server/src/main/resources/application.yml b/spring-cloud/authorization-server/src/main/resources/application.yml
deleted file mode 100644
index fc9942a..0000000
--- a/spring-cloud/authorization-server/src/main/resources/application.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-spring.config.import: "configserver:"
-
-spring:
- application.name: auth-server
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
-
----
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
diff --git a/spring-cloud/config-server/.gitignore b/spring-cloud/config-server/.gitignore
deleted file mode 100644
index 549e00a..0000000
--- a/spring-cloud/config-server/.gitignore
+++ /dev/null
@@ -1,33 +0,0 @@
-HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### IntelliJ IDEA ###
-.idea
-*.iws
-*.iml
-*.ipr
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
diff --git a/spring-cloud/config-server/Dockerfile b/spring-cloud/config-server/Dockerfile
deleted file mode 100644
index 516df3c..0000000
--- a/spring-cloud/config-server/Dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
-
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
-WORKDIR application
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
-
-
-EXPOSE 8888
-
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
diff --git a/spring-cloud/config-server/pom.xml b/spring-cloud/config-server/pom.xml
deleted file mode 100644
index 0327868..0000000
--- a/spring-cloud/config-server/pom.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.4.3
-
-
- io.javatab
- config-server
- 0.0.1-SNAPSHOT
- config-server
- Demo project for Spring Boot
-
- 17
- 2024.0.0
-
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.cloud
- spring-cloud-config-server
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.security
- spring-security-test
- test
-
-
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
-
- repackage
-
-
-
-
-
-
-
-
diff --git a/spring-cloud/config-server/src/main/java/io/javatab/springcloud/configserver/ConfigServerApplication.java b/spring-cloud/config-server/src/main/java/io/javatab/springcloud/configserver/ConfigServerApplication.java
deleted file mode 100644
index cf64d47..0000000
--- a/spring-cloud/config-server/src/main/java/io/javatab/springcloud/configserver/ConfigServerApplication.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.javatab.springcloud.configserver;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.config.server.EnableConfigServer;
-import org.springframework.context.ConfigurableApplicationContext;
-
-@SpringBootApplication
-@EnableConfigServer
-public class ConfigServerApplication {
-
- private static final Logger LOG = LoggerFactory.getLogger(ConfigServerApplication.class);
-
- public static void main(String[] args) {
- ConfigurableApplicationContext ctx = SpringApplication.run(ConfigServerApplication.class, args);
-
- String repoLocation = ctx.getEnvironment().getProperty("spring.cloud.config.server.native.search-locations");
- LOG.info("Serving configurations from folder: " + repoLocation);
- }
-
-}
diff --git a/spring-cloud/config-server/src/main/java/io/javatab/springcloud/configserver/SecurityConfig.java b/spring-cloud/config-server/src/main/java/io/javatab/springcloud/configserver/SecurityConfig.java
deleted file mode 100644
index bbb7210..0000000
--- a/spring-cloud/config-server/src/main/java/io/javatab/springcloud/configserver/SecurityConfig.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package io.javatab.springcloud.configserver;
-
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
-
-import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
-
-@Configuration
-@EnableWebSecurity
-public class SecurityConfig {
- protected void configure(HttpSecurity http) throws Exception {
- http
- // Disable CSRF to allow POST to /encrypt and /decrypt endpoints
- .csrf(AbstractHttpConfigurer::disable)
- .authorizeHttpRequests(expressionInterceptUrlRegistry -> {
- try {
- expressionInterceptUrlRegistry
- .requestMatchers(
- antMatcher("/config/**")
- ).permitAll()
- .anyRequest().authenticated()
- .and()
- .httpBasic(Customizer.withDefaults());
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- }
-}
-
-
diff --git a/spring-cloud/config-server/src/main/resources/application.yml b/spring-cloud/config-server/src/main/resources/application.yml
deleted file mode 100644
index 94b5f18..0000000
--- a/spring-cloud/config-server/src/main/resources/application.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-server.port: 8888
-
-spring.cloud.config.server.native.search-locations: file:${PWD}/config-repo
-
-# WARNING: Exposing all management endpoints over http should only be used during development, must be locked down in production!
-management.endpoint.health.show-details: "ALWAYS"
-management.endpoints.web.exposure.include: "*"
-
-logging:
- level:
- root: info
----
-spring.config.activate.on-profile: docker,native
-spring.cloud.config.server.native.search-locations: file:/config-repo
\ No newline at end of file
diff --git a/spring-cloud/config-server/src/test/java/io/javatab/springcloud/configserver/ConfigServerApplicationTests.java b/spring-cloud/config-server/src/test/java/io/javatab/springcloud/configserver/ConfigServerApplicationTests.java
deleted file mode 100644
index 72b1961..0000000
--- a/spring-cloud/config-server/src/test/java/io/javatab/springcloud/configserver/ConfigServerApplicationTests.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.javatab.springcloud.configserver;
-
-import org.springframework.boot.test.context.SpringBootTest;
-
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT, properties = {"spring.profiles.active=native"})
-class ConfigServerApplicationTests {
-
-}
diff --git a/spring-cloud/eureka-server/.gitignore b/spring-cloud/eureka-server/.gitignore
deleted file mode 100644
index 549e00a..0000000
--- a/spring-cloud/eureka-server/.gitignore
+++ /dev/null
@@ -1,33 +0,0 @@
-HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### IntelliJ IDEA ###
-.idea
-*.iws
-*.iml
-*.ipr
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
diff --git a/spring-cloud/eureka-server/Dockerfile b/spring-cloud/eureka-server/Dockerfile
deleted file mode 100644
index cf957b4..0000000
--- a/spring-cloud/eureka-server/Dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
-
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
-WORKDIR application
-# Copy extracted layers into the correct locations
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
-
-
-EXPOSE 8761
-
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
diff --git a/spring-cloud/eureka-server/pom.xml b/spring-cloud/eureka-server/pom.xml
deleted file mode 100644
index 3a5414e..0000000
--- a/spring-cloud/eureka-server/pom.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.4.3
-
-
- io.javatab
- eureka-server
- 0.0.1-SNAPSHOT
- eureka-server
- Demo project for Spring Boot
-
- 17
- 2024.0.0
-
-
-
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-server
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.cloud
- spring-cloud-starter-config
-
-
- org.springframework.retry
- spring-retry
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
-
- repackage
-
-
-
-
-
-
-
-
diff --git a/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/EurekaServerApplication.java b/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/EurekaServerApplication.java
deleted file mode 100644
index 23556f0..0000000
--- a/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/EurekaServerApplication.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.javatab.springcloud.eurekaserver;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
-
-@SpringBootApplication
-@EnableEurekaServer
-public class EurekaServerApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(EurekaServerApplication.class, args);
- }
-
-}
diff --git a/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/configuration/SecurityConfig.java b/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/configuration/SecurityConfig.java
deleted file mode 100644
index 7eb2c64..0000000
--- a/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/configuration/SecurityConfig.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package io.javatab.springcloud.eurekaserver.configuration;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.provisioning.InMemoryUserDetailsManager;
-import org.springframework.security.web.SecurityFilterChain;
-
-@Configuration
-@EnableWebSecurity
-public class SecurityConfig {
-
- private final String username;
- private final String password;
-
- @Autowired
- public SecurityConfig(
- @Value("${app.eureka-username}") String username,
- @Value("${app.eureka-password}") String password
- ) {
- this.username = username;
- this.password = password;
- }
-
- @Bean
- public InMemoryUserDetailsManager userDetailsService() {
- UserDetails user = User.withDefaultPasswordEncoder()
- .username(username)
- .password(password)
- .roles("USER")
- .build();
- return new InMemoryUserDetailsManager(user);
- }
-
- @Bean
- public SecurityFilterChain configure(HttpSecurity http) throws Exception {
- http
- // Disable CRCF to allow services to register themselves with Eureka
- .csrf()
- .disable()
- .authorizeRequests()
- .anyRequest().authenticated()
- .and()
- .httpBasic();
- return http.build();
- }
-}
diff --git a/spring-cloud/eureka-server/src/main/resources/application.yml b/spring-cloud/eureka-server/src/main/resources/application.yml
deleted file mode 100644
index 9654833..0000000
--- a/spring-cloud/eureka-server/src/main/resources/application.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-spring.config.import: "configserver:"
-
-spring:
- application.name: eureka-server
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
-
-logging:
- level:
- root: DEBUG
----
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
diff --git a/spring-cloud/eureka-server/src/test/java/io/javatab/springcloud/eurekaserver/EurekaServerApplicationTests.java b/spring-cloud/eureka-server/src/test/java/io/javatab/springcloud/eurekaserver/EurekaServerApplicationTests.java
deleted file mode 100644
index 40220da..0000000
--- a/spring-cloud/eureka-server/src/test/java/io/javatab/springcloud/eurekaserver/EurekaServerApplicationTests.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.javatab.springcloud.eurekaserver;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT, properties = {"spring.cloud.config.enabled=false"})
-class EurekaServerApplicationTests {
-
-}
diff --git a/spring-cloud/eureka-server/src/test/resources/application.yml b/spring-cloud/eureka-server/src/test/resources/application.yml
deleted file mode 100644
index 31628c0..0000000
--- a/spring-cloud/eureka-server/src/test/resources/application.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-spring.config.import: "configserver:"
-
-spring:
- application.name: eureka-server
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
-
----
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
diff --git a/spring-cloud/gateway-service/Dockerfile b/spring-cloud/gateway-service/Dockerfile
new file mode 100644
index 0000000..b040ee2
--- /dev/null
+++ b/spring-cloud/gateway-service/Dockerfile
@@ -0,0 +1,25 @@
+FROM eclipse-temurin:17.0.5_8-jre-focal as builder
+WORKDIR extracted
+
+ADD ./target/*.jar app.jar
+
+RUN java -Djarmode=layertools -jar app.jar extract
+
+FROM eclipse-temurin:17.0.5_8-jre-focal
+WORKDIR application
+
+# Copy the dependencies layer from the builder stage
+COPY --from=builder extracted/dependencies/ ./
+# Copy the Spring Boot loader layer from the builder stage
+COPY --from=builder extracted/spring-boot-loader/ ./
+# Copy the snapshot dependencies layer from the builder stage
+COPY --from=builder extracted/snapshot-dependencies/ ./
+# Copy the application layer from the builder stage
+COPY --from=builder extracted/application/ ./
+
+# Expose port 9000
+EXPOSE 9000
+
+# Set the entry point to launch the application
+ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
+
diff --git a/spring-cloud/gateway-service/kubernetes/deployment.yml b/spring-cloud/gateway-service/kubernetes/deployment.yml
new file mode 100644
index 0000000..983f310
--- /dev/null
+++ b/spring-cloud/gateway-service/kubernetes/deployment.yml
@@ -0,0 +1,39 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: gateway-service
+ labels:
+ app: gateway-service
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: gateway-service
+ template:
+ metadata:
+ labels:
+ app: gateway-service
+ spec:
+ containers:
+ - name: gateway-service
+ image: gateway-service
+ imagePullPolicy: IfNotPresent
+ lifecycle:
+ preStop:
+ exec:
+ command: [ "sh", "-c", "sleep 5" ]
+ ports:
+ - containerPort: 9000
+ env:
+ - name: COURSE_SERVICE_URL
+ value: http://course-service
+ - name: REVIEW_SERVICE_URL
+ value: http://review-service
+ - name: COURSE_AGGREGATE_SERVICE_URL
+ value: http://course-composite-service #there are service names in kubectl get svc
+ - name: APP_KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
+ - name: KEYCLOAK_ISSUER_URI
+ value: http://keycloak:8080/realms/course-management-realm
+ - name: KEYCLOAK_JWK_SET_URI
+ value: http://keycloak:8080/realms/course-management-realm/protocol/openid-connect/certs
\ No newline at end of file
diff --git a/spring-cloud/gateway-service/kubernetes/ingress.yml b/spring-cloud/gateway-service/kubernetes/ingress.yml
new file mode 100644
index 0000000..70b9495
--- /dev/null
+++ b/spring-cloud/gateway-service/kubernetes/ingress.yml
@@ -0,0 +1,16 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: gateway-ingress
+spec:
+ ingressClassName: nginx
+ rules:
+ - http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: gateway-service
+ port:
+ number: 80
\ No newline at end of file
diff --git a/spring-cloud/gateway-service/kubernetes/service.yml b/spring-cloud/gateway-service/kubernetes/service.yml
new file mode 100644
index 0000000..d0a6e46
--- /dev/null
+++ b/spring-cloud/gateway-service/kubernetes/service.yml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: gateway-service
+ labels:
+ app: gateway-service
+spec:
+ type: ClusterIP
+ selector:
+ app: gateway-service
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 9000
\ No newline at end of file
diff --git a/spring-cloud/gateway-service/src/main/java/com/example/springcloud/gateway/config/SecurityConfig.java b/spring-cloud/gateway-service/src/main/java/com/example/springcloud/gateway/config/SecurityConfig.java
new file mode 100644
index 0000000..3cdb2c6
--- /dev/null
+++ b/spring-cloud/gateway-service/src/main/java/com/example/springcloud/gateway/config/SecurityConfig.java
@@ -0,0 +1,88 @@
+package com.example.springcloud.gateway.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+@Configuration
+@EnableWebFluxSecurity
+public class SecurityConfig {
+
+ private String jwkSetUri;
+ public SecurityConfig(@Value("${app.jwk-set-uri}") String jwkSetUri) {
+ this.jwkSetUri = jwkSetUri;
+ }
+
+ @Bean
+ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+ http
+ .authorizeExchange(exchanges -> exchanges
+ .pathMatchers("/api/public").permitAll()
+ .anyExchange().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))
+ );
+
+ return http.build();
+ }
+
+ @Bean
+ public ReactiveJwtDecoder jwtDecoder() {
+ System.out.println("======== " + jwkSetUri);
+ return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
+ }
+
+ @Bean
+ public Converter> grantedAuthoritiesExtractor() {
+ return new Converter>() {
+ @Override
+ public Mono convert(Jwt jwt) {
+ Collection authorities = new ArrayList<>();
+
+ // Extract realm roles
+ Map realmAccess = jwt.getClaim("realm_access");
+ if (realmAccess != null && realmAccess.containsKey("roles")) {
+ List roles = (List) realmAccess.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+
+ Map resourceAccess = jwt.getClaim("resource_access");
+ if (resourceAccess != null) {
+ resourceAccess.forEach((resource, access) -> {
+ if (access instanceof Map) {
+ Map clientRoles = (Map) access;
+ if (clientRoles.containsKey("roles")) {
+ List roles = (List) clientRoles.get("roles");
+ authorities.addAll(roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
+ .toList());
+ }
+ }
+ });
+ }
+ return Mono.just(new JwtAuthenticationToken(jwt, authorities));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/gateway/.gitignore b/spring-cloud/gateway/.gitignore
deleted file mode 100644
index 549e00a..0000000
--- a/spring-cloud/gateway/.gitignore
+++ /dev/null
@@ -1,33 +0,0 @@
-HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### IntelliJ IDEA ###
-.idea
-*.iws
-*.iml
-*.ipr
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
diff --git a/spring-cloud/gateway/Dockerfile b/spring-cloud/gateway/Dockerfile
deleted file mode 100644
index 2bf8c0f..0000000
--- a/spring-cloud/gateway/Dockerfile
+++ /dev/null
@@ -1,19 +0,0 @@
-# stage 1
-# Start with a base image containing Java runtime
-FROM eclipse-temurin:17-jre-alpine as builder
-WORKDIR application
-ARG JAR_FILE=target/*.jar
-COPY ${JAR_FILE} application.jar
-RUN java -Djarmode=layertools -jar application.jar extract
-
-# the second stage of our build will copy the extracted layers
-FROM eclipse-temurin:17-jre-alpine
-WORKDIR application
-COPY --from=builder application/dependencies/ ./
-COPY --from=builder application/spring-boot-loader/ ./
-COPY --from=builder application/snapshot-dependencies/ ./
-COPY --from=builder application/application/ ./
-
-EXPOSE 8080
-
-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
\ No newline at end of file
diff --git a/spring-cloud/gateway/pom.xml b/spring-cloud/gateway/pom.xml
deleted file mode 100644
index ce5b46e..0000000
--- a/spring-cloud/gateway/pom.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.4.3
-
-
- io.javatab
- gateway
- 1.0.0
- gateway
- Demo project for Spring Boot
-
- 17
- 2024.0.0
-
-
-
- org.springframework.cloud
- spring-cloud-starter-gateway
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
-
- org.springframework.cloud
- spring-cloud-starter-netflix-eureka-client
-
-
-
- org.springframework.security
- spring-security-oauth2-resource-server
-
-
- org.springframework.security
- spring-security-oauth2-jose
-
-
-
- org.springframework.cloud
- spring-cloud-starter-config
-
-
- org.springframework.retry
- spring-retry
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
-
-
-
- org.springframework.cloud
- spring-cloud-dependencies
- ${spring-cloud.version}
- pom
- import
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
-
- repackage
-
-
-
-
-
-
-
-
diff --git a/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/GatewayApplication.java b/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/GatewayApplication.java
deleted file mode 100644
index c026ee0..0000000
--- a/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/GatewayApplication.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package io.javatab.springcloud.gateway;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.client.loadbalancer.LoadBalanced;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.web.reactive.function.client.WebClient;
-
-@SpringBootApplication
-public class GatewayApplication {
-
- private static final Logger LOG = LoggerFactory.getLogger(GatewayApplication.class);
-
- @Bean
- @LoadBalanced
- public WebClient.Builder loadBalancedWebClientBuilder() {
- return WebClient.builder();
- }
-
- public static void main(String[] args) {
- ConfigurableApplicationContext ctx = SpringApplication.run(GatewayApplication.class, args);
-
- String testdata = ctx.getEnvironment().getProperty("test1.data");
- LOG.info("testdata ====> " + testdata);
- }
-}
diff --git a/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/configuration/HealthCheckConfiguration.java b/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/configuration/HealthCheckConfiguration.java
deleted file mode 100644
index f71fcd2..0000000
--- a/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/configuration/HealthCheckConfiguration.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package io.javatab.springcloud.gateway.configuration;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
-import org.springframework.boot.actuate.health.Health;
-import org.springframework.boot.actuate.health.ReactiveHealthContributor;
-import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import static java.util.logging.Level.FINE;
-
-@Configuration
-public class HealthCheckConfiguration {
-
- private static final Logger LOG = LoggerFactory.getLogger(HealthCheckConfiguration.class);
-
- private final WebClient webClient;
-
- @Autowired
- public HealthCheckConfiguration(WebClient.Builder webClientBuilder) {
- this.webClient = webClientBuilder.build();
- }
-
- @Bean
- ReactiveHealthContributor healthCheckMicroservices() {
-
- final Map registry = new LinkedHashMap<>();
-
- registry.put("course", () -> getHealth("http://course"));
- registry.put("student", () -> getHealth("http://student"));
- registry.put("vote", () -> getHealth("http://vote"));
- registry.put("search", () -> getHealth("http://search"));
- registry.put("course-composite", () -> getHealth("http://course-composite"));
-
- return CompositeReactiveHealthContributor.fromMap(registry);
- }
-
- private Mono getHealth(String baseUrl) {
- String url = baseUrl + "/actuator/health";
- LOG.debug("Setting up a call to the Health API on URL: {}", url);
- return webClient.get().uri(url).retrieve().bodyToMono(String.class)
- .map(s -> new Health.Builder().up().build())
- .onErrorResume(ex -> Mono.just(new Health.Builder().down(ex).build()))
- .log(LOG.getName(), FINE);
- }
-}
\ No newline at end of file
diff --git a/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/configuration/SecurityConfig.java b/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/configuration/SecurityConfig.java
deleted file mode 100644
index 712e765..0000000
--- a/spring-cloud/gateway/src/main/java/io/javatab/springcloud/gateway/configuration/SecurityConfig.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package io.javatab.springcloud.gateway.configuration;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
-import org.springframework.security.config.web.server.ServerHttpSecurity;
-import org.springframework.security.web.server.SecurityWebFilterChain;
-
-@Configuration
-@EnableWebFluxSecurity
-public class SecurityConfig {
-
- private static final Logger LOG = LoggerFactory.getLogger(SecurityConfig.class);
-
- @Bean
- SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
- http
- .csrf().disable()
- .authorizeExchange()
- .pathMatchers("/actuator/**").permitAll()
- .pathMatchers("/eureka/**").permitAll()
- .pathMatchers("/oauth2/**").permitAll()
- .pathMatchers("/login/**").permitAll()
- .pathMatchers("/error/**").permitAll()
- .pathMatchers("/openapi/**").permitAll()
- .pathMatchers("/webjars/**").permitAll()
- .pathMatchers("/config/**").permitAll()
- .anyExchange().authenticated()
- .and()
- .oauth2ResourceServer()
- .jwt();
- return http.build();
- }
-
-}
\ No newline at end of file
diff --git a/spring-cloud/gateway/src/main/resources/application.yml b/spring-cloud/gateway/src/main/resources/application.yml
deleted file mode 100644
index 8b07630..0000000
--- a/spring-cloud/gateway/src/main/resources/application.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-spring.config.import: "configserver:"
-
-spring:
- application.name: gateway
- cloud.config:
- failFast: true
- retry:
- initialInterval: 3000
- multiplier: 1.3
- maxInterval: 10000
- maxAttempts: 20
- uri: http://localhost:8888
- username: ${CONFIG_SERVER_USR}
- password: ${CONFIG_SERVER_PWD}
-
----
-spring.config.activate.on-profile: docker
-
-spring.cloud.config.uri: http://config-server:8888
\ No newline at end of file
diff --git a/spring-cloud/gateway/src/test/java/io/javatab/springcloud/gateway/GatewayApplicationTests.java b/spring-cloud/gateway/src/test/java/io/javatab/springcloud/gateway/GatewayApplicationTests.java
deleted file mode 100644
index 2506a72..0000000
--- a/spring-cloud/gateway/src/test/java/io/javatab/springcloud/gateway/GatewayApplicationTests.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.javatab.springcloud.gateway;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-@SpringBootTest(
- webEnvironment = RANDOM_PORT,
- properties = {
- "spring.security.oauth2.resourceserver.jwt.issuer-uri=",
- "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=some-url",
- "spring.main.allow-bean-definition-overriding=true",
- "eureka.client.enabled=false",
- "spring.cloud.config.enabled=false"})
-class GatewayApplicationTests {
-
-}
diff --git a/spring-cloud/gateway/src/test/resources/application.yml b/spring-cloud/gateway/src/test/resources/application.yml
deleted file mode 100644
index 6a97b7f..0000000
--- a/spring-cloud/gateway/src/test/resources/application.yml
+++ /dev/null
@@ -1 +0,0 @@
-spring.security.oauth2.resourceserver.jwt.jwk-set-uri: .
\ No newline at end of file
diff --git a/student-course-registration-system.iml b/student-course-registration-system.iml
deleted file mode 100644
index 1daccae..0000000
--- a/student-course-registration-system.iml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/util/.gitattributes b/util/.gitattributes
new file mode 100644
index 0000000..3b41682
--- /dev/null
+++ b/util/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/util/.mvn/wrapper/maven-wrapper.properties b/util/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..d58dfb7
--- /dev/null
+++ b/util/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/util/mvnw b/util/mvnw
new file mode 100755
index 0000000..19529dd
--- /dev/null
+++ b/util/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/util/mvnw.cmd b/util/mvnw.cmd
new file mode 100644
index 0000000..249bdf3
--- /dev/null
+++ b/util/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/util/pom.xml b/util/pom.xml
index 3f34614..5e40ca9 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -8,12 +8,24 @@
3.4.3
- io.javatab.microservices.util
+ io.javatab.util
util
1.0.0
- jar
util
Demo project for Spring Boot
+
+
+
+
+
+
+
+
+
+
+
+
+
17
@@ -28,45 +40,23 @@
spring-boot-starter-test
test
-
- io.projectreactor
- reactor-test
- test
-
-
- io.javatab.microservices.api
- api
- 1.0.0
- compile
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- 3.4.3
-
-
- repackage
- none
-
- repackage
-
-
-
+ 3.4.3
+
+
+ repackage
+ none
+
+ repackage
+
+
+
diff --git a/util/src/main/java/io/javatab/microservices/util/UtilApplication.java b/util/src/main/java/io/javatab/microservices/util/UtilApplication.java
deleted file mode 100644
index b34e1e2..0000000
--- a/util/src/main/java/io/javatab/microservices/util/UtilApplication.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.javatab.microservices.util;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class UtilApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(UtilApplication.class, args);
- }
-
-}
diff --git a/util/src/main/java/io/javatab/microservices/util/http/GlobalControllerExceptionHandler.java b/util/src/main/java/io/javatab/microservices/util/http/GlobalControllerExceptionHandler.java
deleted file mode 100644
index 5a2e274..0000000
--- a/util/src/main/java/io/javatab/microservices/util/http/GlobalControllerExceptionHandler.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package io.javatab.microservices.util.http;
-
-import io.javatab.microservices.api.exceptions.InvalidInputException;
-import io.javatab.microservices.api.exceptions.NotFoundException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.server.reactive.ServerHttpRequest;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.ResponseStatus;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
-
-import static org.springframework.http.HttpStatus.NOT_FOUND;
-import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
-
-@RestControllerAdvice
-class GlobalControllerExceptionHandler {
-
- private static final Logger LOG = LoggerFactory.getLogger(GlobalControllerExceptionHandler.class);
-
- @ResponseStatus(NOT_FOUND)
- @ExceptionHandler(NotFoundException.class)
- public @ResponseBody HttpErrorInfo handleNotFoundExceptions(
- ServerHttpRequest request, NotFoundException ex) {
-
- return createHttpErrorInfo(NOT_FOUND, request, ex);
- }
-
- @ResponseStatus(UNPROCESSABLE_ENTITY)
- @ExceptionHandler(InvalidInputException.class)
- public @ResponseBody HttpErrorInfo handleInvalidInputException(
- ServerHttpRequest request, InvalidInputException ex) {
-
- return createHttpErrorInfo(UNPROCESSABLE_ENTITY, request, ex);
- }
-
- private HttpErrorInfo createHttpErrorInfo(
- HttpStatus httpStatus, ServerHttpRequest request, Exception ex) {
-
- final String path = request.getPath().pathWithinApplication().value();
- final String message = ex.getMessage();
-
- LOG.debug("Returning HTTP status: {} for path: {}, message: {}", httpStatus, path, message);
- return new HttpErrorInfo(httpStatus, path, message);
- }
-}
diff --git a/util/src/main/java/io/javatab/microservices/util/http/HttpErrorInfo.java b/util/src/main/java/io/javatab/microservices/util/http/HttpErrorInfo.java
deleted file mode 100644
index 9720bc8..0000000
--- a/util/src/main/java/io/javatab/microservices/util/http/HttpErrorInfo.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package io.javatab.microservices.util.http;
-
-import org.springframework.http.HttpStatus;
-
-import java.time.ZonedDateTime;
-
-public class HttpErrorInfo {
- private final ZonedDateTime timestamp;
- private final String path;
- private final HttpStatus httpStatus;
- private final String message;
-
- public HttpErrorInfo() {
- timestamp = null;
- this.httpStatus = null;
- this.path = null;
- this.message = null;
- }
-
- public HttpErrorInfo(HttpStatus httpStatus, String path, String message) {
- timestamp = ZonedDateTime.now();
- this.httpStatus = httpStatus;
- this.path = path;
- this.message = message;
- }
-
- public ZonedDateTime getTimestamp() {
- return timestamp;
- }
-
- public String getPath() {
- return path;
- }
-
- public int getStatus() {
- return httpStatus.value();
- }
-
- public String getError() {
- return httpStatus.getReasonPhrase();
- }
-
- public String getMessage() {
- return message;
- }
-}
diff --git a/util/src/main/java/io/javatab/microservices/util/http/ServiceUtil.java b/util/src/main/java/io/javatab/microservices/util/http/ServiceUtil.java
deleted file mode 100644
index 58bc640..0000000
--- a/util/src/main/java/io/javatab/microservices/util/http/ServiceUtil.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package io.javatab.microservices.util.http;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-@Component
-public class ServiceUtil {
- private static final Logger LOG = LoggerFactory.getLogger(ServiceUtil.class);
-
- private final String port;
-
- private String serviceAddress = null;
-
- @Autowired
- public ServiceUtil(@Value("${server.port}") String port) {
-
- this.port = port;
- }
-
- public String getServiceAddress() {
- if (serviceAddress == null) {
- serviceAddress = findMyHostname() + "/" + findMyIpAddress() + ":" + port;
- }
- return serviceAddress;
- }
-
- private String findMyHostname() {
- try {
- return InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- return "unknown host name";
- }
- }
-
- private String findMyIpAddress() {
- try {
- return InetAddress.getLocalHost().getHostAddress();
- } catch (UnknownHostException e) {
- return "unknown IP address";
- }
- }
-}
diff --git a/util/src/main/java/io/javatab/util/http/NetworkUtility.java b/util/src/main/java/io/javatab/util/http/NetworkUtility.java
new file mode 100644
index 0000000..ef88919
--- /dev/null
+++ b/util/src/main/java/io/javatab/util/http/NetworkUtility.java
@@ -0,0 +1,43 @@
+package io.javatab.util.http;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+@Component
+public class NetworkUtility {
+
+ private final String port;
+
+ private String serviceAddress = null;
+
+ public NetworkUtility(@Value("${server.port}") String port) {
+ this.port = port;
+ }
+
+ public String getServiceAddress() {
+ if (serviceAddress == null) {
+ serviceAddress = findMyHostname() + "/" + findMyIpAddress() + ":" + port;
+ }
+ return serviceAddress;
+ }
+
+ private String findMyHostname() {
+ try {
+ return InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ return "unknown host name";
+ }
+ }
+
+ private String findMyIpAddress() {
+ try {
+ return InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException e) {
+ return "unknown IP address";
+ }
+ }
+
+}
diff --git a/util/src/main/resources/application.properties b/util/src/main/resources/application.properties
deleted file mode 100644
index 1c421cf..0000000
--- a/util/src/main/resources/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-server.port=0
\ No newline at end of file
diff --git a/util/src/main/resources/application.yml b/util/src/main/resources/application.yml
new file mode 100644
index 0000000..18bb645
--- /dev/null
+++ b/util/src/main/resources/application.yml
@@ -0,0 +1,3 @@
+spring:
+ application:
+ name: util
diff --git a/util/src/test/java/io/javatab/microservices/util/UtilApplicationTests.java b/util/src/test/java/io/javatab/microservices/util/UtilApplicationTests.java
deleted file mode 100644
index 41743ce..0000000
--- a/util/src/test/java/io/javatab/microservices/util/UtilApplicationTests.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.javatab.microservices.util;
-
-import org.junit.jupiter.api.Test;
-class UtilApplicationTests {
-
- @Test
- void testFlux() {
-
- }
-}