diff --git a/.DS_Store b/.DS_Store index 2f9f1cb..5008ddf 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 461627c..c2065bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,37 @@ -*.* -/Status +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ 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 deleted file mode 100644 index a02af30..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 215261b..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index e20a4a2..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/jpa-buddy.xml b/.idea/jpa-buddy.xml deleted file mode 100644 index d08f400..0000000 --- a/.idea/jpa-buddy.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 7befe12..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ 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 deleted file mode 100644 index b7cb93e..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -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 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 -``` -![Maven Build](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/build.png?raw=true) - -- Locate the docker directory from the root directory and run docker compose to startup the containers -``` - cd docker && docker compose up --build -``` -![Docker Compose Build](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/docker-compose.png?raw=true) - -- Check if all our services are running and healthy -``` - docker ps -``` -![Docker PS](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/docker-ps.png?raw=true) - -- 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 -``` -![Eureka](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/eureka.png?raw=true) - -- 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. -![Gateway Routes](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/gateway-routes.png?raw=true) - - -- Coming to swagger/openapi specs, here is the address to access them - http://localhost:8443/openapi/swagger-ui.html -![Swagger OpenApi Specs](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/swagger-openapi.png?raw=true) - -- 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" -``` -![Swagger OpenApi Specs](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/oauth-endpoint.png?raw=true) -![Swagger OpenApi Specs](https://github.com/Nasruddin/spring-boot-based-microservices/blob/master/images/jwt-io.png?raw=true) - -## 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/api/.gitattributes b/api/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/api/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/api/.gitignore b/api/.gitignore index 549e00a..c2065bc 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -1,8 +1,9 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ ### STS ### .apt_generated @@ -12,12 +13,18 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ ### NetBeans ### /nbproject/private/ @@ -25,9 +32,6 @@ target/ /dist/ /nbdist/ /.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..1b1a3b1 --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.example.api' +version = '1.0.0-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +ext { + springBootVersion = '3.4.3' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}") + + implementation 'org.springdoc:springdoc-openapi-starter-common:2.0.4' + + implementation 'org.springframework.boot:spring-boot-starter-webflux' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/api/gradle/wrapper/gradle-wrapper.jar b/api/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/api/gradle/wrapper/gradle-wrapper.jar differ diff --git a/api/gradle/wrapper/gradle-wrapper.properties b/api/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e18bc25 --- /dev/null +++ b/api/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/api/gradlew b/api/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/api/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/api/gradlew.bat b/api/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/api/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega 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/settings.gradle b/api/settings.gradle new file mode 100644 index 0000000..5cd7dd3 --- /dev/null +++ b/api/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'api' diff --git a/api/src/main/java/com/example/api/composite/ProductAggregate.java b/api/src/main/java/com/example/api/composite/ProductAggregate.java new file mode 100644 index 0000000..3eca98f --- /dev/null +++ b/api/src/main/java/com/example/api/composite/ProductAggregate.java @@ -0,0 +1,61 @@ +package com.example.api.composite; + +import java.util.List; + +public class ProductAggregate { + private final int productId; + private final String name; + private final int weight; + private final List recommendations; + private final List reviews; + private final ServiceAddresses serviceAddresses; + + public ProductAggregate() { + productId = 0; + name = null; + weight = 0; + recommendations = null; + reviews = null; + serviceAddresses = null; + } + + public ProductAggregate( + int productId, + String name, + int weight, + List recommendations, + List reviews, + ServiceAddresses serviceAddresses) { + + this.productId = productId; + this.name = name; + this.weight = weight; + this.recommendations = recommendations; + this.reviews = reviews; + this.serviceAddresses = serviceAddresses; + } + + public int getProductId() { + return productId; + } + + public String getName() { + return name; + } + + public int getWeight() { + return weight; + } + + public List getRecommendations() { + return recommendations; + } + + public List getReviews() { + return reviews; + } + + public ServiceAddresses getServiceAddresses() { + return serviceAddresses; + } + } \ No newline at end of file diff --git a/api/src/main/java/com/example/api/composite/ProductCompositeService.java b/api/src/main/java/com/example/api/composite/ProductCompositeService.java new file mode 100644 index 0000000..9a2ca36 --- /dev/null +++ b/api/src/main/java/com/example/api/composite/ProductCompositeService.java @@ -0,0 +1,67 @@ +package com.example.api.composite; + +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.tags.Tag; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "ProductComposite", description = "REST API for composite product information.") +public interface ProductCompositeService { + + /** + * Sample usage, see below. + * + * curl -X POST $HOST:$PORT/product-composite \ + * -H "Content-Type: application/json" --data \ + * '{"productId":123,"name":"product 123","weight":123}' + * + * @param body A JSON representation of the new composite product + */ + @Operation( + summary = "${api.product-composite.create-composite-product.description}", + description = "${api.product-composite.create-composite-product.notes}") + @ApiResponses(value = { + @ApiResponse(responseCode = "400", description = "${api.responseCodes.badRequest.description}"), + @ApiResponse(responseCode = "422", description = "${api.responseCodes.unprocessableEntity.description}") + }) + @PostMapping( + value = "/product-composite", + consumes = "application/json") + void createProduct(@RequestBody ProductAggregate body); + + /** + * Sample usage: "curl $HOST:$PORT/product-composite/1". + * + * @param productId Id of the product + * @return the composite product info, if found, else null + */ + @Operation( + summary = "${api.product-composite.get-composite-product.description}", + description = "${api.product-composite.get-composite-product.notes}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "${api.responseCodes.ok.description}"), + @ApiResponse(responseCode = "400", description = "${api.responseCodes.badRequest.description}"), + @ApiResponse(responseCode = "404", description = "${api.responseCodes.notFound.description}"), + @ApiResponse(responseCode = "422", description = "${api.responseCodes.unprocessableEntity.description}") + }) + @GetMapping( + value = "/product-composite/{productId}", + produces = "application/json") + ProductAggregate getProduct(@PathVariable int productId); + + /** + * Sample usage: "curl -X DELETE $HOST:$PORT/product-composite/1". + * + * @param productId Id of the product + */ + @Operation( + summary = "${api.product-composite.delete-composite-product.description}", + description = "${api.product-composite.delete-composite-product.notes}") + @ApiResponses(value = { + @ApiResponse(responseCode = "400", description = "${api.responseCodes.badRequest.description}"), + @ApiResponse(responseCode = "422", description = "${api.responseCodes.unprocessableEntity.description}") + }) + @DeleteMapping(value = "/product-composite/{productId}") + void deleteProduct(@PathVariable int productId); +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/composite/RecommendationSummary.java b/api/src/main/java/com/example/api/composite/RecommendationSummary.java new file mode 100644 index 0000000..3375805 --- /dev/null +++ b/api/src/main/java/com/example/api/composite/RecommendationSummary.java @@ -0,0 +1,39 @@ +package com.example.api.composite; + +public class RecommendationSummary { + + private final int recommendationId; + private final String author; + private final int rate; + private final String content; + + public RecommendationSummary() { + this.recommendationId = 0; + this.author = null; + this.rate = 0; + this.content = null; + } + + public RecommendationSummary(int recommendationId, String author, int rate, String content) { + this.recommendationId = recommendationId; + this.author = author; + this.rate = rate; + this.content = content; + } + + public int getRecommendationId() { + return recommendationId; + } + + public String getAuthor() { + return author; + } + + public int getRate() { + return rate; + } + + public String getContent() { + return content; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/composite/ReviewSummary.java b/api/src/main/java/com/example/api/composite/ReviewSummary.java new file mode 100644 index 0000000..0c8c294 --- /dev/null +++ b/api/src/main/java/com/example/api/composite/ReviewSummary.java @@ -0,0 +1,39 @@ +package com.example.api.composite; + +public class ReviewSummary { + + private final int reviewId; + private final String author; + private final String subject; + private final String content; + + public ReviewSummary() { + this.reviewId = 0; + this.author = null; + this.subject = null; + this.content = null; + } + + public ReviewSummary(int reviewId, String author, String subject, String content) { + this.reviewId = reviewId; + this.author = author; + this.subject = subject; + this.content = content; + } + + public int getReviewId() { + return reviewId; + } + + public String getAuthor() { + return author; + } + + public String getSubject() { + return subject; + } + + public String getContent() { + return content; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/composite/ServiceAddresses.java b/api/src/main/java/com/example/api/composite/ServiceAddresses.java new file mode 100644 index 0000000..330423e --- /dev/null +++ b/api/src/main/java/com/example/api/composite/ServiceAddresses.java @@ -0,0 +1,43 @@ +package com.example.api.composite; + +public class ServiceAddresses { + private final String cmp; + private final String pro; + private final String rev; + private final String rec; + + public ServiceAddresses() { + cmp = null; + pro = null; + rev = null; + rec = null; + } + + public ServiceAddresses( + String compositeAddress, + String productAddress, + String reviewAddress, + String recommendationAddress) { + + this.cmp = compositeAddress; + this.pro = productAddress; + this.rev = reviewAddress; + this.rec = recommendationAddress; + } + + public String getCmp() { + return cmp; + } + + public String getPro() { + return pro; + } + + public String getRev() { + return rev; + } + + public String getRec() { + return rec; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/core/product/Product.java b/api/src/main/java/com/example/api/core/product/Product.java new file mode 100644 index 0000000..2193439 --- /dev/null +++ b/api/src/main/java/com/example/api/core/product/Product.java @@ -0,0 +1,54 @@ +package com.example.api.core.product; + +public class Product { + private int productId; + private String name; + private int weight; + private String serviceAddress; + + public Product() { + productId = 0; + name = null; + weight = 0; + serviceAddress = null; + } + + public Product(int productId, String name, int weight, String serviceAddress) { + this.productId = productId; + this.name = name; + this.weight = weight; + this.serviceAddress = serviceAddress; + } + + public int getProductId() { + return productId; + } + + public String getName() { + return name; + } + + public int getWeight() { + return weight; + } + + public String getServiceAddress() { + return serviceAddress; + } + + public void setProductId(int productId) { + this.productId = productId; + } + + public void setName(String name) { + this.name = name; + } + + public void setWeight(int weight) { + this.weight = weight; + } + + public void setServiceAddress(String serviceAddress) { + this.serviceAddress = serviceAddress; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/core/product/ProductService.java b/api/src/main/java/com/example/api/core/product/ProductService.java new file mode 100644 index 0000000..a8422ea --- /dev/null +++ b/api/src/main/java/com/example/api/core/product/ProductService.java @@ -0,0 +1,41 @@ +package com.example.api.core.product; + +import org.springframework.web.bind.annotation.*; + +public interface ProductService { + + /** + * Sample usage, see below. + * + * curl -X POST $HOST:$PORT/product \ + * -H "Content-Type: application/json" --data \ + * '{"productId":123,"name":"product 123","weight":123}' + * + * @param body A JSON representation of the new product + * @return A JSON representation of the newly created product + */ + @PostMapping( + value = "/product", + consumes = "application/json", + produces = "application/json") + Product createProduct(@RequestBody Product body); + + /** + * Sample usage: "curl $HOST:$PORT/product/1". + * + * @param productId Id of the product + * @return the product, if found, else null + */ + @GetMapping( + value = "/product/{productId}", + produces = "application/json") + Product getProduct(@PathVariable int productId); + + /** + * Sample usage: "curl -X DELETE $HOST:$PORT/product/1". + * + * @param productId Id of the product + */ + @DeleteMapping(value = "/product/{productId}") + void deleteProduct(@PathVariable int productId); +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/core/recommendation/Recommendation.java b/api/src/main/java/com/example/api/core/recommendation/Recommendation.java new file mode 100644 index 0000000..7d31ef7 --- /dev/null +++ b/api/src/main/java/com/example/api/core/recommendation/Recommendation.java @@ -0,0 +1,84 @@ +package com.example.api.core.recommendation; + + +public class Recommendation { + private int productId; + private int recommendationId; + private String author; + private int rate; + private String content; + private String serviceAddress; + + public Recommendation() { + productId = 0; + recommendationId = 0; + author = null; + rate = 0; + content = null; + serviceAddress = null; + } + + public Recommendation( + int productId, + int recommendationId, + String author, + int rate, + String content, + String serviceAddress) { + + this.productId = productId; + this.recommendationId = recommendationId; + this.author = author; + this.rate = rate; + this.content = content; + this.serviceAddress = serviceAddress; + } + + public int getProductId() { + return productId; + } + + public int getRecommendationId() { + return recommendationId; + } + + public String getAuthor() { + return author; + } + + public int getRate() { + return rate; + } + + public String getContent() { + return content; + } + + public String getServiceAddress() { + return serviceAddress; + } + + public void setProductId(int productId) { + this.productId = productId; + } + + public void setRecommendationId(int recommendationId) { + this.recommendationId = recommendationId; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setRate(int rate) { + this.rate = rate; + } + + public void setContent(String content) { + this.content = content; + } + + public void setServiceAddress(String serviceAddress) { + this.serviceAddress = serviceAddress; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/core/recommendation/RecommendationService.java b/api/src/main/java/com/example/api/core/recommendation/RecommendationService.java new file mode 100644 index 0000000..79c3ecd --- /dev/null +++ b/api/src/main/java/com/example/api/core/recommendation/RecommendationService.java @@ -0,0 +1,44 @@ +package com.example.api.core.recommendation; + +import java.util.List; + +import org.springframework.web.bind.annotation.*; + +public interface RecommendationService { + + /** + * Sample usage, see below. + * + * curl -X POST $HOST:$PORT/recommendation \ + * -H "Content-Type: application/json" --data \ + * '{"productId":123,"recommendationId":456,"author":"me","rate":5,"content":"yada, yada, yada"}' + * + * @param body A JSON representation of the new recommendation + * @return A JSON representation of the newly created recommendation + */ + @PostMapping( + value = "/recommendation", + consumes = "application/json", + produces = "application/json") + Recommendation createRecommendation(@RequestBody Recommendation body); + + /** + * Sample usage: "curl $HOST:$PORT/recommendation?productId=1". + * + * @param productId Id of the product + * @return the recommendations of the product + */ + @GetMapping( + value = "/recommendation", + produces = "application/json") + List getRecommendations( + @RequestParam(value = "productId", required = true) int productId); + + /** + * Sample usage: "curl -X DELETE $HOST:$PORT/recommendation?productId=1". + * + * @param productId Id of the product + */ + @DeleteMapping(value = "/recommendation") + void deleteRecommendations(@RequestParam(value = "productId", required = true) int productId); +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/core/review/Review.java b/api/src/main/java/com/example/api/core/review/Review.java new file mode 100644 index 0000000..0673aed --- /dev/null +++ b/api/src/main/java/com/example/api/core/review/Review.java @@ -0,0 +1,83 @@ +package com.example.api.core.review; + +public class Review { + private int productId; + private int reviewId; + private String author; + private String subject; + private String content; + private String serviceAddress; + + public Review() { + productId = 0; + reviewId = 0; + author = null; + subject = null; + content = null; + serviceAddress = null; + } + + public Review( + int productId, + int reviewId, + String author, + String subject, + String content, + String serviceAddress) { + + this.productId = productId; + this.reviewId = reviewId; + this.author = author; + this.subject = subject; + this.content = content; + this.serviceAddress = serviceAddress; + } + + public int getProductId() { + return productId; + } + + public int getReviewId() { + return reviewId; + } + + public String getAuthor() { + return author; + } + + public String getSubject() { + return subject; + } + + public String getContent() { + return content; + } + + public String getServiceAddress() { + return serviceAddress; + } + + public void setProductId(int productId) { + this.productId = productId; + } + + public void setReviewId(int reviewId) { + this.reviewId = reviewId; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public void setContent(String content) { + this.content = content; + } + + public void setServiceAddress(String serviceAddress) { + this.serviceAddress = serviceAddress; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/example/api/core/review/ReviewService.java b/api/src/main/java/com/example/api/core/review/ReviewService.java new file mode 100644 index 0000000..dc0190c --- /dev/null +++ b/api/src/main/java/com/example/api/core/review/ReviewService.java @@ -0,0 +1,43 @@ +package com.example.api.core.review; + +import java.util.List; + +import org.springframework.web.bind.annotation.*; + +public interface ReviewService { + + /** + * Sample usage, see below. + * + * curl -X POST $HOST:$PORT/review \ + * -H "Content-Type: application/json" --data \ + * '{"productId":123,"reviewId":456,"author":"me","subject":"yada, yada, yada","content":"yada, yada, yada"}' + * + * @param body A JSON representation of the new review + * @return A JSON representation of the newly created review + */ + @PostMapping( + value = "/review", + consumes = "application/json", + produces = "application/json") + Review createReview(@RequestBody Review body); + + /** + * Sample usage: "curl $HOST:$PORT/review?productId=1". + * + * @param productId Id of the product + * @return the reviews of the product + */ + @GetMapping( + value = "/review", + produces = "application/json") + List getReviews(@RequestParam(value = "productId", required = true) int productId); + + /** + * Sample usage: "curl -X DELETE $HOST:$PORT/review?productId=1". + * + * @param productId Id of the product + */ + @DeleteMapping(value = "/review") + void deleteReviews(@RequestParam(value = "productId", required = true) int productId); +} \ No newline at end of file diff --git a/api/src/main/java/io/javatab/microservices/api/exceptions/InvalidInputException.java b/api/src/main/java/com/example/api/exceptions/InvalidInputException.java similarity index 87% rename from api/src/main/java/io/javatab/microservices/api/exceptions/InvalidInputException.java rename to api/src/main/java/com/example/api/exceptions/InvalidInputException.java index a6ad110..210541e 100644 --- a/api/src/main/java/io/javatab/microservices/api/exceptions/InvalidInputException.java +++ b/api/src/main/java/com/example/api/exceptions/InvalidInputException.java @@ -1,4 +1,4 @@ -package io.javatab.microservices.api.exceptions; +package com.example.api.exceptions; public class InvalidInputException extends RuntimeException { public InvalidInputException() {} @@ -14,4 +14,4 @@ public InvalidInputException(String message, Throwable cause) { public InvalidInputException(Throwable cause) { super(cause); } -} +} \ No newline at end of file diff --git a/api/src/main/java/io/javatab/microservices/api/exceptions/NotFoundException.java b/api/src/main/java/com/example/api/exceptions/NotFoundException.java similarity index 87% rename from api/src/main/java/io/javatab/microservices/api/exceptions/NotFoundException.java rename to api/src/main/java/com/example/api/exceptions/NotFoundException.java index f459c0b..73eb0c5 100644 --- a/api/src/main/java/io/javatab/microservices/api/exceptions/NotFoundException.java +++ b/api/src/main/java/com/example/api/exceptions/NotFoundException.java @@ -1,4 +1,5 @@ -package io.javatab.microservices.api.exceptions; +package com.example.api.exceptions; + public class NotFoundException extends RuntimeException { public NotFoundException() {} @@ -14,4 +15,4 @@ public NotFoundException(String message, Throwable cause) { public NotFoundException(Throwable cause) { super(cause); } -} +} \ No newline at end of file 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/resources/application.properties b/api/src/main/resources/application.properties index 8b13789..bbe3a4c 100644 --- a/api/src/main/resources/application.properties +++ b/api/src/main/resources/application.properties @@ -1 +1 @@ - +spring.application.name=api diff --git a/api/src/test/java/io/javatab/microservices/api/ApiApplicationTests.java b/api/src/test/java/com/example/api/ApiApplicationTests.java similarity index 74% rename from api/src/test/java/io/javatab/microservices/api/ApiApplicationTests.java rename to api/src/test/java/com/example/api/ApiApplicationTests.java index 5401095..35fd201 100644 --- a/api/src/test/java/io/javatab/microservices/api/ApiApplicationTests.java +++ b/api/src/test/java/com/example/api/ApiApplicationTests.java @@ -1,9 +1,9 @@ -package io.javatab.microservices.api; +package com.example.api; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest() +@SpringBootTest class ApiApplicationTests { @Test 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..215f7c0 --- /dev/null +++ b/create-project.sh @@ -0,0 +1,52 @@ +mkdir microservices +cd microservices + +spring init \ +--boot-version=3.4.3 \ +--type=gradle-project \ +--java-version=17 \ +--packaging=jar \ +--name=product-service \ +--package-name=com.example.microservices.core.product \ +--groupId=com.example.microservices.core.product \ +--dependencies=actuator,webflux \ +--version=1.0.0-SNAPSHOT \ +product-service + +spring init \ +--boot-version=3.4.3 \ +--type=gradle-project \ +--java-version=17 \ +--packaging=jar \ +--name=review-service \ +--package-name=com.example.microservices.core.review \ +--groupId=com.example.microservices.core.review \ +--dependencies=actuator,webflux \ +--version=1.0.0-SNAPSHOT \ +review-service + +spring init \ +--boot-version=3.4.3 \ +--type=gradle-project \ +--java-version=17 \ +--packaging=jar \ +--name=recommendation-service \ +--package-name=com.example.microservices.core.recommendation \ +--groupId=com.example.microservices.core.recommendation \ +--dependencies=actuator,webflux \ +--version=1.0.0-SNAPSHOT \ +recommendation-service + +spring init \ +--boot-version=3.4.3 \ +--type=gradle-project \ +--java-version=17 \ +--packaging=jar \ +--name=product-composite-service \ +--package-name=com.example.microservices.composite.product \ +--groupId=com.example.microservices.composite.product \ +--dependencies=actuator,webflux \ +--version=1.0.0-SNAPSHOT \ +product-composite-service + +cd .. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..76a6067 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +services: + product: + build: microservices/product-service + mem_limit: 512m + environment: + - SPRING_PROFILES_ACTIVE=docker + depends_on: + mongodb: + condition: service_healthy + + + + recommendation: + build: microservices/recommendation-service + mem_limit: 512m + environment: + - SPRING_PROFILES_ACTIVE=docker + depends_on: + mongodb: + condition: service_healthy + + + review: + build: microservices/review-service + mem_limit: 512m + environment: + - SPRING_PROFILES_ACTIVE=docker + depends_on: + mysql: + condition: service_healthy + + product-composite: + build: microservices/product-composite-service + mem_limit: 512m + ports: + - "8080:8080" + environment: + - SPRING_PROFILES_ACTIVE=docker + mongodb: + image: mongo:6.0.4 + mem_limit: 512m + ports: + - "27017:27017" + command: mongod + healthcheck: + test: "mongostat -n 1" + + mysql: + image: mysql:8.0.32 + mem_limit: 512m + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD=rootpwd + - MYSQL_DATABASE=review-db + - MYSQL_USER=user + - MYSQL_PASSWORD=pwd + healthcheck: + test: "/usr/bin/mysql --user=user --password=pwd --execute \"SHOW DATABASES;\"" + interval: 5s + timeout: 2s + + eureka: + build: spring-cloud/eureka-server + mem_limit: 512m + ports: + - "8761:8761" + 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.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/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e18bc25 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega 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/microservices/course-composite-service/Dockerfile b/microservices/course-composite-service/Dockerfile deleted file mode 100644 index f5249bc..0000000 --- a/microservices/course-composite-service/Dockerfile +++ /dev/null @@ -1,20 +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/course-composite-service/pom.xml b/microservices/course-composite-service/pom.xml deleted file mode 100644 index b87e1bf..0000000 --- a/microservices/course-composite-service/pom.xml +++ /dev/null @@ -1,135 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.4.3 - - - io.javatab.microservices.composite.course - course-composite-service - 1.0.0 - course-composite-service - Demo project for Spring Boot - - 17 - 2024.0.0 - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-webflux - - - - org.springdoc - springdoc-openapi-starter-webflux-ui - 2.0.4 - - - org.springframework.boot - spring-boot-starter-security - - - - 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 - - - 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.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/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 deleted file mode 100644 index 4e0654d..0000000 --- a/microservices/course-composite-service/src/main/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplication.java +++ /dev/null @@ -1,26 +0,0 @@ -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) -public class CourseCompositeServiceApplication { - - public static void main(String[] args) { - SpringApplication.run(CourseCompositeServiceApplication.class, args); - } - -} 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/resources/application.yml b/microservices/course-composite-service/src/main/resources/application.yml deleted file mode 100644 index e82e6c9..0000000 --- a/microservices/course-composite-service/src/main/resources/application.yml +++ /dev/null @@ -1,19 +0,0 @@ -spring.config.import: "configserver:" - -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} - ---- -spring.config.activate.on-profile: docker - -spring.cloud.config.uri: http://config-server:8888 \ 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 deleted file mode 100644 index 67f9319..0000000 --- a/microservices/course-composite-service/src/test/java/io/javatab/microservices/composite/course/CourseCompositeServiceApplicationTests.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.javatab.microservices.composite.course; - -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"}) -class CourseCompositeServiceApplicationTests { - -} 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/Dockerfile b/microservices/course-service/Dockerfile deleted file mode 100644 index f2dbab8..0000000 --- a/microservices/course-service/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# 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 - -# 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/course-service/pom.xml b/microservices/course-service/pom.xml deleted file mode 100644 index a7a2c82..0000000 --- a/microservices/course-service/pom.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.4.3 - - - io.javatab.microservices.core.course - course-service - 1.0.0 - course-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-mongodb - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - org.mapstruct - mapstruct - 1.3.1.Final - - - 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 - 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 deleted file mode 100644 index 3421539..0000000 --- a/microservices/course-service/src/main/java/io/javatab/microservices/core/course/CourseServiceApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.javatab.microservices.core.course; - -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: -public class CourseServiceApplication { - - 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); - } - -} 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/resources/application.yml b/microservices/course-service/src/main/resources/application.yml deleted file mode 100644 index 968bed7..0000000 --- a/microservices/course-service/src/main/resources/application.yml +++ /dev/null @@ -1,19 +0,0 @@ -spring.config.import: "configserver:" - -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} - ---- -spring.config.activate.on-profile: docker - -spring.cloud.config.uri: http://config-server:8888 \ No newline at end of file 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/product-composite-service/.gitattributes b/microservices/product-composite-service/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/microservices/product-composite-service/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/microservices/course-composite-service/.gitignore b/microservices/product-composite-service/.gitignore similarity index 69% rename from microservices/course-composite-service/.gitignore rename to microservices/product-composite-service/.gitignore index 549e00a..c2065bc 100644 --- a/microservices/course-composite-service/.gitignore +++ b/microservices/product-composite-service/.gitignore @@ -1,8 +1,9 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ ### STS ### .apt_generated @@ -12,12 +13,18 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ ### NetBeans ### /nbproject/private/ @@ -25,9 +32,6 @@ target/ /dist/ /nbdist/ /.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/microservices/product-composite-service/Dockerfile b/microservices/product-composite-service/Dockerfile new file mode 100644 index 0000000..75b67ac --- /dev/null +++ b/microservices/product-composite-service/Dockerfile @@ -0,0 +1,25 @@ +FROM eclipse-temurin:17.0.5_8-jre-focal as builder +WORKDIR extracted + +ADD ./build/libs/*.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/product-composite-service/build.gradle b/microservices/product-composite-service/build.gradle new file mode 100644 index 0000000..96a9a87 --- /dev/null +++ b/microservices/product-composite-service/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.3' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.example.microservices.composite.product' +version = '1.0.0-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':api') + implementation project(':util') + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.8.5' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.projectreactor:reactor-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/microservices/product-composite-service/settings.gradle b/microservices/product-composite-service/settings.gradle new file mode 100644 index 0000000..f06d086 --- /dev/null +++ b/microservices/product-composite-service/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'product-composite-service' diff --git a/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/ProductCompositeServiceApplication.java b/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/ProductCompositeServiceApplication.java new file mode 100644 index 0000000..9a24894 --- /dev/null +++ b/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/ProductCompositeServiceApplication.java @@ -0,0 +1,60 @@ +package com.example.microservices.composite.product; + +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.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.web.client.RestTemplate; + +@SpringBootApplication +@ComponentScan("com.example") +public class ProductCompositeServiceApplication { + + @Value("${api.common.version}") String apiVersion; + @Value("${api.common.title}") String apiTitle; + @Value("${api.common.description}") String apiDescription; + @Value("${api.common.termsOfService}") String apiTermsOfService; + @Value("${api.common.license}") String apiLicense; + @Value("${api.common.licenseUrl}") String apiLicenseUrl; + @Value("${api.common.externalDocDesc}") String apiExternalDocDesc; + @Value("${api.common.externalDocUrl}") String apiExternalDocUrl; + @Value("${api.common.contact.name}") String apiContactName; + @Value("${api.common.contact.url}") String apiContactUrl; + @Value("${api.common.contact.email}") String apiContactEmail; + + @Bean + RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public OpenAPI getOpenApiDocumentation() { + return new OpenAPI() + .info(new Info().title(apiTitle) + .description(apiDescription) + .version(apiVersion) + .contact(new Contact() + .name(apiContactName) + .url(apiContactUrl) + .email(apiContactEmail)) + .termsOfService(apiTermsOfService) + .license(new License() + .name(apiLicense) + .url(apiLicenseUrl))) + .externalDocs(new ExternalDocumentation() + .description(apiExternalDocDesc) + .url(apiExternalDocUrl)); + +} + + public static void main(String[] args) { + SpringApplication.run(ProductCompositeServiceApplication.class, args); + } + +} diff --git a/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/service/ProductCompositeIntegration.java b/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/service/ProductCompositeIntegration.java new file mode 100644 index 0000000..1611915 --- /dev/null +++ b/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/service/ProductCompositeIntegration.java @@ -0,0 +1,232 @@ +package com.example.microservices.composite.product.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +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.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import com.example.api.core.product.Product; +import com.example.api.core.product.ProductService; +import com.example.api.core.recommendation.Recommendation; +import com.example.api.core.recommendation.RecommendationService; +import com.example.api.core.review.Review; +import com.example.api.core.review.ReviewService; +import com.example.api.exceptions.InvalidInputException; +import com.example.api.exceptions.NotFoundException; +import com.example.util.http.HttpErrorInfo; + +import static org.springframework.http.HttpMethod.GET; + + +@Component +public class ProductCompositeIntegration implements ProductService, RecommendationService, ReviewService { + + private static final Logger LOG = LoggerFactory.getLogger(ProductCompositeIntegration.class); + + private final RestTemplate restTemplate; + private final ObjectMapper mapper; + + private final String productServiceUrl; + private final String recommendationServiceUrl; + private final String reviewServiceUrl; + + @Autowired + public ProductCompositeIntegration( + RestTemplate restTemplate, + ObjectMapper mapper, + @Value("${app.product-service.host}") String productServiceHost, + @Value("${app.product-service.port}") int productServicePort, + @Value("${app.recommendation-service.host}") String recommendationServiceHost, + @Value("${app.recommendation-service.port}") int recommendationServicePort, + @Value("${app.review-service.host}") String reviewServiceHost, + @Value("${app.review-service.port}") int reviewServicePort) { + + this.restTemplate = restTemplate; + this.mapper = mapper; + + productServiceUrl = "http://" + productServiceHost + ":" + productServicePort + "/product"; + recommendationServiceUrl = "http://" + recommendationServiceHost + ":" + recommendationServicePort + "/recommendation"; + reviewServiceUrl = "http://" + reviewServiceHost + ":" + reviewServicePort + "/review"; + } + + @Override + public Product createProduct(Product body) { + + try { + String url = productServiceUrl; + LOG.debug("Will post a new product to URL: {}", url); + + Product product = restTemplate.postForObject(url, body, Product.class); + LOG.debug("Created a product with id: {}", product.getProductId()); + + return product; + + } catch (HttpClientErrorException ex) { + throw handleHttpClientException(ex); + } + } + + @Override + public Product getProduct(int productId) { + + try { + String url = productServiceUrl + "/" + productId; + LOG.debug("Will call the getProduct API on URL: {}", url); + + Product product = restTemplate.getForObject(url, Product.class); + LOG.debug("Found a product with id: {}", product.getProductId()); + + return product; + + } catch (HttpClientErrorException ex) { + throw handleHttpClientException(ex); + } + } + + @Override + public void deleteProduct(int productId) { + try { + String url = productServiceUrl + "/" + productId; + LOG.debug("Will call the deleteProduct API on URL: {}", url); + + restTemplate.delete(url); + + } catch (HttpClientErrorException ex) { + throw handleHttpClientException(ex); + } + } + + @Override + public Recommendation createRecommendation(Recommendation body) { + + try { + String url = recommendationServiceUrl; + LOG.debug("Will post a new recommendation to URL: {}", url); + + Recommendation recommendation = restTemplate.postForObject(url, body, Recommendation.class); + LOG.debug("Created a recommendation with id: {}", recommendation.getProductId()); + + return recommendation; + + } catch (HttpClientErrorException ex) { + throw handleHttpClientException(ex); + } + } + + @Override + public List getRecommendations(int productId) { + + try { + String url = recommendationServiceUrl + "?productId=" + productId; + + LOG.debug("Will call the getRecommendations API on URL: {}", url); + List recommendations = restTemplate + .exchange(url, GET, null, new ParameterizedTypeReference>() {}) + .getBody(); + + LOG.debug("Found {} recommendations for a product with id: {}", recommendations.size(), productId); + return recommendations; + + } catch (Exception ex) { + LOG.warn("Got an exception while requesting recommendations, return zero recommendations: {}", ex.getMessage()); + return new ArrayList<>(); + } + } + + @Override + public void deleteRecommendations(int productId) { + try { + String url = recommendationServiceUrl + "?productId=" + productId; + LOG.debug("Will call the deleteRecommendations API on URL: {}", url); + + restTemplate.delete(url); + + } catch (HttpClientErrorException ex) { + throw handleHttpClientException(ex); + } + } + + @Override + public Review createReview(Review body) { + + try { + String url = reviewServiceUrl; + LOG.debug("Will post a new review to URL: {}", url); + + Review review = restTemplate.postForObject(url, body, Review.class); + LOG.debug("Created a review with id: {}", review.getProductId()); + + return review; + + } catch (HttpClientErrorException ex) { + throw handleHttpClientException(ex); + } + } + + @Override + public List getReviews(int productId) { + + try { + String url = reviewServiceUrl + "?productId=" + productId; + + LOG.debug("Will call the getReviews API on URL: {}", url); + List reviews = restTemplate + .exchange(url, GET, null, new ParameterizedTypeReference>() {}) + .getBody(); + + LOG.debug("Found {} reviews for a product with id: {}", reviews.size(), productId); + return reviews; + + } catch (Exception ex) { + LOG.warn("Got an exception while requesting reviews, return zero reviews: {}", ex.getMessage()); + return new ArrayList<>(); + } + } + + @Override + public void deleteReviews(int productId) { + try { + String url = reviewServiceUrl + "?productId=" + productId; + LOG.debug("Will call the deleteReviews API on URL: {}", url); + + restTemplate.delete(url); + + } catch (HttpClientErrorException ex) { + throw handleHttpClientException(ex); + } + } + + private RuntimeException handleHttpClientException(HttpClientErrorException ex) { + switch (HttpStatus.resolve(ex.getStatusCode().value())) { + + case NOT_FOUND: + return new NotFoundException(getErrorMessage(ex)); + + case UNPROCESSABLE_ENTITY: + return new InvalidInputException(getErrorMessage(ex)); + + default: + LOG.warn("Got an unexpected HTTP error: {}, will rethrow it", ex.getStatusCode()); + LOG.warn("Error body: {}", ex.getResponseBodyAsString()); + return ex; + } + } + + private String getErrorMessage(HttpClientErrorException ex) { + try { + return mapper.readValue(ex.getResponseBodyAsString(), HttpErrorInfo.class).getMessage(); + } catch (IOException ioex) { + return ex.getMessage(); + } + } +} \ No newline at end of file diff --git a/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/service/ProductCompositeServiceImpl.java b/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/service/ProductCompositeServiceImpl.java new file mode 100644 index 0000000..02bf1f8 --- /dev/null +++ b/microservices/product-composite-service/src/main/java/com/example/microservices/composite/product/service/ProductCompositeServiceImpl.java @@ -0,0 +1,130 @@ +package com.example.microservices.composite.product.service; + +import java.util.List; +import java.util.stream.Collectors; + +import com.example.api.composite.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; +import com.example.api.core.product.Product; +import com.example.api.core.recommendation.Recommendation; +import com.example.api.core.review.Review; +import com.example.api.exceptions.NotFoundException; +import com.example.util.http.ServiceUtil; + +@RestController +public class ProductCompositeServiceImpl implements ProductCompositeService { + + private static final Logger LOG = LoggerFactory.getLogger(ProductCompositeServiceImpl.class); + + private final ServiceUtil serviceUtil; + private ProductCompositeIntegration integration; + + @Autowired + public ProductCompositeServiceImpl( + ServiceUtil serviceUtil, ProductCompositeIntegration integration) { + + this.serviceUtil = serviceUtil; + this.integration = integration; + } + + @Override + public void createProduct(ProductAggregate body) { + + try { + + LOG.debug("createCompositeProduct: creates a new composite entity for productId: {}", body.getProductId()); + + Product product = new Product(body.getProductId(), body.getName(), body.getWeight(), null); + integration.createProduct(product); + + if (body.getRecommendations() != null) { + body.getRecommendations().forEach(r -> { + Recommendation recommendation = new Recommendation(body.getProductId(), r.getRecommendationId(), r.getAuthor(), r.getRate(), r.getContent(), null); + integration.createRecommendation(recommendation); + }); + } + + if (body.getReviews() != null) { + body.getReviews().forEach(r -> { + Review review = new Review(body.getProductId(), r.getReviewId(), r.getAuthor(), r.getSubject(), r.getContent(), null); + integration.createReview(review); + }); + } + + LOG.debug("createCompositeProduct: composite entities created for productId: {}", body.getProductId()); + + } catch (RuntimeException re) { + LOG.warn("createCompositeProduct failed", re); + throw re; + } + } + + + @Override + public ProductAggregate getProduct(int productId) { + + LOG.debug("getCompositeProduct: lookup a product aggregate for productId: {}", productId); + + Product product = integration.getProduct(productId); + if (product == null) { + throw new NotFoundException("No product found for productId: " + productId); + } + + List recommendations = integration.getRecommendations(productId); + + List reviews = integration.getReviews(productId); + + LOG.debug("getCompositeProduct: aggregate entity found for productId: {}", productId); + + return createProductAggregate(product, recommendations, reviews, serviceUtil.getServiceAddress()); + } + + @Override + public void deleteProduct(int productId) { + + LOG.debug("deleteCompositeProduct: Deletes a product aggregate for productId: {}", productId); + + integration.deleteProduct(productId); + + integration.deleteRecommendations(productId); + + integration.deleteReviews(productId); + + LOG.debug("deleteCompositeProduct: aggregate entities deleted for productId: {}", productId); + } + + private ProductAggregate createProductAggregate( + Product product, + List recommendations, + List reviews, + String serviceAddress) { + + // 1. Setup product info + int productId = product.getProductId(); + String name = product.getName(); + int weight = product.getWeight(); + + // 2. Copy summary recommendation info, if available + List recommendationSummaries = (recommendations == null) ? null : + recommendations.stream() + .map(r -> new RecommendationSummary(r.getRecommendationId(), r.getAuthor(), r.getRate(), r.getContent())) + .collect(Collectors.toList()); + + // 3. Copy summary review info, if available + List reviewSummaries = (reviews == null) ? null : + reviews.stream() + .map(r -> new ReviewSummary(r.getReviewId(), r.getAuthor(), r.getSubject(), r.getContent())) + .collect(Collectors.toList()); + + // 4. Create info regarding the involved microservices addresses + String productAddress = product.getServiceAddress(); + String reviewAddress = (reviews != null && reviews.size() > 0) ? reviews.get(0).getServiceAddress() : ""; + String recommendationAddress = (recommendations != null && recommendations.size() > 0) ? recommendations.get(0).getServiceAddress() : ""; + ServiceAddresses serviceAddresses = new ServiceAddresses(serviceAddress, productAddress, reviewAddress, recommendationAddress); + + return new ProductAggregate(productId, name, weight, recommendationSummaries, reviewSummaries, serviceAddresses); + } +} \ No newline at end of file diff --git a/microservices/product-composite-service/src/main/resources/application.yml b/microservices/product-composite-service/src/main/resources/application.yml new file mode 100644 index 0000000..545cd3e --- /dev/null +++ b/microservices/product-composite-service/src/main/resources/application.yml @@ -0,0 +1,130 @@ +spring: + application: + name: product-composite-service + +server: + port: 7000 + error: + include-message: always + +app: + product-service: + host: localhost + port: 7001 + recommendation-service: + host: localhost + port: 7002 + review-service: + host: localhost + port: 7003 + +springdoc: + swagger-ui.path: /openapi/swagger-ui.html + api-docs.path: /openapi/v3/api-docs + packagesToScan: com.example.microservices.composite.product + pathsToMatch: /** + show-actuator: true + + +api: + common: + version: 1.0.0 + title: Sample API + description: Description of the API... + termsOfService: MY TERMS OF SERVICE + license: MY LICENSE + licenseUrl: MY LICENSE URL + + externalDocDesc: MY WIKI PAGE + externalDocUrl: MY WIKI URL + contact: + name: NAME OF CONTACT + url: URL TO CONTACT + email: contact@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 + + product-composite: + get-composite-product: + description: Returns a composite view of the specified product id + notes: | + # Normal response + If the requested product id is found the method will return information regarding: + 1. Base product information + 1. Reviews + 1. Recommendations + 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) + + ## Product id 113 + 200 - Ok, but no recommendations will be returned + + ## Product id 213 + 200 - Ok, but no reviews will be returned + + ## Non numerical product id + 400 - A **Bad Request** error will be returned + + ## Product id 13 + 404 - A **Not Found** error will be returned + + ## Negative product ids + 422 - An **Unprocessable Entity** error will be returned + + + create-composite-product: + description: Creates a composite product + notes: | + # Normal response + The composite product information posted to the API will be + split up and stored as separate product-info, recommendation + and review entities. + + # Expected error responses + 1. If a product with the same productId as specified in the + posted information already exists, an **422 - Unprocessable + Entity** error with a "duplicate key" error message will be + Returned + + delete-composite-product: + description: Deletes a product composite + notes: | + # Normal response + Entities for product information, recommendations and reviews + related to the specified productId will be deleted. + The implementation of the delete method is idempotent, that is, + it can be called several times with the same response. + This means that a delete request of a non-existing product will + return **200 Ok**. + +logging: + level: + root: INFO + se.magnus: DEBUG +--- +spring: + config: + activate: + on-profile: docker +server: + port: 8080 +app: + product-service: + host: product + port: 8080 + recommendation-service: + host: recommendation + port: 8080 + review-service: + host: review + port: 8080 \ No newline at end of file diff --git a/microservices/product-composite-service/src/test/java/com/example/microservices/composite/product/ProductCompositeServiceApplicationTests.java b/microservices/product-composite-service/src/test/java/com/example/microservices/composite/product/ProductCompositeServiceApplicationTests.java new file mode 100644 index 0000000..583ee48 --- /dev/null +++ b/microservices/product-composite-service/src/test/java/com/example/microservices/composite/product/ProductCompositeServiceApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.microservices.composite.product; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ProductCompositeServiceApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/microservices/product-service/.gitattributes b/microservices/product-service/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/microservices/product-service/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/microservices/search-service/.gitignore b/microservices/product-service/.gitignore similarity index 69% rename from microservices/search-service/.gitignore rename to microservices/product-service/.gitignore index 549e00a..c2065bc 100644 --- a/microservices/search-service/.gitignore +++ b/microservices/product-service/.gitignore @@ -1,8 +1,9 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ ### STS ### .apt_generated @@ -12,12 +13,18 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ ### NetBeans ### /nbproject/private/ @@ -25,9 +32,6 @@ target/ /dist/ /nbdist/ /.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/microservices/product-service/Dockerfile b/microservices/product-service/Dockerfile new file mode 100644 index 0000000..75b67ac --- /dev/null +++ b/microservices/product-service/Dockerfile @@ -0,0 +1,25 @@ +FROM eclipse-temurin:17.0.5_8-jre-focal as builder +WORKDIR extracted + +ADD ./build/libs/*.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/product-service/build.gradle b/microservices/product-service/build.gradle new file mode 100644 index 0000000..00573e5 --- /dev/null +++ b/microservices/product-service/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.3' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.example.microservices.core.product' +version = '1.0.0-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +ext { + mapstructVersion = "1.5.3.Final" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':api') + implementation project(':util') + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + + compileOnly "org.mapstruct:mapstruct-processor:${mapstructVersion}" + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.projectreactor:reactor-test' + + implementation platform('org.testcontainers:testcontainers-bom:1.17.6') + testImplementation 'org.testcontainers:testcontainers' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:mongodb' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/microservices/product-service/settings.gradle b/microservices/product-service/settings.gradle new file mode 100644 index 0000000..433e543 --- /dev/null +++ b/microservices/product-service/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'product-service' diff --git a/microservices/product-service/src/main/java/com/example/microservices/core/product/ProductServiceApplication.java b/microservices/product-service/src/main/java/com/example/microservices/core/product/ProductServiceApplication.java new file mode 100644 index 0000000..9b9439e --- /dev/null +++ b/microservices/product-service/src/main/java/com/example/microservices/core/product/ProductServiceApplication.java @@ -0,0 +1,48 @@ +package com.example.microservices.core.product; + +import com.example.microservices.core.product.persistence.ProductEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.index.IndexOperations; +import org.springframework.data.mongodb.core.index.IndexResolver; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +@SpringBootApplication +@ComponentScan("com.example") +public class ProductServiceApplication { + + private static final Logger LOG = + LoggerFactory.getLogger(ProductServiceApplication.class); + + public static void main(String[] args) { + ConfigurableApplicationContext ctx = + SpringApplication.run(ProductServiceApplication.class, args); + String mongodDbHost = ctx.getEnvironment().getProperty("spring.data.mongodb.host"); + String mongodDbPort = ctx.getEnvironment().getProperty("spring.data.mongodb.port"); + LOG.info("Connected to MongoDb: " + mongodDbHost + ":" + mongodDbPort); + } + + @Autowired + MongoOperations mongoTemplate; + + @EventListener(ContextRefreshedEvent.class) + public void initIndicesAfterStartup() { + + MappingContext, MongoPersistentProperty> mappingContext = mongoTemplate.getConverter().getMappingContext(); + IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext); + + IndexOperations indexOps = mongoTemplate.indexOps(ProductEntity.class); + resolver.resolveIndexFor(ProductEntity.class).forEach(e -> indexOps.ensureIndex(e)); + } +} diff --git a/microservices/product-service/src/main/java/com/example/microservices/core/product/persistence/ProductEntity.java b/microservices/product-service/src/main/java/com/example/microservices/core/product/persistence/ProductEntity.java new file mode 100644 index 0000000..6efe9bf --- /dev/null +++ b/microservices/product-service/src/main/java/com/example/microservices/core/product/persistence/ProductEntity.java @@ -0,0 +1,70 @@ +package com.example.microservices.core.product.persistence; + +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 = "products") +public class ProductEntity { + + @Id + private String id; + + @Version + private Integer version; + + @Indexed(unique = true) + private int productId; + + private String name; + private int weight; + + public ProductEntity() {} + + public ProductEntity(int productId, String name, int weight) { + this.productId = productId; + this.name = name; + this.weight = weight; + } + + 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 getProductId() { + return productId; + } + + public void setProductId(int productId) { + this.productId = productId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } +} \ No newline at end of file diff --git a/microservices/product-service/src/main/java/com/example/microservices/core/product/persistence/ProductRepository.java b/microservices/product-service/src/main/java/com/example/microservices/core/product/persistence/ProductRepository.java new file mode 100644 index 0000000..df4f5ff --- /dev/null +++ b/microservices/product-service/src/main/java/com/example/microservices/core/product/persistence/ProductRepository.java @@ -0,0 +1,10 @@ +package com.example.microservices.core.product.persistence; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +import java.util.Optional; + +public interface ProductRepository extends PagingAndSortingRepository, CrudRepository { + Optional findByProductId(int productId); +} diff --git a/microservices/product-service/src/main/java/com/example/microservices/core/product/services/ProductMapper.java b/microservices/product-service/src/main/java/com/example/microservices/core/product/services/ProductMapper.java new file mode 100644 index 0000000..9222b29 --- /dev/null +++ b/microservices/product-service/src/main/java/com/example/microservices/core/product/services/ProductMapper.java @@ -0,0 +1,21 @@ +package com.example.microservices.core.product.services; + +import com.example.api.core.product.Product; +import com.example.microservices.core.product.persistence.ProductEntity; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +@Mapper(componentModel = "spring") +public interface ProductMapper { + + @Mappings({ + @Mapping(target = "serviceAddress", ignore = true) + }) + Product entityToApi(ProductEntity entity); + + @Mappings({ + @Mapping(target = "id", ignore = true), @Mapping(target = "version", ignore = true) + }) + ProductEntity apiToEntity(Product api); +} \ No newline at end of file diff --git a/microservices/product-service/src/main/java/com/example/microservices/core/product/services/ProductServiceImpl.java b/microservices/product-service/src/main/java/com/example/microservices/core/product/services/ProductServiceImpl.java new file mode 100644 index 0000000..81c3523 --- /dev/null +++ b/microservices/product-service/src/main/java/com/example/microservices/core/product/services/ProductServiceImpl.java @@ -0,0 +1,70 @@ +package com.example.microservices.core.product.services; + +import com.example.microservices.core.product.persistence.ProductEntity; +import com.example.microservices.core.product.persistence.ProductRepository; +import com.mongodb.DuplicateKeyException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; +import com.example.api.core.product.Product; +import com.example.api.core.product.ProductService; +import com.example.api.exceptions.InvalidInputException; +import com.example.api.exceptions.NotFoundException; +import com.example.util.http.ServiceUtil; + +@RestController +public class ProductServiceImpl implements ProductService { + + private static final Logger LOG = LoggerFactory.getLogger(ProductServiceImpl.class); + + private final ServiceUtil serviceUtil; + private final ProductRepository repository; + private final ProductMapper mapper; + + @Autowired + public ProductServiceImpl(ServiceUtil serviceUtil, ProductRepository repository, ProductMapper mapper) { + this.serviceUtil = serviceUtil; + this.repository = repository; + this.mapper = mapper; + } + + @Override + public Product createProduct(Product body) { + try { + ProductEntity entity = mapper.apiToEntity(body); + ProductEntity newEntity = repository.save(entity); + + LOG.debug("createProduct: entity created for productId: {}", body.getProductId()); + return mapper.entityToApi(newEntity); + + } catch (DuplicateKeyException dke) { + throw new InvalidInputException("Duplicate key, Product Id: " + body.getProductId()); + } + } + + @Override + public Product getProduct(int productId) { + + if (productId < 1) { + throw new InvalidInputException("Invalid productId: " + productId); + } + + ProductEntity entity = repository.findByProductId(productId) + .orElseThrow(() -> new NotFoundException("No product found for productId: " + productId)); + + Product response = mapper.entityToApi(entity); + response.setServiceAddress(serviceUtil.getServiceAddress()); + + LOG.debug("getProduct: found productId: {}", response.getProductId()); + + return response; + } + + @Override + public void deleteProduct(int productId) { + LOG.debug("deleteProduct: tries to delete an entity with productId: {}", productId); + repository.findByProductId(productId).ifPresent(repository::delete); + } + +} diff --git a/microservices/product-service/src/main/resources/application.yml b/microservices/product-service/src/main/resources/application.yml new file mode 100644 index 0000000..3769a54 --- /dev/null +++ b/microservices/product-service/src/main/resources/application.yml @@ -0,0 +1,31 @@ +spring: + application: + name: product-service + data: + mongodb: + host: localhost + port: 27017 + database: product-db + +server: + port: 7001 + + +logging: + level: + root: INFO + com.example.microservices: DEBUG + org.springframework.data.mongodb.core.MongoTemplate: DEBUG + +--- +spring: + config: + activate: + on-profile: docker + data: + mongodb: + host: mongodb + + +server: + port: 8080 \ No newline at end of file diff --git a/microservices/product-service/src/test/java/com/example/microservices/core/product/ProductServiceApplicationTests.java b/microservices/product-service/src/test/java/com/example/microservices/core/product/ProductServiceApplicationTests.java new file mode 100644 index 0000000..94fbcd1 --- /dev/null +++ b/microservices/product-service/src/test/java/com/example/microservices/core/product/ProductServiceApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.microservices.core.product; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ProductServiceApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/microservices/recommendation-service/.gitattributes b/microservices/recommendation-service/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/microservices/recommendation-service/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/microservices/course-service/.gitignore b/microservices/recommendation-service/.gitignore similarity index 69% rename from microservices/course-service/.gitignore rename to microservices/recommendation-service/.gitignore index 549e00a..c2065bc 100644 --- a/microservices/course-service/.gitignore +++ b/microservices/recommendation-service/.gitignore @@ -1,8 +1,9 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ ### STS ### .apt_generated @@ -12,12 +13,18 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ ### NetBeans ### /nbproject/private/ @@ -25,9 +32,6 @@ target/ /dist/ /nbdist/ /.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/microservices/recommendation-service/Dockerfile b/microservices/recommendation-service/Dockerfile new file mode 100644 index 0000000..75b67ac --- /dev/null +++ b/microservices/recommendation-service/Dockerfile @@ -0,0 +1,25 @@ +FROM eclipse-temurin:17.0.5_8-jre-focal as builder +WORKDIR extracted + +ADD ./build/libs/*.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/recommendation-service/build.gradle b/microservices/recommendation-service/build.gradle new file mode 100644 index 0000000..fd098ba --- /dev/null +++ b/microservices/recommendation-service/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.3' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.example.microservices.core.recommendation' +version = '1.0.0-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +ext { + mapstructVersion = "1.5.3.Final" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':api') + implementation project(':util') + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + + compileOnly "org.mapstruct:mapstruct-processor:${mapstructVersion}" + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.projectreactor:reactor-test' + + implementation platform('org.testcontainers:testcontainers-bom:1.17.6') + testImplementation 'org.testcontainers:testcontainers' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:mongodb' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/microservices/recommendation-service/settings.gradle b/microservices/recommendation-service/settings.gradle new file mode 100644 index 0000000..98f9d89 --- /dev/null +++ b/microservices/recommendation-service/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'recommendation-service' diff --git a/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/RecommendationServiceApplication.java b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/RecommendationServiceApplication.java new file mode 100644 index 0000000..ca23a9a --- /dev/null +++ b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/RecommendationServiceApplication.java @@ -0,0 +1,47 @@ +package com.example.microservices.core.recommendation; + +import com.example.microservices.core.recommendation.persistence.RecommendationEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.index.IndexOperations; +import org.springframework.data.mongodb.core.index.IndexResolver; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +@SpringBootApplication +@ComponentScan("com.example") +public class RecommendationServiceApplication { + + private static final Logger LOG = LoggerFactory.getLogger(RecommendationServiceApplication.class); + + + public static void main(String[] args) { + ConfigurableApplicationContext ctx = SpringApplication.run(RecommendationServiceApplication.class, args); + String mongodDbHost = ctx.getEnvironment().getProperty("spring.data.mongodb.host"); + String mongodDbPort = ctx.getEnvironment().getProperty("spring.data.mongodb.port"); + LOG.info("Connected to MongoDb: " + mongodDbHost + ":" + mongodDbPort); + } + + @Autowired + MongoOperations mongoTemplate; + + @EventListener(ContextRefreshedEvent.class) + public void initIndicesAfterStartup() { + + MappingContext, MongoPersistentProperty> mappingContext = mongoTemplate.getConverter().getMappingContext(); + IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext); + + IndexOperations indexOps = mongoTemplate.indexOps(RecommendationEntity.class); + resolver.resolveIndexFor(RecommendationEntity.class).forEach(e -> indexOps.ensureIndex(e)); + } +} diff --git a/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/persistence/RecommendationEntity.java b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/persistence/RecommendationEntity.java new file mode 100644 index 0000000..d9b8b59 --- /dev/null +++ b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/persistence/RecommendationEntity.java @@ -0,0 +1,90 @@ +package com.example.microservices.core.recommendation.persistence; + +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Version; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "recommendations") +@CompoundIndex(name = "prod-rec-id", unique = true, def = "{'productId': 1, 'recommendationId' : 1}") +public class RecommendationEntity { + + @Id + private String id; + + @Version + private Integer version; + + private int productId; + private int recommendationId; + private String author; + private int rating; + private String content; + + public RecommendationEntity() { + } + + public RecommendationEntity(int productId, int recommendationId, String author, int rating, String content) { + this.productId = productId; + this.recommendationId = recommendationId; + this.author = author; + this.rating = rating; + this.content = content; + } + + public String getId() { + return id; + } + + public Integer getVersion() { + return version; + } + + public int getProductId() { + return productId; + } + + public int getRecommendationId() { + return recommendationId; + } + + public String getAuthor() { + return author; + } + + public int getRating() { + return rating; + } + + public String getContent() { + return content; + } + + public void setId(String id) { + this.id = id; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public void setProductId(int productId) { + this.productId = productId; + } + + public void setRecommendationId(int recommendationId) { + this.recommendationId = recommendationId; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setRating(int rating) { + this.rating = rating; + } + + public void setContent(String content) { + this.content = content; + } +} \ No newline at end of file diff --git a/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/persistence/RecommendationRepository.java b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/persistence/RecommendationRepository.java new file mode 100644 index 0000000..d503f07 --- /dev/null +++ b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/persistence/RecommendationRepository.java @@ -0,0 +1,9 @@ +package com.example.microservices.core.recommendation.persistence; + +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface RecommendationRepository extends CrudRepository { + List findByProductId(int productId); +} \ No newline at end of file diff --git a/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/services/RecommendationMapper.java b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/services/RecommendationMapper.java new file mode 100644 index 0000000..5e2b244 --- /dev/null +++ b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/services/RecommendationMapper.java @@ -0,0 +1,31 @@ +package com.example.microservices.core.recommendation.services; + + +import com.example.api.core.recommendation.Recommendation; +import com.example.microservices.core.recommendation.persistence.RecommendationEntity; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface RecommendationMapper { + + @Mappings({ + @Mapping(target = "rate", source = "entity.rating"), + @Mapping(target = "serviceAddress", ignore = true) + }) + Recommendation entityToApi(RecommendationEntity entity); + + @Mappings({ + @Mapping(target = "rating", source = "api.rate"), + @Mapping(target = "id", ignore = true), + @Mapping(target = "version", ignore = true) + }) + RecommendationEntity apiToEntity(Recommendation api); + + List entityListToApiList(List entity); + + List apiListToEntityList(List api); +} \ No newline at end of file diff --git a/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/services/RecommendationServiceImpl.java b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/services/RecommendationServiceImpl.java new file mode 100644 index 0000000..f762b04 --- /dev/null +++ b/microservices/recommendation-service/src/main/java/com/example/microservices/core/recommendation/services/RecommendationServiceImpl.java @@ -0,0 +1,71 @@ +package com.example.microservices.core.recommendation.services; + +import com.example.api.core.recommendation.Recommendation; +import com.example.api.core.recommendation.RecommendationService; +import com.example.api.exceptions.InvalidInputException; +import com.example.microservices.core.recommendation.persistence.RecommendationEntity; +import com.example.microservices.core.recommendation.persistence.RecommendationRepository; +import com.example.util.http.ServiceUtil; +import com.mongodb.DuplicateKeyException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +@RestController +public class RecommendationServiceImpl implements RecommendationService { + + private static final Logger LOG = LoggerFactory.getLogger(RecommendationServiceImpl.class); + + private final RecommendationRepository repository; + + private final RecommendationMapper mapper; + + private final ServiceUtil serviceUtil; + + @Autowired + public RecommendationServiceImpl(RecommendationRepository repository, RecommendationMapper mapper, ServiceUtil serviceUtil) { + this.repository = repository; + this.mapper = mapper; + this.serviceUtil = serviceUtil; + } + + @Override + public Recommendation createRecommendation(Recommendation body) { + try { + RecommendationEntity entity = mapper.apiToEntity(body); + RecommendationEntity newEntity = repository.save(entity); + + LOG.debug("createRecommendation: created a recommendation entity: {}/{}", body.getProductId(), body.getRecommendationId()); + return mapper.entityToApi(newEntity); + + } catch (DuplicateKeyException dke) { + throw new InvalidInputException("Duplicate key, Product Id: " + body.getProductId() + ", Recommendation Id:" + body.getRecommendationId()); + } + } + + @Override + public List getRecommendations(int productId) { + + if (productId < 1) { + throw new InvalidInputException("Invalid productId: " + productId); + } + + List entityList = repository.findByProductId(productId); + List list = mapper.entityListToApiList(entityList); + list.forEach(e -> e.setServiceAddress(serviceUtil.getServiceAddress())); + + LOG.debug("getRecommendations: response size: {}", list.size()); + + return list; + } + + @Override + public void deleteRecommendations(int productId) { + LOG.debug("deleteRecommendations: tries to delete recommendations for the product with productId: {}", productId); + repository.deleteAll(repository.findByProductId(productId)); + } +} \ No newline at end of file diff --git a/microservices/recommendation-service/src/main/resources/application.yml b/microservices/recommendation-service/src/main/resources/application.yml new file mode 100644 index 0000000..3d95bad --- /dev/null +++ b/microservices/recommendation-service/src/main/resources/application.yml @@ -0,0 +1,27 @@ +spring: + application: + name: recommendation-service + + data: + mongodb: + host: localhost + port: 27017 + database: recommendation-db + + +server.port: 7002 +logging: + level: + root: INFO + com.example.microservices: DEBUG + org.springframework.data.mongodb.core.MongoTemplate: DEBUG +--- +spring: + config: + activate: + on-profile: docker + data: + mongodb: + host: mongodb +server: + port: 8080 diff --git a/microservices/recommendation-service/src/test/java/com/example/microservices/core/recommendation/RecommendationServiceApplicationTests.java b/microservices/recommendation-service/src/test/java/com/example/microservices/core/recommendation/RecommendationServiceApplicationTests.java new file mode 100644 index 0000000..bc79056 --- /dev/null +++ b/microservices/recommendation-service/src/test/java/com/example/microservices/core/recommendation/RecommendationServiceApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.microservices.core.recommendation; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class RecommendationServiceApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/microservices/review-service/.gitattributes b/microservices/review-service/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/microservices/review-service/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/microservices/student-service/.gitignore b/microservices/review-service/.gitignore similarity index 69% rename from microservices/student-service/.gitignore rename to microservices/review-service/.gitignore index 549e00a..c2065bc 100644 --- a/microservices/student-service/.gitignore +++ b/microservices/review-service/.gitignore @@ -1,8 +1,9 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ ### STS ### .apt_generated @@ -12,12 +13,18 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ ### NetBeans ### /nbproject/private/ @@ -25,9 +32,6 @@ target/ /dist/ /nbdist/ /.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/microservices/review-service/Dockerfile b/microservices/review-service/Dockerfile new file mode 100644 index 0000000..75b67ac --- /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 ./build/libs/*.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/build.gradle b/microservices/review-service/build.gradle new file mode 100644 index 0000000..0b61c24 --- /dev/null +++ b/microservices/review-service/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.3' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.example.microservices.core.review' +version = '1.0.0-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +ext { + mapstructVersion = "1.5.3.Final" +} + + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':api') + implementation project(':util') + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'com.mysql:mysql-connector-j' + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + + compileOnly "org.mapstruct:mapstruct-processor:${mapstructVersion}" + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.projectreactor:reactor-test' + + implementation platform('org.testcontainers:testcontainers-bom:1.17.6') + testImplementation 'org.testcontainers:testcontainers' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:mysql' + +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/microservices/review-service/settings.gradle b/microservices/review-service/settings.gradle new file mode 100644 index 0000000..b699da2 --- /dev/null +++ b/microservices/review-service/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'review-service' diff --git a/microservices/review-service/src/main/java/com/example/microservices/core/review/ReviewServiceApplication.java b/microservices/review-service/src/main/java/com/example/microservices/core/review/ReviewServiceApplication.java new file mode 100644 index 0000000..6a1637f --- /dev/null +++ b/microservices/review-service/src/main/java/com/example/microservices/core/review/ReviewServiceApplication.java @@ -0,0 +1,23 @@ +package com.example.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("com.example") +public class ReviewServiceApplication { + + private static final Logger LOG = LoggerFactory.getLogger(ReviewServiceApplication.class); + + public static void main(String[] args) { + ConfigurableApplicationContext ctx = SpringApplication.run(ReviewServiceApplication.class, args); + + String mysqlUri = ctx.getEnvironment().getProperty("spring.datasource.url"); + LOG.info("Connected to MySQL: " + mysqlUri); + } + +} diff --git a/microservices/review-service/src/main/java/com/example/microservices/core/review/persistence/ReviewEntity.java b/microservices/review-service/src/main/java/com/example/microservices/core/review/persistence/ReviewEntity.java new file mode 100644 index 0000000..e2bbccd --- /dev/null +++ b/microservices/review-service/src/main/java/com/example/microservices/core/review/persistence/ReviewEntity.java @@ -0,0 +1,87 @@ +package com.example.microservices.core.review.persistence; + +import jakarta.persistence.*; + +@Entity +@Table(name = "reviews", indexes = { @Index(name = "reviews_unique_idx", unique = true, columnList = "productId,reviewId") }) +public class ReviewEntity { + + @Id @GeneratedValue + private int id; + + @Version + private int version; + + private int productId; + private int reviewId; + private String author; + private String subject; + private String content; + + public ReviewEntity() { + } + + public ReviewEntity(int productId, int reviewId, String author, String subject, String content) { + this.productId = productId; + this.reviewId = reviewId; + this.author = author; + this.subject = subject; + this.content = content; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public int getProductId() { + return productId; + } + + public void setProductId(int productId) { + this.productId = productId; + } + + public int getReviewId() { + return reviewId; + } + + public void setReviewId(int reviewId) { + this.reviewId = reviewId; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} \ No newline at end of file diff --git a/microservices/review-service/src/main/java/com/example/microservices/core/review/persistence/ReviewRepository.java b/microservices/review-service/src/main/java/com/example/microservices/core/review/persistence/ReviewRepository.java new file mode 100644 index 0000000..13dcdfa --- /dev/null +++ b/microservices/review-service/src/main/java/com/example/microservices/core/review/persistence/ReviewRepository.java @@ -0,0 +1,12 @@ +package com.example.microservices.core.review.persistence; + +import org.springframework.transaction.annotation.Transactional; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface ReviewRepository extends CrudRepository { + + @Transactional(readOnly = true) + List findByProductId(int productId); +} \ No newline at end of file diff --git a/microservices/review-service/src/main/java/com/example/microservices/core/review/services/ReviewMapper.java b/microservices/review-service/src/main/java/com/example/microservices/core/review/services/ReviewMapper.java new file mode 100644 index 0000000..9c9cd92 --- /dev/null +++ b/microservices/review-service/src/main/java/com/example/microservices/core/review/services/ReviewMapper.java @@ -0,0 +1,28 @@ +package com.example.microservices.core.review.services; + +import com.example.api.core.review.Review; +import com.example.microservices.core.review.persistence.ReviewEntity; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface ReviewMapper { + + @Mappings({ + @Mapping(target = "serviceAddress", ignore = true) + }) + Review entityToApi(ReviewEntity entity); + + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(target = "version", ignore = true) + }) + ReviewEntity apiToEntity(Review api); + + List entityListToApiList(List entity); + + List apiListToEntityList(List api); +} \ No newline at end of file diff --git a/microservices/review-service/src/main/java/com/example/microservices/core/review/services/ReviewServiceImpl.java b/microservices/review-service/src/main/java/com/example/microservices/core/review/services/ReviewServiceImpl.java new file mode 100644 index 0000000..eed086e --- /dev/null +++ b/microservices/review-service/src/main/java/com/example/microservices/core/review/services/ReviewServiceImpl.java @@ -0,0 +1,71 @@ +package com.example.microservices.core.review.services; + +import com.example.api.core.review.Review; +import com.example.api.core.review.ReviewService; +import com.example.api.exceptions.InvalidInputException; +import com.example.microservices.core.review.persistence.ReviewEntity; +import com.example.microservices.core.review.persistence.ReviewRepository; +import com.example.util.http.ServiceUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +@RestController +public class ReviewServiceImpl implements ReviewService { + + private static final Logger LOG = LoggerFactory.getLogger(ReviewServiceImpl.class); + + private final ReviewRepository repository; + + private final ReviewMapper mapper; + + private final ServiceUtil serviceUtil; + + @Autowired + public ReviewServiceImpl(ReviewRepository repository, ReviewMapper mapper, ServiceUtil serviceUtil) { + this.repository = repository; + this.mapper = mapper; + this.serviceUtil = serviceUtil; + } + + @Override + public Review createReview(Review body) { + try { + ReviewEntity entity = mapper.apiToEntity(body); + ReviewEntity newEntity = repository.save(entity); + + LOG.debug("createReview: created a review entity: {}/{}", body.getProductId(), body.getReviewId()); + return mapper.entityToApi(newEntity); + + } catch (DataIntegrityViolationException dive) { + throw new InvalidInputException("Duplicate key, Product Id: " + body.getProductId() + ", Review Id:" + body.getReviewId()); + } + } + + @Override + public List getReviews(int productId) { + + if (productId < 1) { + throw new InvalidInputException("Invalid productId: " + productId); + } + + List entityList = repository.findByProductId(productId); + List list = mapper.entityListToApiList(entityList); + list.forEach(e -> e.setServiceAddress(serviceUtil.getServiceAddress())); + + LOG.debug("getReviews: response size: {}", list.size()); + + return list; + } + + @Override + public void deleteReviews(int productId) { + LOG.debug("deleteReviews: tries to delete reviews for the product with productId: {}", productId); + repository.deleteAll(repository.findByProductId(productId)); + } +} 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..a9262c5 --- /dev/null +++ b/microservices/review-service/src/main/resources/application.yml @@ -0,0 +1,33 @@ +spring: + application: + name: review-service + # Strongly recommend to set this property to "none" in a production environment! + jpa: + hibernate: + ddl-auto: update + + datasource: + url: jdbc:mysql://localhost/review-db + username: user + password: pwd + hikari: + initializationFailTimeout: 60000 +server: + port: 7003 + +logging: + level: + root: INFO + se.magnus: DEBUG + org.hibernate.SQL: DEBUG + org.hibernate.type.descriptor.sql.BasicBinder: TRACE +--- +spring: + config: + activate: + on-profile: docker + datasource: + url: jdbc:mysql://mysql/review-db + +server: + port: 8080 \ No newline at end of file diff --git a/microservices/review-service/src/test/java/com/example/microservices/core/review/ReviewServiceApplicationTests.java b/microservices/review-service/src/test/java/com/example/microservices/core/review/ReviewServiceApplicationTests.java new file mode 100644 index 0000000..949ac1d --- /dev/null +++ b/microservices/review-service/src/test/java/com/example/microservices/core/review/ReviewServiceApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.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/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/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 deleted file mode 100755 index 8a8fb22..0000000 --- a/mvnw +++ /dev/null @@ -1,316 +0,0 @@ -#!/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 -# -# 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. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# 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 -# ---------------------------------------------------------------------------- - -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 - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# 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" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - 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 - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# 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 - -# 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)`" -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 - -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 -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$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 -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 -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 - -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 "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index 1d8ab01..0000000 --- a/mvnw.cmd +++ /dev/null @@ -1,188 +0,0 @@ -@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 https://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 Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@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 ---------------------------------------------------------------------------- - -@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% - ) -) -@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% diff --git a/pom.xml b/pom.xml deleted file mode 100644 index e99c8af..0000000 --- a/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - 4.0.0 - - io.javatab.microservices - student-course-system - 1.0.0 - pom - - student-course-system - Parent Pom for the student course registration system project - - - org.springframework.boot - spring-boot-starter-parent - 3.4.3 - - - - 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 - - \ No newline at end of file diff --git a/run.sh b/run.sh deleted file mode 100644 index e69de29..0000000 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..546b98d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,7 @@ +include ':api' +include ':util' +include ':microservices:product-service' +include ':microservices:review-service' +include ':microservices:recommendation-service' +include ':microservices:product-composite-service' +include ':springcloud:eurika-server' 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/pom.xml b/spring-cloud/authorization-server/pom.xml deleted file mode 100644 index 97db72d..0000000 --- a/spring-cloud/authorization-server/pom.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.4.3 - - - io.javatab - authorization-server - 1.0.0 - authorization-server - Demo project for Spring Boot - - 17 - 2024.0.0 - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.security - spring-security-oauth2-authorization-server - 1.1.6 - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - org.springframework.cloud - spring-cloud-starter-config - - - org.springframework.retry - spring-retry - - - org.springframework.boot - spring-boot-starter-actuator - - - - 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/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/.gitattributes b/spring-cloud/eureka-server/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/spring-cloud/eureka-server/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/spring-cloud/eureka-server/.gitignore b/spring-cloud/eureka-server/.gitignore index 549e00a..c2065bc 100644 --- a/spring-cloud/eureka-server/.gitignore +++ b/spring-cloud/eureka-server/.gitignore @@ -1,8 +1,9 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ ### STS ### .apt_generated @@ -12,12 +13,18 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ ### NetBeans ### /nbproject/private/ @@ -25,9 +32,6 @@ target/ /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/build.gradle b/spring-cloud/eureka-server/build.gradle new file mode 100644 index 0000000..36baf5c --- /dev/null +++ b/spring-cloud/eureka-server/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.3' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.example.springcloud' +version = '1.0.0-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} 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/settings.gradle b/spring-cloud/eureka-server/settings.gradle new file mode 100644 index 0000000..52cb81f --- /dev/null +++ b/spring-cloud/eureka-server/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'eureka-server' diff --git a/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/EurekaServerApplication.java b/spring-cloud/eureka-server/src/main/java/com/example/springcloud/eurekaserver/EurekaServerApplication.java similarity index 67% rename from spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/EurekaServerApplication.java rename to spring-cloud/eureka-server/src/main/java/com/example/springcloud/eurekaserver/EurekaServerApplication.java index 23556f0..e75b2a6 100644 --- a/spring-cloud/eureka-server/src/main/java/io/javatab/springcloud/eurekaserver/EurekaServerApplication.java +++ b/spring-cloud/eureka-server/src/main/java/com/example/springcloud/eurekaserver/EurekaServerApplication.java @@ -1,11 +1,9 @@ -package io.javatab.springcloud.eurekaserver; +package com.example.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) { 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 index 9654833..a8dc1a2 100644 --- a/spring-cloud/eureka-server/src/main/resources/application.yml +++ b/spring-cloud/eureka-server/src/main/resources/application.yml @@ -1,22 +1,23 @@ -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} + application: + name: eureka-server +server: + port: 8761 -logging: - level: - root: DEBUG ---- -spring.config.activate.on-profile: docker +eureka: + instance: + hostname: localhost + client: + registerWithEureka: false + fetchRegistry: false + serviceUrl: + defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ + server: + waitTimeInMsWhenSyncEmpty: 0 + response-cache-update-interval-ms: 5000 -spring.cloud.config.uri: http://config-server:8888 \ No newline at end of file +management: + endpoints: + web: + exposure: + include: "*" \ No newline at end of file diff --git a/spring-cloud/eureka-server/src/test/java/com/example/springcloud/eurekaserver/EurekaServerApplicationTests.java b/spring-cloud/eureka-server/src/test/java/com/example/springcloud/eurekaserver/EurekaServerApplicationTests.java new file mode 100644 index 0000000..dd6a4b2 --- /dev/null +++ b/spring-cloud/eureka-server/src/test/java/com/example/springcloud/eurekaserver/EurekaServerApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.springcloud.eurekaserver; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class EurekaServerApplicationTests { + + @Test + void contextLoads() { + } + +} 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/eureka-server/target/classes/com/example/springcloud/eurekaserver/EurekaServerApplication.class b/spring-cloud/eureka-server/target/classes/com/example/springcloud/eurekaserver/EurekaServerApplication.class new file mode 100644 index 0000000..c3f2265 Binary files /dev/null and b/spring-cloud/eureka-server/target/classes/com/example/springcloud/eurekaserver/EurekaServerApplication.class differ 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/test-em-all.sh b/test-em-all.sh new file mode 100644 index 0000000..449fc8b --- /dev/null +++ b/test-em-all.sh @@ -0,0 +1,5 @@ +cd spring-boot-based-microservices +java -jar microservices/product-composite-service/build/libs/product-composite-service-1.0.0-SNAPSHOT.jar & +java -jar microservices/product-service/build/libs/product-service-1.0.0-SNAPSHOT.jar & +java -jar microservices/recommendation-service/build/libs/recommendation-service-1.0.0-SNAPSHOT.jar & +java -jar microservices/review-service/build/libs/review-service-1.0.0-SNAPSHOT.jar & \ No newline at end of file diff --git a/util/.gitattributes b/util/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/util/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/util/.gitignore b/util/.gitignore index 549e00a..c2065bc 100644 --- a/util/.gitignore +++ b/util/.gitignore @@ -1,8 +1,9 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ ### STS ### .apt_generated @@ -12,12 +13,18 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ ### NetBeans ### /nbproject/private/ @@ -25,9 +32,6 @@ target/ /dist/ /nbdist/ /.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/util/build.gradle b/util/build.gradle new file mode 100644 index 0000000..38abe64 --- /dev/null +++ b/util/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'java' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'com.example.util' +version = '1.0.0-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +ext { + springBootVersion = '3.4.3' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}") + + implementation project(':api') + implementation 'org.springframework.boot:spring-boot-starter-webflux' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/util/pom.xml b/util/pom.xml deleted file mode 100644 index 3f34614..0000000 --- a/util/pom.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.4.3 - - - io.javatab.microservices.util - util - 1.0.0 - jar - util - Demo project for Spring Boot - - 17 - - - - org.springframework.boot - spring-boot-starter-webflux - - - - org.springframework.boot - 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 - - - - - - - - diff --git a/util/settings.gradle b/util/settings.gradle new file mode 100644 index 0000000..28b50c8 --- /dev/null +++ b/util/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'util' diff --git a/util/src/main/java/com/example/util/http/GlobalControllerExceptionHandler.java b/util/src/main/java/com/example/util/http/GlobalControllerExceptionHandler.java new file mode 100644 index 0000000..07dd3c6 --- /dev/null +++ b/util/src/main/java/com/example/util/http/GlobalControllerExceptionHandler.java @@ -0,0 +1,47 @@ +package com.example.util.http; + +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; + +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 com.example.api.exceptions.InvalidInputException; +import com.example.api.exceptions.NotFoundException; + +@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); + } +} \ No newline at end of file diff --git a/util/src/main/java/com/example/util/http/HttpErrorInfo.java b/util/src/main/java/com/example/util/http/HttpErrorInfo.java new file mode 100644 index 0000000..72b6553 --- /dev/null +++ b/util/src/main/java/com/example/util/http/HttpErrorInfo.java @@ -0,0 +1,45 @@ +package com.example.util.http; + +import java.time.ZonedDateTime; +import org.springframework.http.HttpStatus; + +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; + } +} \ No newline at end of file diff --git a/util/src/main/java/com/example/util/http/ServiceUtil.java b/util/src/main/java/com/example/util/http/ServiceUtil.java new file mode 100644 index 0000000..7dc84e4 --- /dev/null +++ b/util/src/main/java/com/example/util/http/ServiceUtil.java @@ -0,0 +1,48 @@ +package com.example.util.http; + + +import java.net.InetAddress; +import java.net.UnknownHostException; +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; + +@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"; + } + } +} \ No newline at end of file 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/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..93d967e --- /dev/null +++ b/util/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + application: + name:util \ No newline at end of file diff --git a/util/src/test/java/com/example/util/UtilApplicationTests.java b/util/src/test/java/com/example/util/UtilApplicationTests.java new file mode 100644 index 0000000..68b9b67 --- /dev/null +++ b/util/src/test/java/com/example/util/UtilApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.util; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class UtilApplicationTests { + + @Test + void contextLoads() { + } + +} 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() { - - } -}