From be52740c5312f5c6e5de0d97e7a9382c02e07488 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 12:51:01 +0200 Subject: [PATCH 01/31] Implement create project route --- config/checkstyle/checkstyle-ignore.xml | 18 +- config/checkstyle/checkstyle.xml | 46 +- gradle/wrapper/gradle-wrapper.properties | 14 +- gradlew | 504 +++++++++--------- .../config/OpenAPIConfiguration.java | 120 ++--- .../lf8_starter/project/GetProjectDto.java | 43 ++ .../project/dto/CreateProjectAction.java | 42 ++ .../security/KeycloakLogoutHandler.java | 98 ++-- .../resources/application-test.properties | 8 +- .../Lf8StarterApplicationTests.java | 28 +- 10 files changed, 503 insertions(+), 418 deletions(-) create mode 100644 src/main/java/de/szut/lf8_starter/project/GetProjectDto.java create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java diff --git a/config/checkstyle/checkstyle-ignore.xml b/config/checkstyle/checkstyle-ignore.xml index fe3b2fe..5ec61a5 100644 --- a/config/checkstyle/checkstyle-ignore.xml +++ b/config/checkstyle/checkstyle-ignore.xml @@ -1,9 +1,9 @@ - - - - - - + + + + + + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index d8f0701..3c18de8 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72..b9ddd88 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index f5feea6..ec19934 100755 --- a/gradlew +++ b/gradlew @@ -1,252 +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" "$@" +#!/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/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java index 1b2282b..76a6f58 100644 --- a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java +++ b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java @@ -1,60 +1,60 @@ -package de.szut.lf8_starter.config; - - - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; -import jakarta.servlet.ServletContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - -@Configuration -public class OpenAPIConfiguration { - - private ServletContext context; - - public OpenAPIConfiguration(ServletContext context) { - this.context = context; - } - - - @Bean - public OpenAPI springShopOpenAPI( - // @Value("${info.app.version}") String appVersion, - ) { - final String securitySchemeName = "bearerAuth"; - - return new OpenAPI() - .addServersItem(new Server().url(this.context.getContextPath())) - .info(new Info() - .title("LF8 project starter") - .description("\n## Auth\n" + - "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + - "\n" + - "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + - "\n" + - "\nor by CURL\n" + - "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + - "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") - - .version("0.1")) - .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) - .components( - new Components() - .addSecuritySchemes(securitySchemeName, - new SecurityScheme() - .name(securitySchemeName) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - ) - ); - } - - -} +package de.szut.lf8_starter.config; + + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import jakarta.servlet.ServletContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class OpenAPIConfiguration { + + private ServletContext context; + + public OpenAPIConfiguration(ServletContext context) { + this.context = context; + } + + + @Bean + public OpenAPI springShopOpenAPI( + // @Value("${info.app.version}") String appVersion, + ) { + final String securitySchemeName = "bearerAuth"; + + return new OpenAPI() + .addServersItem(new Server().url(this.context.getContextPath())) + .info(new Info() + .title("LF8 project starter") + .description("\n## Auth\n" + + "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + + "\n" + + "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + + "\n" + + "\nor by CURL\n" + + "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + + "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") + + .version("0.1")) + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) + .components( + new Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } + + +} diff --git a/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java new file mode 100644 index 0000000..86ce087 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java @@ -0,0 +1,43 @@ +package de.szut.lf8_starter.project; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class GetProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java new file mode 100644 index 0000000..f1c6cdc --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java @@ -0,0 +1,42 @@ +package de.szut.lf8_starter.project.dto; + +import de.szut.lf8_starter.project.GetProjectDto; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/projects") +public class CreateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Creates a new Project") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @PostMapping + public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + + projectEntity = this.projectService.create(projectEntity); + + return this.projectMapper.mapToGetDto(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java index 8555ef9..e06f2f7 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java @@ -1,49 +1,49 @@ -package de.szut.lf8_starter.security; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Slf4j -@Component -public class KeycloakLogoutHandler implements LogoutHandler { - - - private final RestTemplate restTemplate; - - public KeycloakLogoutHandler(RestTemplate restTemplate) { - this.restTemplate = restTemplate; - } - - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { - logout(request, auth); - } - - public void logout(HttpServletRequest request, Authentication auth) { - logoutFromKeycloak((OidcUser) auth.getPrincipal()); - } - - private void logoutFromKeycloak(OidcUser user) { - String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; - UriComponentsBuilder builder = UriComponentsBuilder - .fromUriString(endSessionEndpoint) - .queryParam("id_token_hint", user.getIdToken().getTokenValue()); - - ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); - if (logoutResponse.getStatusCode().is2xxSuccessful()) { - log.info("Successfulley logged out from Keycloak"); - } else { - log.error("Could not propagate logout to Keycloak"); - } - } - -} +package de.szut.lf8_starter.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Slf4j +@Component +public class KeycloakLogoutHandler implements LogoutHandler { + + + private final RestTemplate restTemplate; + + public KeycloakLogoutHandler(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { + logout(request, auth); + } + + public void logout(HttpServletRequest request, Authentication auth) { + logoutFromKeycloak((OidcUser) auth.getPrincipal()); + } + + private void logoutFromKeycloak(OidcUser user) { + String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; + UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(endSessionEndpoint) + .queryParam("id_token_hint", user.getIdToken().getTokenValue()); + + ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + log.info("Successfulley logged out from Keycloak"); + } else { + log.error("Could not propagate logout to Keycloak"); + } + } + +} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index af21e0e..259f612 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -1,5 +1,5 @@ -spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password= +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java index b5c7f7b..d418f1a 100644 --- a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java +++ b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java @@ -1,14 +1,14 @@ -package de.szut.lf8_starter; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; - -@SpringBootTest -class Lf8StarterApplicationTests { - - @Test - void contextLoads() { - } - -} +package de.szut.lf8_starter; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest +class Lf8StarterApplicationTests { + + @Test + void contextLoads() { + } + +} From 2f92dd3faabcf37f5637169794e87b5ea5c16d3e Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 12:52:05 +0200 Subject: [PATCH 02/31] Rebase --- GetBearerToken.http | 10 +- requests/createProject.http | 28 ++--- .../GlobalExceptionHandler.java | 102 +++++++++--------- .../lf8_starter/project/GetProjectDto.java | 86 +++++++-------- .../lf8_starter/project/ProjectEntity.java | 86 +++++++-------- .../project/dto/CreateProjectAction.java | 84 +++++++-------- .../project/dto/CreateProjectDto.java | 84 +++++++-------- 7 files changed, 240 insertions(+), 240 deletions(-) diff --git a/GetBearerToken.http b/GetBearerToken.http index f5209ca..abf7e94 100644 --- a/GetBearerToken.http +++ b/GetBearerToken.http @@ -1,6 +1,6 @@ -POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token -Content-Type: application/x-www-form-urlencoded - -grant_type=password&client_id=employee-management-service&username=user&password=test - +POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token +Content-Type: application/x-www-form-urlencoded + +grant_type=password&client_id=employee-management-service&username=user&password=test + > {% client.global.set("auth_token", response.body.access_token); %} \ No newline at end of file diff --git a/requests/createProject.http b/requests/createProject.http index add2394..6854828 100644 --- a/requests/createProject.http +++ b/requests/createProject.http @@ -1,15 +1,15 @@ -### GET request to example server -POST http://localhost:8080/projects -Authorization: Bearer {{auth_token}} -Content-Type: application/json - -{ - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractorName": "Peter File", - "comment": "goal of project", - "startDate": "01.01.2000", - "plannedEndDate": "01.01.2001" +### GET request to example server +POST http://localhost:8080/projects +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractorName": "Peter File", + "comment": "goal of project", + "startDate": "01.01.2000", + "plannedEndDate": "01.01.2001" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index 68a15a7..92ba734 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -1,51 +1,51 @@ -package de.szut.lf8_starter.exceptionHandling; - -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.ConstraintViolationException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; - -import java.util.Date; - -@ControllerAdvice -@ApiResponses(value = { - @ApiResponse(responseCode = "500", description = "invalid JSON posted", - content = @Content) -}) -public class GlobalExceptionHandler { - - @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { - String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); - - ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } -} +package de.szut.lf8_starter.exceptionHandling; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +import java.util.Date; + +@ControllerAdvice +@ApiResponses(value = { + @ApiResponse(responseCode = "500", description = "invalid JSON posted", + content = @Content) +}) +public class GlobalExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { + String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); + + ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java index 86ce087..75b8c60 100644 --- a/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java @@ -1,43 +1,43 @@ -package de.szut.lf8_starter.project; - -import com.fasterxml.jackson.annotation.JsonFormat; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -public class GetProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate startDate; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate plannedEndDate; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class GetProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java index d9c35a1..82563fe 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java @@ -1,43 +1,43 @@ -package de.szut.lf8_starter.project; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.annotation.CreatedDate; - -import java.time.LocalDate; -import java.util.List; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@Entity -@Table(name = "projects") -public class ProjectEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - private String name; - - private long leadingEmployee; - - @ElementCollection - private List employees; - - private long contractor; - - private String contractorName; - - private String comment; - - @CreatedDate - private LocalDate startDate; - - private LocalDate plannedEndDate; - - private LocalDate endDate; -} +package de.szut.lf8_starter.project; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDate; +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "projects") +public class ProjectEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String name; + + private long leadingEmployee; + + @ElementCollection + private List employees; + + private long contractor; + + private String contractorName; + + private String comment; + + @CreatedDate + private LocalDate startDate; + + private LocalDate plannedEndDate; + + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java index f1c6cdc..511c5ee 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java @@ -1,42 +1,42 @@ -package de.szut.lf8_starter.project.dto; - -import de.szut.lf8_starter.project.GetProjectDto; -import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectMapper; -import de.szut.lf8_starter.project.ProjectService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping(value = "/projects") -public class CreateProjectAction { - private final ProjectService projectService; - private final ProjectMapper projectMapper; - - public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { - this.projectService = projectService; - this.projectMapper = mappingService; - } - - @Operation(summary = "Creates a new Project") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), - @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) - @PostMapping - public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); - - projectEntity = this.projectService.create(projectEntity); - - return this.projectMapper.mapToGetDto(projectEntity); - } -} +package de.szut.lf8_starter.project.dto; + +import de.szut.lf8_starter.project.GetProjectDto; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/projects") +public class CreateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Creates a new Project") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @PostMapping + public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + + projectEntity = this.projectService.create(projectEntity); + + return this.projectMapper.mapToGetDto(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java index 4c6ab0d..f27e7f4 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -1,42 +1,42 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -public class CreateProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class CreateProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} From a5c627ccaa2181a99d6f84099514f24e7b3313fb Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 12:56:18 +0200 Subject: [PATCH 03/31] Line ending --- GetBearerToken.http | 10 +- config/checkstyle/checkstyle-ignore.xml | 18 +- config/checkstyle/checkstyle.xml | 46 +- gradle/wrapper/gradle-wrapper.properties | 14 +- gradlew | 504 +++++++++--------- requests/createProject.http | 28 +- .../config/OpenAPIConfiguration.java | 120 ++--- .../GlobalExceptionHandler.java | 102 ++-- .../lf8_starter/project/GetProjectDto.java | 86 +-- .../lf8_starter/project/ProjectEntity.java | 86 +-- .../project/dto/CreateProjectAction.java | 84 +-- .../project/dto/CreateProjectDto.java | 84 +-- .../security/KeycloakLogoutHandler.java | 98 ++-- .../resources/application-test.properties | 8 +- .../Lf8StarterApplicationTests.java | 28 +- 15 files changed, 658 insertions(+), 658 deletions(-) diff --git a/GetBearerToken.http b/GetBearerToken.http index abf7e94..f5209ca 100644 --- a/GetBearerToken.http +++ b/GetBearerToken.http @@ -1,6 +1,6 @@ -POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token -Content-Type: application/x-www-form-urlencoded - -grant_type=password&client_id=employee-management-service&username=user&password=test - +POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token +Content-Type: application/x-www-form-urlencoded + +grant_type=password&client_id=employee-management-service&username=user&password=test + > {% client.global.set("auth_token", response.body.access_token); %} \ No newline at end of file diff --git a/config/checkstyle/checkstyle-ignore.xml b/config/checkstyle/checkstyle-ignore.xml index 5ec61a5..fe3b2fe 100644 --- a/config/checkstyle/checkstyle-ignore.xml +++ b/config/checkstyle/checkstyle-ignore.xml @@ -1,9 +1,9 @@ - - - - - - + + + + + + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 3c18de8..d8f0701 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b9ddd88..df97d72 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index ec19934..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -1,252 +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" "$@" +#!/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/requests/createProject.http b/requests/createProject.http index 6854828..add2394 100644 --- a/requests/createProject.http +++ b/requests/createProject.http @@ -1,15 +1,15 @@ -### GET request to example server -POST http://localhost:8080/projects -Authorization: Bearer {{auth_token}} -Content-Type: application/json - -{ - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractorName": "Peter File", - "comment": "goal of project", - "startDate": "01.01.2000", - "plannedEndDate": "01.01.2001" +### GET request to example server +POST http://localhost:8080/projects +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractorName": "Peter File", + "comment": "goal of project", + "startDate": "01.01.2000", + "plannedEndDate": "01.01.2001" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java index 76a6f58..1b2282b 100644 --- a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java +++ b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java @@ -1,60 +1,60 @@ -package de.szut.lf8_starter.config; - - - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; -import jakarta.servlet.ServletContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - -@Configuration -public class OpenAPIConfiguration { - - private ServletContext context; - - public OpenAPIConfiguration(ServletContext context) { - this.context = context; - } - - - @Bean - public OpenAPI springShopOpenAPI( - // @Value("${info.app.version}") String appVersion, - ) { - final String securitySchemeName = "bearerAuth"; - - return new OpenAPI() - .addServersItem(new Server().url(this.context.getContextPath())) - .info(new Info() - .title("LF8 project starter") - .description("\n## Auth\n" + - "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + - "\n" + - "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + - "\n" + - "\nor by CURL\n" + - "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + - "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") - - .version("0.1")) - .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) - .components( - new Components() - .addSecuritySchemes(securitySchemeName, - new SecurityScheme() - .name(securitySchemeName) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - ) - ); - } - - -} +package de.szut.lf8_starter.config; + + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import jakarta.servlet.ServletContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class OpenAPIConfiguration { + + private ServletContext context; + + public OpenAPIConfiguration(ServletContext context) { + this.context = context; + } + + + @Bean + public OpenAPI springShopOpenAPI( + // @Value("${info.app.version}") String appVersion, + ) { + final String securitySchemeName = "bearerAuth"; + + return new OpenAPI() + .addServersItem(new Server().url(this.context.getContextPath())) + .info(new Info() + .title("LF8 project starter") + .description("\n## Auth\n" + + "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + + "\n" + + "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + + "\n" + + "\nor by CURL\n" + + "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + + "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") + + .version("0.1")) + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) + .components( + new Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } + + +} diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index 92ba734..68a15a7 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -1,51 +1,51 @@ -package de.szut.lf8_starter.exceptionHandling; - -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.ConstraintViolationException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; - -import java.util.Date; - -@ControllerAdvice -@ApiResponses(value = { - @ApiResponse(responseCode = "500", description = "invalid JSON posted", - content = @Content) -}) -public class GlobalExceptionHandler { - - @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { - String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); - - ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } -} +package de.szut.lf8_starter.exceptionHandling; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +import java.util.Date; + +@ControllerAdvice +@ApiResponses(value = { + @ApiResponse(responseCode = "500", description = "invalid JSON posted", + content = @Content) +}) +public class GlobalExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { + String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); + + ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java index 75b8c60..86ce087 100644 --- a/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java @@ -1,43 +1,43 @@ -package de.szut.lf8_starter.project; - -import com.fasterxml.jackson.annotation.JsonFormat; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -public class GetProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate startDate; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate plannedEndDate; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class GetProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java index 82563fe..d9c35a1 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java @@ -1,43 +1,43 @@ -package de.szut.lf8_starter.project; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.annotation.CreatedDate; - -import java.time.LocalDate; -import java.util.List; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@Entity -@Table(name = "projects") -public class ProjectEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - private String name; - - private long leadingEmployee; - - @ElementCollection - private List employees; - - private long contractor; - - private String contractorName; - - private String comment; - - @CreatedDate - private LocalDate startDate; - - private LocalDate plannedEndDate; - - private LocalDate endDate; -} +package de.szut.lf8_starter.project; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDate; +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "projects") +public class ProjectEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String name; + + private long leadingEmployee; + + @ElementCollection + private List employees; + + private long contractor; + + private String contractorName; + + private String comment; + + @CreatedDate + private LocalDate startDate; + + private LocalDate plannedEndDate; + + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java index 511c5ee..f1c6cdc 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java @@ -1,42 +1,42 @@ -package de.szut.lf8_starter.project.dto; - -import de.szut.lf8_starter.project.GetProjectDto; -import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectMapper; -import de.szut.lf8_starter.project.ProjectService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping(value = "/projects") -public class CreateProjectAction { - private final ProjectService projectService; - private final ProjectMapper projectMapper; - - public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { - this.projectService = projectService; - this.projectMapper = mappingService; - } - - @Operation(summary = "Creates a new Project") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), - @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) - @PostMapping - public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); - - projectEntity = this.projectService.create(projectEntity); - - return this.projectMapper.mapToGetDto(projectEntity); - } -} +package de.szut.lf8_starter.project.dto; + +import de.szut.lf8_starter.project.GetProjectDto; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/projects") +public class CreateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Creates a new Project") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @PostMapping + public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + + projectEntity = this.projectService.create(projectEntity); + + return this.projectMapper.mapToGetDto(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java index f27e7f4..4c6ab0d 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -1,42 +1,42 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -public class CreateProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class CreateProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java index e06f2f7..8555ef9 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java @@ -1,49 +1,49 @@ -package de.szut.lf8_starter.security; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Slf4j -@Component -public class KeycloakLogoutHandler implements LogoutHandler { - - - private final RestTemplate restTemplate; - - public KeycloakLogoutHandler(RestTemplate restTemplate) { - this.restTemplate = restTemplate; - } - - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { - logout(request, auth); - } - - public void logout(HttpServletRequest request, Authentication auth) { - logoutFromKeycloak((OidcUser) auth.getPrincipal()); - } - - private void logoutFromKeycloak(OidcUser user) { - String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; - UriComponentsBuilder builder = UriComponentsBuilder - .fromUriString(endSessionEndpoint) - .queryParam("id_token_hint", user.getIdToken().getTokenValue()); - - ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); - if (logoutResponse.getStatusCode().is2xxSuccessful()) { - log.info("Successfulley logged out from Keycloak"); - } else { - log.error("Could not propagate logout to Keycloak"); - } - } - -} +package de.szut.lf8_starter.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Slf4j +@Component +public class KeycloakLogoutHandler implements LogoutHandler { + + + private final RestTemplate restTemplate; + + public KeycloakLogoutHandler(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { + logout(request, auth); + } + + public void logout(HttpServletRequest request, Authentication auth) { + logoutFromKeycloak((OidcUser) auth.getPrincipal()); + } + + private void logoutFromKeycloak(OidcUser user) { + String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; + UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(endSessionEndpoint) + .queryParam("id_token_hint", user.getIdToken().getTokenValue()); + + ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + log.info("Successfulley logged out from Keycloak"); + } else { + log.error("Could not propagate logout to Keycloak"); + } + } + +} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 259f612..af21e0e 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -1,5 +1,5 @@ -spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password= +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java index d418f1a..b5c7f7b 100644 --- a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java +++ b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java @@ -1,14 +1,14 @@ -package de.szut.lf8_starter; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; - -@SpringBootTest -class Lf8StarterApplicationTests { - - @Test - void contextLoads() { - } - -} +package de.szut.lf8_starter; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest +class Lf8StarterApplicationTests { + + @Test + void contextLoads() { + } + +} From e209379f41d3d26ab958d379d3b7404f770c4026 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 12:58:28 +0200 Subject: [PATCH 04/31] Order files --- .../lf8_starter/project/GetProjectDto.java | 43 ------------------- .../project/dto/CreateProjectAction.java | 42 ------------------ 2 files changed, 85 deletions(-) delete mode 100644 src/main/java/de/szut/lf8_starter/project/GetProjectDto.java delete mode 100644 src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java diff --git a/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java deleted file mode 100644 index 86ce087..0000000 --- a/src/main/java/de/szut/lf8_starter/project/GetProjectDto.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.szut.lf8_starter.project; - -import com.fasterxml.jackson.annotation.JsonFormat; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -public class GetProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate startDate; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate plannedEndDate; - - @NotNull - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java deleted file mode 100644 index f1c6cdc..0000000 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectAction.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.szut.lf8_starter.project.dto; - -import de.szut.lf8_starter.project.GetProjectDto; -import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectMapper; -import de.szut.lf8_starter.project.ProjectService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping(value = "/projects") -public class CreateProjectAction { - private final ProjectService projectService; - private final ProjectMapper projectMapper; - - public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { - this.projectService = projectService; - this.projectMapper = mappingService; - } - - @Operation(summary = "Creates a new Project") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), - @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) - @PostMapping - public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); - - projectEntity = this.projectService.create(projectEntity); - - return this.projectMapper.mapToGetDto(projectEntity); - } -} From 6723b49f7dcedeba69b958c0eb66e3f6d1f00cbd Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 14:05:31 +0200 Subject: [PATCH 05/31] Write integration test --- .../project/CreateProjectAction.java | 7 +- .../project/CreateProjectActionTest.java | 70 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/test/java/de/szut/lf8_starter/project/CreateProjectActionTest.java diff --git a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java index c41b134..d54318b 100644 --- a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java @@ -8,10 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/projects") @@ -30,6 +28,7 @@ public class CreateProjectAction { @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) @PostMapping + @ResponseStatus(code = HttpStatus.CREATED) public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); diff --git a/src/test/java/de/szut/lf8_starter/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/project/CreateProjectActionTest.java new file mode 100644 index 0000000..b510ac7 --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/project/CreateProjectActionTest.java @@ -0,0 +1,70 @@ +package de.szut.lf8_starter.project; + +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class CreateProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void test() throws Exception { + String content = """ + { + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractorName": "Peter File", + "comment": "goal of project", + "startDate": "01.01.2000", + "plannedEndDate": "01.01.2001" + } + """; + + final var contentAsString = this.mockMvc.perform( + post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("name", is("name"))) + .andExpect(jsonPath("leadingEmployee", is(0))) + .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) + .andExpect(jsonPath("contractor", is(4))) + .andExpect(jsonPath("contractorName", is("Peter File"))) + .andExpect(jsonPath("comment", is("goal of project"))) + .andExpect(jsonPath("startDate", is("01.01.2000"))) + .andExpect(jsonPath("plannedEndDate", is("01.01.2001"))) + .andReturn() + .getResponse() + .getContentAsString(); + + final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + + final var project = this.projectRepository.findById(id); + assertThat(project.get().getName()).isEqualTo("name"); + assertThat(project.get().getLeadingEmployee()).isEqualTo(0); + assertThat(project.get().getContractor()).isEqualTo(4); + assertThat(project.get().getContractorName()).isEqualTo("Peter File"); + assertThat(project.get().getComment()).isEqualTo("goal of project"); + assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); + assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); + } +} From ffb1a1586a8e04f131e79321ea782249cc6783e7 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 14:16:27 +0200 Subject: [PATCH 06/31] Ensure Snake Case in JSON --- requests/createProject.http | 6 ++-- .../project/dto/CreateProjectDto.java | 3 ++ .../project/dto/GetProjectDto.java | 17 ++-------- .../project/CreateProjectActionTest.java | 31 ++++++++++--------- 4 files changed, 25 insertions(+), 32 deletions(-) rename src/test/java/de/szut/lf8_starter/{ => integration}/project/CreateProjectActionTest.java (75%) diff --git a/requests/createProject.http b/requests/createProject.http index add2394..7b08e28 100644 --- a/requests/createProject.http +++ b/requests/createProject.http @@ -8,8 +8,8 @@ Content-Type: application/json "leading_employee": 1, "employees": [2, 3], "contractor": 4, - "contractorName": "Peter File", + "contractor_name": "Peter File", "comment": "goal of project", - "startDate": "01.01.2000", - "plannedEndDate": "01.01.2001" + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java index 4c6ab0d..ebf4393 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -1,6 +1,8 @@ package de.szut.lf8_starter.project.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -11,6 +13,7 @@ import java.util.List; @Getter @Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class CreateProjectDto { @NotBlank private String name; diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java index c90cd50..614ece1 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java @@ -1,6 +1,8 @@ package de.szut.lf8_starter.project.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -11,35 +13,22 @@ import java.util.List; @Getter @Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class GetProjectDto { private long id; - - @NotBlank private String name; - - @NotNull private long leadingEmployee; - private List employees; - - @NotNull private long contractor; - - @NotBlank private String contractorName; - - @NotBlank private String comment; - @NotNull @JsonFormat(pattern = "dd.MM.yyyy") private LocalDate startDate; - @NotNull @JsonFormat(pattern = "dd.MM.yyyy") private LocalDate plannedEndDate; - @NotNull @JsonFormat(pattern = "dd.MM.yyyy") private LocalDate endDate; } diff --git a/src/test/java/de/szut/lf8_starter/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java similarity index 75% rename from src/test/java/de/szut/lf8_starter/project/CreateProjectActionTest.java rename to src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java index b510ac7..22e40f8 100644 --- a/src/test/java/de/szut/lf8_starter/project/CreateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -1,5 +1,6 @@ -package de.szut.lf8_starter.project; +package de.szut.lf8_starter.integration.project; +import de.szut.lf8_starter.project.ProjectRepository; import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,15 +30,15 @@ public class CreateProjectActionTest { void test() throws Exception { String content = """ { - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractorName": "Peter File", - "comment": "goal of project", - "startDate": "01.01.2000", - "plannedEndDate": "01.01.2001" - } + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" + } """; final var contentAsString = this.mockMvc.perform( @@ -45,13 +46,13 @@ public class CreateProjectActionTest { ) .andExpect(status().isCreated()) .andExpect(jsonPath("name", is("name"))) - .andExpect(jsonPath("leadingEmployee", is(0))) + .andExpect(jsonPath("leading_employee", is(1))) .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) .andExpect(jsonPath("contractor", is(4))) - .andExpect(jsonPath("contractorName", is("Peter File"))) + .andExpect(jsonPath("contractor_name", is("Peter File"))) .andExpect(jsonPath("comment", is("goal of project"))) - .andExpect(jsonPath("startDate", is("01.01.2000"))) - .andExpect(jsonPath("plannedEndDate", is("01.01.2001"))) + .andExpect(jsonPath("start_date", is("01.01.2000"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) .andReturn() .getResponse() .getContentAsString(); @@ -60,7 +61,7 @@ public class CreateProjectActionTest { final var project = this.projectRepository.findById(id); assertThat(project.get().getName()).isEqualTo("name"); - assertThat(project.get().getLeadingEmployee()).isEqualTo(0); + assertThat(project.get().getLeadingEmployee()).isEqualTo(1); assertThat(project.get().getContractor()).isEqualTo(4); assertThat(project.get().getContractorName()).isEqualTo("Peter File"); assertThat(project.get().getComment()).isEqualTo("goal of project"); From a365c7aee32dfd08fb1c5066d55e641235d60e99 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 14:22:26 +0200 Subject: [PATCH 07/31] Fix tests --- .gitea/workflows/release.yml | 62 +-- GetBearerToken.http | 10 +- config/checkstyle/checkstyle-ignore.xml | 18 +- config/checkstyle/checkstyle.xml | 46 +- gradle/wrapper/gradle-wrapper.properties | 14 +- gradlew | 504 +++++++++--------- release.config.cjs | 24 +- requests/createProject.http | 28 +- .../config/OpenAPIConfiguration.java | 120 ++--- .../GlobalExceptionHandler.java | 102 ++-- .../project/CreateProjectAction.java | 78 +-- .../lf8_starter/project/ProjectEntity.java | 86 +-- .../project/dto/CreateProjectDto.java | 90 ++-- .../project/dto/GetProjectDto.java | 68 +-- .../security/KeycloakLogoutHandler.java | 98 ++-- .../resources/application-test.properties | 8 +- .../Lf8StarterApplicationTests.java | 28 +- .../project/CreateProjectActionTest.java | 142 ++--- ...ss.java => ProjectFindAllSuccessTest.java} | 16 +- 19 files changed, 769 insertions(+), 773 deletions(-) rename src/test/java/de/szut/lf8_starter/integration/project/{ProjectFindAllSuccess.java => ProjectFindAllSuccessTest.java} (80%) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 1c29ae9..a04d812 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,31 +1,31 @@ -name: Release -on: - push: - branches: - - main - -env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - -permissions: - contents: read # for checkout - -concurrency: - group: ${{ github.ref }} # Ensure each branch has its own group - cancel-in-progress: false # Prevent new runs from canceling in-progress runs - -jobs: - release: - name: Release - runs-on: ubuntu-latest - permissions: - contents: write # to be able to publish a GitHub release - issues: write # to be able to comment on released issues - pull-requests: write # to be able to comment on released pull requests - id-token: write # to enable use of OIDC for npm provenance - steps: - - name: Create Release - uses: https://git.kjan.de/actions/semantic-release@main - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} +name: Release +on: + push: + branches: + - main + +env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + +permissions: + contents: read # for checkout + +concurrency: + group: ${{ github.ref }} # Ensure each branch has its own group + cancel-in-progress: false # Prevent new runs from canceling in-progress runs + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues + pull-requests: write # to be able to comment on released pull requests + id-token: write # to enable use of OIDC for npm provenance + steps: + - name: Create Release + uses: https://git.kjan.de/actions/semantic-release@main + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} diff --git a/GetBearerToken.http b/GetBearerToken.http index f5209ca..abf7e94 100644 --- a/GetBearerToken.http +++ b/GetBearerToken.http @@ -1,6 +1,6 @@ -POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token -Content-Type: application/x-www-form-urlencoded - -grant_type=password&client_id=employee-management-service&username=user&password=test - +POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token +Content-Type: application/x-www-form-urlencoded + +grant_type=password&client_id=employee-management-service&username=user&password=test + > {% client.global.set("auth_token", response.body.access_token); %} \ No newline at end of file diff --git a/config/checkstyle/checkstyle-ignore.xml b/config/checkstyle/checkstyle-ignore.xml index fe3b2fe..5ec61a5 100644 --- a/config/checkstyle/checkstyle-ignore.xml +++ b/config/checkstyle/checkstyle-ignore.xml @@ -1,9 +1,9 @@ - - - - - - + + + + + + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index d8f0701..3c18de8 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72..b9ddd88 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index f5feea6..ec19934 100755 --- a/gradlew +++ b/gradlew @@ -1,252 +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" "$@" +#!/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/release.config.cjs b/release.config.cjs index 375ccd5..1b62d04 100644 --- a/release.config.cjs +++ b/release.config.cjs @@ -1,12 +1,12 @@ -module.exports = { - - branches: ['main'], - plugins: [ - '@semantic-release/commit-analyzer', - '@semantic-release/release-notes-generator', - '@semantic-release/changelog', - ["@saithodev/semantic-release-gitea", { - "giteaUrl": "https://git.kjan.de" - }], - ], -}; +module.exports = { + + branches: ['main'], + plugins: [ + '@semantic-release/commit-analyzer', + '@semantic-release/release-notes-generator', + '@semantic-release/changelog', + ["@saithodev/semantic-release-gitea", { + "giteaUrl": "https://git.kjan.de" + }], + ], +}; diff --git a/requests/createProject.http b/requests/createProject.http index 7b08e28..885c49e 100644 --- a/requests/createProject.http +++ b/requests/createProject.http @@ -1,15 +1,15 @@ -### GET request to example server -POST http://localhost:8080/projects -Authorization: Bearer {{auth_token}} -Content-Type: application/json - -{ - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractor_name": "Peter File", - "comment": "goal of project", - "start_date": "01.01.2000", - "planned_end_date": "01.01.2001" +### GET request to example server +POST http://localhost:8080/projects +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java index 1b2282b..76a6f58 100644 --- a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java +++ b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java @@ -1,60 +1,60 @@ -package de.szut.lf8_starter.config; - - - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; -import jakarta.servlet.ServletContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - -@Configuration -public class OpenAPIConfiguration { - - private ServletContext context; - - public OpenAPIConfiguration(ServletContext context) { - this.context = context; - } - - - @Bean - public OpenAPI springShopOpenAPI( - // @Value("${info.app.version}") String appVersion, - ) { - final String securitySchemeName = "bearerAuth"; - - return new OpenAPI() - .addServersItem(new Server().url(this.context.getContextPath())) - .info(new Info() - .title("LF8 project starter") - .description("\n## Auth\n" + - "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + - "\n" + - "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + - "\n" + - "\nor by CURL\n" + - "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + - "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") - - .version("0.1")) - .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) - .components( - new Components() - .addSecuritySchemes(securitySchemeName, - new SecurityScheme() - .name(securitySchemeName) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - ) - ); - } - - -} +package de.szut.lf8_starter.config; + + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import jakarta.servlet.ServletContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class OpenAPIConfiguration { + + private ServletContext context; + + public OpenAPIConfiguration(ServletContext context) { + this.context = context; + } + + + @Bean + public OpenAPI springShopOpenAPI( + // @Value("${info.app.version}") String appVersion, + ) { + final String securitySchemeName = "bearerAuth"; + + return new OpenAPI() + .addServersItem(new Server().url(this.context.getContextPath())) + .info(new Info() + .title("LF8 project starter") + .description("\n## Auth\n" + + "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + + "\n" + + "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + + "\n" + + "\nor by CURL\n" + + "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + + "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") + + .version("0.1")) + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) + .components( + new Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } + + +} diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index 68a15a7..92ba734 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -1,51 +1,51 @@ -package de.szut.lf8_starter.exceptionHandling; - -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.ConstraintViolationException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; - -import java.util.Date; - -@ControllerAdvice -@ApiResponses(value = { - @ApiResponse(responseCode = "500", description = "invalid JSON posted", - content = @Content) -}) -public class GlobalExceptionHandler { - - @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { - String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); - - ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } -} +package de.szut.lf8_starter.exceptionHandling; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +import java.util.Date; + +@ControllerAdvice +@ApiResponses(value = { + @ApiResponse(responseCode = "500", description = "invalid JSON posted", + content = @Content) +}) +public class GlobalExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { + String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); + + ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java index d54318b..4158fee 100644 --- a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java @@ -1,39 +1,39 @@ -package de.szut.lf8_starter.project; - -import de.szut.lf8_starter.project.dto.CreateProjectDto; -import de.szut.lf8_starter.project.dto.GetProjectDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping(value = "/projects") -public class CreateProjectAction { - private final ProjectService projectService; - private final ProjectMapper projectMapper; - - public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { - this.projectService = projectService; - this.projectMapper = mappingService; - } - - @Operation(summary = "Creates a new Project") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), - @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) - @PostMapping - @ResponseStatus(code = HttpStatus.CREATED) - public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); - - projectEntity = this.projectService.create(projectEntity); - - return this.projectMapper.mapToGetDto(projectEntity); - } -} +package de.szut.lf8_starter.project; + +import de.szut.lf8_starter.project.dto.CreateProjectDto; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(value = "/projects") +public class CreateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Creates a new Project") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @PostMapping + @ResponseStatus(code = HttpStatus.CREATED) + public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + + projectEntity = this.projectService.create(projectEntity); + + return this.projectMapper.mapToGetDto(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java index d9c35a1..82563fe 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java @@ -1,43 +1,43 @@ -package de.szut.lf8_starter.project; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.annotation.CreatedDate; - -import java.time.LocalDate; -import java.util.List; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@Entity -@Table(name = "projects") -public class ProjectEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - private String name; - - private long leadingEmployee; - - @ElementCollection - private List employees; - - private long contractor; - - private String contractorName; - - private String comment; - - @CreatedDate - private LocalDate startDate; - - private LocalDate plannedEndDate; - - private LocalDate endDate; -} +package de.szut.lf8_starter.project; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDate; +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "projects") +public class ProjectEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String name; + + private long leadingEmployee; + + @ElementCollection + private List employees; + + private long contractor; + + private String contractorName; + + private String comment; + + @CreatedDate + private LocalDate startDate; + + private LocalDate plannedEndDate; + + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java index ebf4393..64d22b7 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -1,45 +1,45 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class CreateProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class CreateProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java index 614ece1..250a6a0 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java @@ -1,34 +1,34 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class GetProjectDto { - private long id; - private String name; - private long leadingEmployee; - private List employees; - private long contractor; - private String contractorName; - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class GetProjectDto { + private long id; + private String name; + private long leadingEmployee; + private List employees; + private long contractor; + private String contractorName; + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java index 8555ef9..e06f2f7 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java @@ -1,49 +1,49 @@ -package de.szut.lf8_starter.security; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Slf4j -@Component -public class KeycloakLogoutHandler implements LogoutHandler { - - - private final RestTemplate restTemplate; - - public KeycloakLogoutHandler(RestTemplate restTemplate) { - this.restTemplate = restTemplate; - } - - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { - logout(request, auth); - } - - public void logout(HttpServletRequest request, Authentication auth) { - logoutFromKeycloak((OidcUser) auth.getPrincipal()); - } - - private void logoutFromKeycloak(OidcUser user) { - String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; - UriComponentsBuilder builder = UriComponentsBuilder - .fromUriString(endSessionEndpoint) - .queryParam("id_token_hint", user.getIdToken().getTokenValue()); - - ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); - if (logoutResponse.getStatusCode().is2xxSuccessful()) { - log.info("Successfulley logged out from Keycloak"); - } else { - log.error("Could not propagate logout to Keycloak"); - } - } - -} +package de.szut.lf8_starter.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Slf4j +@Component +public class KeycloakLogoutHandler implements LogoutHandler { + + + private final RestTemplate restTemplate; + + public KeycloakLogoutHandler(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { + logout(request, auth); + } + + public void logout(HttpServletRequest request, Authentication auth) { + logoutFromKeycloak((OidcUser) auth.getPrincipal()); + } + + private void logoutFromKeycloak(OidcUser user) { + String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; + UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(endSessionEndpoint) + .queryParam("id_token_hint", user.getIdToken().getTokenValue()); + + ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + log.info("Successfulley logged out from Keycloak"); + } else { + log.error("Could not propagate logout to Keycloak"); + } + } + +} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index af21e0e..259f612 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -1,5 +1,5 @@ -spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password= +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java index b5c7f7b..d418f1a 100644 --- a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java +++ b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java @@ -1,14 +1,14 @@ -package de.szut.lf8_starter; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; - -@SpringBootTest -class Lf8StarterApplicationTests { - - @Test - void contextLoads() { - } - -} +package de.szut.lf8_starter; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest +class Lf8StarterApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java index 22e40f8..06d02b6 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -1,71 +1,71 @@ -package de.szut.lf8_starter.integration.project; - -import de.szut.lf8_starter.project.ProjectRepository; -import org.json.JSONObject; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import java.time.LocalDate; -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc(addFilters = false) -public class CreateProjectActionTest { - @Autowired - private MockMvc mockMvc; - @Autowired - private ProjectRepository projectRepository; - - @Test - void test() throws Exception { - String content = """ - { - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractor_name": "Peter File", - "comment": "goal of project", - "start_date": "01.01.2000", - "planned_end_date": "01.01.2001" - } - """; - - final var contentAsString = this.mockMvc.perform( - post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isCreated()) - .andExpect(jsonPath("name", is("name"))) - .andExpect(jsonPath("leading_employee", is(1))) - .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) - .andExpect(jsonPath("contractor", is(4))) - .andExpect(jsonPath("contractor_name", is("Peter File"))) - .andExpect(jsonPath("comment", is("goal of project"))) - .andExpect(jsonPath("start_date", is("01.01.2000"))) - .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) - .andReturn() - .getResponse() - .getContentAsString(); - - final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); - - final var project = this.projectRepository.findById(id); - assertThat(project.get().getName()).isEqualTo("name"); - assertThat(project.get().getLeadingEmployee()).isEqualTo(1); - assertThat(project.get().getContractor()).isEqualTo(4); - assertThat(project.get().getContractorName()).isEqualTo("Peter File"); - assertThat(project.get().getComment()).isEqualTo("goal of project"); - assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); - assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); - } -} +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectRepository; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class CreateProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void createProjectTest() throws Exception { + String content = """ + { + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" + } + """; + + final var contentAsString = this.mockMvc.perform( + post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("name", is("name"))) + .andExpect(jsonPath("leading_employee", is(1))) + .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) + .andExpect(jsonPath("contractor", is(4))) + .andExpect(jsonPath("contractor_name", is("Peter File"))) + .andExpect(jsonPath("comment", is("goal of project"))) + .andExpect(jsonPath("start_date", is("01.01.2000"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) + .andReturn() + .getResponse() + .getContentAsString(); + + final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + + final var project = this.projectRepository.findById(id); + assertThat(project.get().getName()).isEqualTo("name"); + assertThat(project.get().getLeadingEmployee()).isEqualTo(1); + assertThat(project.get().getContractor()).isEqualTo(4); + assertThat(project.get().getContractorName()).isEqualTo("Peter File"); + assertThat(project.get().getComment()).isEqualTo("goal of project"); + assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); + assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccess.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java similarity index 80% rename from src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccess.java rename to src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java index 906b63f..8f1974a 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccess.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; @@ -19,18 +18,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc(addFilters = false) -public class ProjectFindAllSuccess { +public class ProjectFindAllSuccessTest { @Autowired private ProjectRepository projectRepository; @Autowired private MockMvc mockMvc; - @Autowired - private TestRestTemplate restTemplate; - @Test - void findAllProjects() throws Exception { + void findAllProjectsTest() throws Exception { var project = new ProjectEntity(); project.setId(1); project.setComment("comment"); @@ -49,11 +45,11 @@ public class ProjectFindAllSuccess { .andExpect(jsonPath("$[0].id").value(1)) .andExpect(jsonPath("$[0].comment").value("comment")) .andExpect(jsonPath("$[0].contractor").value(1)) - .andExpect(jsonPath("$[0].contractorName").value("contractorName")) - .andExpect(jsonPath("$[0].endDate").value("01.01.2024")) - .andExpect(jsonPath("$[0].leadingEmployee").value(1)) + .andExpect(jsonPath("$[0].contractor_name").value("contractorName")) + .andExpect(jsonPath("$[0].end_date").value("01.01.2024")) + .andExpect(jsonPath("$[0].leading_employee").value(1)) .andExpect(jsonPath("$[0].name").value("name")) - .andExpect(jsonPath("$[0].startDate").value("01.01.2021")) + .andExpect(jsonPath("$[0].start_date").value("01.01.2021")) .andExpect(jsonPath("$[0].employees").isArray()) .andExpect(jsonPath("$[0].employees", hasSize(3))); } From 79300c77ee17017b3b370a0dc71ca4272e1b3fde Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 14:25:55 +0200 Subject: [PATCH 08/31] Fix line endings --- .gitea/workflows/release.yml | 62 +-- GetBearerToken.http | 10 +- config/checkstyle/checkstyle-ignore.xml | 18 +- config/checkstyle/checkstyle.xml | 46 +- gradle/wrapper/gradle-wrapper.properties | 14 +- gradlew | 504 +++++++++--------- release.config.cjs | 24 +- requests/createProject.http | 28 +- .../config/OpenAPIConfiguration.java | 120 ++--- .../GlobalExceptionHandler.java | 102 ++-- .../project/CreateProjectAction.java | 78 +-- .../lf8_starter/project/ProjectEntity.java | 86 +-- .../project/dto/CreateProjectDto.java | 90 ++-- .../project/dto/GetProjectDto.java | 68 +-- .../security/KeycloakLogoutHandler.java | 98 ++-- .../resources/application-test.properties | 8 +- .../Lf8StarterApplicationTests.java | 28 +- .../project/CreateProjectActionTest.java | 142 ++--- 18 files changed, 763 insertions(+), 763 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index a04d812..1c29ae9 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,31 +1,31 @@ -name: Release -on: - push: - branches: - - main - -env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - -permissions: - contents: read # for checkout - -concurrency: - group: ${{ github.ref }} # Ensure each branch has its own group - cancel-in-progress: false # Prevent new runs from canceling in-progress runs - -jobs: - release: - name: Release - runs-on: ubuntu-latest - permissions: - contents: write # to be able to publish a GitHub release - issues: write # to be able to comment on released issues - pull-requests: write # to be able to comment on released pull requests - id-token: write # to enable use of OIDC for npm provenance - steps: - - name: Create Release - uses: https://git.kjan.de/actions/semantic-release@main - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} +name: Release +on: + push: + branches: + - main + +env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + +permissions: + contents: read # for checkout + +concurrency: + group: ${{ github.ref }} # Ensure each branch has its own group + cancel-in-progress: false # Prevent new runs from canceling in-progress runs + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues + pull-requests: write # to be able to comment on released pull requests + id-token: write # to enable use of OIDC for npm provenance + steps: + - name: Create Release + uses: https://git.kjan.de/actions/semantic-release@main + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} diff --git a/GetBearerToken.http b/GetBearerToken.http index abf7e94..f5209ca 100644 --- a/GetBearerToken.http +++ b/GetBearerToken.http @@ -1,6 +1,6 @@ -POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token -Content-Type: application/x-www-form-urlencoded - -grant_type=password&client_id=employee-management-service&username=user&password=test - +POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token +Content-Type: application/x-www-form-urlencoded + +grant_type=password&client_id=employee-management-service&username=user&password=test + > {% client.global.set("auth_token", response.body.access_token); %} \ No newline at end of file diff --git a/config/checkstyle/checkstyle-ignore.xml b/config/checkstyle/checkstyle-ignore.xml index 5ec61a5..fe3b2fe 100644 --- a/config/checkstyle/checkstyle-ignore.xml +++ b/config/checkstyle/checkstyle-ignore.xml @@ -1,9 +1,9 @@ - - - - - - + + + + + + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 3c18de8..d8f0701 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b9ddd88..df97d72 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index ec19934..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -1,252 +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" "$@" +#!/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/release.config.cjs b/release.config.cjs index 1b62d04..375ccd5 100644 --- a/release.config.cjs +++ b/release.config.cjs @@ -1,12 +1,12 @@ -module.exports = { - - branches: ['main'], - plugins: [ - '@semantic-release/commit-analyzer', - '@semantic-release/release-notes-generator', - '@semantic-release/changelog', - ["@saithodev/semantic-release-gitea", { - "giteaUrl": "https://git.kjan.de" - }], - ], -}; +module.exports = { + + branches: ['main'], + plugins: [ + '@semantic-release/commit-analyzer', + '@semantic-release/release-notes-generator', + '@semantic-release/changelog', + ["@saithodev/semantic-release-gitea", { + "giteaUrl": "https://git.kjan.de" + }], + ], +}; diff --git a/requests/createProject.http b/requests/createProject.http index 885c49e..7b08e28 100644 --- a/requests/createProject.http +++ b/requests/createProject.http @@ -1,15 +1,15 @@ -### GET request to example server -POST http://localhost:8080/projects -Authorization: Bearer {{auth_token}} -Content-Type: application/json - -{ - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractor_name": "Peter File", - "comment": "goal of project", - "start_date": "01.01.2000", - "planned_end_date": "01.01.2001" +### GET request to example server +POST http://localhost:8080/projects +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java index 76a6f58..1b2282b 100644 --- a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java +++ b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java @@ -1,60 +1,60 @@ -package de.szut.lf8_starter.config; - - - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; -import jakarta.servlet.ServletContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - -@Configuration -public class OpenAPIConfiguration { - - private ServletContext context; - - public OpenAPIConfiguration(ServletContext context) { - this.context = context; - } - - - @Bean - public OpenAPI springShopOpenAPI( - // @Value("${info.app.version}") String appVersion, - ) { - final String securitySchemeName = "bearerAuth"; - - return new OpenAPI() - .addServersItem(new Server().url(this.context.getContextPath())) - .info(new Info() - .title("LF8 project starter") - .description("\n## Auth\n" + - "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + - "\n" + - "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + - "\n" + - "\nor by CURL\n" + - "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + - "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") - - .version("0.1")) - .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) - .components( - new Components() - .addSecuritySchemes(securitySchemeName, - new SecurityScheme() - .name(securitySchemeName) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - ) - ); - } - - -} +package de.szut.lf8_starter.config; + + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import jakarta.servlet.ServletContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class OpenAPIConfiguration { + + private ServletContext context; + + public OpenAPIConfiguration(ServletContext context) { + this.context = context; + } + + + @Bean + public OpenAPI springShopOpenAPI( + // @Value("${info.app.version}") String appVersion, + ) { + final String securitySchemeName = "bearerAuth"; + + return new OpenAPI() + .addServersItem(new Server().url(this.context.getContextPath())) + .info(new Info() + .title("LF8 project starter") + .description("\n## Auth\n" + + "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + + "\n" + + "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + + "\n" + + "\nor by CURL\n" + + "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + + "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") + + .version("0.1")) + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) + .components( + new Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } + + +} diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index 92ba734..68a15a7 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -1,51 +1,51 @@ -package de.szut.lf8_starter.exceptionHandling; - -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.ConstraintViolationException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; - -import java.util.Date; - -@ControllerAdvice -@ApiResponses(value = { - @ApiResponse(responseCode = "500", description = "invalid JSON posted", - content = @Content) -}) -public class GlobalExceptionHandler { - - @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { - String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); - - ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } -} +package de.szut.lf8_starter.exceptionHandling; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +import java.util.Date; + +@ControllerAdvice +@ApiResponses(value = { + @ApiResponse(responseCode = "500", description = "invalid JSON posted", + content = @Content) +}) +public class GlobalExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { + String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); + + ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java index 4158fee..d54318b 100644 --- a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java @@ -1,39 +1,39 @@ -package de.szut.lf8_starter.project; - -import de.szut.lf8_starter.project.dto.CreateProjectDto; -import de.szut.lf8_starter.project.dto.GetProjectDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping(value = "/projects") -public class CreateProjectAction { - private final ProjectService projectService; - private final ProjectMapper projectMapper; - - public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { - this.projectService = projectService; - this.projectMapper = mappingService; - } - - @Operation(summary = "Creates a new Project") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), - @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) - @PostMapping - @ResponseStatus(code = HttpStatus.CREATED) - public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); - - projectEntity = this.projectService.create(projectEntity); - - return this.projectMapper.mapToGetDto(projectEntity); - } -} +package de.szut.lf8_starter.project; + +import de.szut.lf8_starter.project.dto.CreateProjectDto; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(value = "/projects") +public class CreateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Creates a new Project") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @PostMapping + @ResponseStatus(code = HttpStatus.CREATED) + public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + + projectEntity = this.projectService.create(projectEntity); + + return this.projectMapper.mapToGetDto(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java index 82563fe..d9c35a1 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java @@ -1,43 +1,43 @@ -package de.szut.lf8_starter.project; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.annotation.CreatedDate; - -import java.time.LocalDate; -import java.util.List; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@Entity -@Table(name = "projects") -public class ProjectEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - private String name; - - private long leadingEmployee; - - @ElementCollection - private List employees; - - private long contractor; - - private String contractorName; - - private String comment; - - @CreatedDate - private LocalDate startDate; - - private LocalDate plannedEndDate; - - private LocalDate endDate; -} +package de.szut.lf8_starter.project; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDate; +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "projects") +public class ProjectEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String name; + + private long leadingEmployee; + + @ElementCollection + private List employees; + + private long contractor; + + private String contractorName; + + private String comment; + + @CreatedDate + private LocalDate startDate; + + private LocalDate plannedEndDate; + + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java index 64d22b7..ebf4393 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -1,45 +1,45 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class CreateProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class CreateProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java index 250a6a0..614ece1 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java @@ -1,34 +1,34 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class GetProjectDto { - private long id; - private String name; - private long leadingEmployee; - private List employees; - private long contractor; - private String contractorName; - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class GetProjectDto { + private long id; + private String name; + private long leadingEmployee; + private List employees; + private long contractor; + private String contractorName; + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java index e06f2f7..8555ef9 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java @@ -1,49 +1,49 @@ -package de.szut.lf8_starter.security; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Slf4j -@Component -public class KeycloakLogoutHandler implements LogoutHandler { - - - private final RestTemplate restTemplate; - - public KeycloakLogoutHandler(RestTemplate restTemplate) { - this.restTemplate = restTemplate; - } - - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { - logout(request, auth); - } - - public void logout(HttpServletRequest request, Authentication auth) { - logoutFromKeycloak((OidcUser) auth.getPrincipal()); - } - - private void logoutFromKeycloak(OidcUser user) { - String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; - UriComponentsBuilder builder = UriComponentsBuilder - .fromUriString(endSessionEndpoint) - .queryParam("id_token_hint", user.getIdToken().getTokenValue()); - - ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); - if (logoutResponse.getStatusCode().is2xxSuccessful()) { - log.info("Successfulley logged out from Keycloak"); - } else { - log.error("Could not propagate logout to Keycloak"); - } - } - -} +package de.szut.lf8_starter.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Slf4j +@Component +public class KeycloakLogoutHandler implements LogoutHandler { + + + private final RestTemplate restTemplate; + + public KeycloakLogoutHandler(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { + logout(request, auth); + } + + public void logout(HttpServletRequest request, Authentication auth) { + logoutFromKeycloak((OidcUser) auth.getPrincipal()); + } + + private void logoutFromKeycloak(OidcUser user) { + String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; + UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(endSessionEndpoint) + .queryParam("id_token_hint", user.getIdToken().getTokenValue()); + + ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + log.info("Successfulley logged out from Keycloak"); + } else { + log.error("Could not propagate logout to Keycloak"); + } + } + +} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 259f612..af21e0e 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -1,5 +1,5 @@ -spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password= +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java index d418f1a..b5c7f7b 100644 --- a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java +++ b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java @@ -1,14 +1,14 @@ -package de.szut.lf8_starter; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; - -@SpringBootTest -class Lf8StarterApplicationTests { - - @Test - void contextLoads() { - } - -} +package de.szut.lf8_starter; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest +class Lf8StarterApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java index 06d02b6..36eee54 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -1,71 +1,71 @@ -package de.szut.lf8_starter.integration.project; - -import de.szut.lf8_starter.project.ProjectRepository; -import org.json.JSONObject; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import java.time.LocalDate; -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc(addFilters = false) -public class CreateProjectActionTest { - @Autowired - private MockMvc mockMvc; - @Autowired - private ProjectRepository projectRepository; - - @Test - void createProjectTest() throws Exception { - String content = """ - { - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractor_name": "Peter File", - "comment": "goal of project", - "start_date": "01.01.2000", - "planned_end_date": "01.01.2001" - } - """; - - final var contentAsString = this.mockMvc.perform( - post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isCreated()) - .andExpect(jsonPath("name", is("name"))) - .andExpect(jsonPath("leading_employee", is(1))) - .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) - .andExpect(jsonPath("contractor", is(4))) - .andExpect(jsonPath("contractor_name", is("Peter File"))) - .andExpect(jsonPath("comment", is("goal of project"))) - .andExpect(jsonPath("start_date", is("01.01.2000"))) - .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) - .andReturn() - .getResponse() - .getContentAsString(); - - final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); - - final var project = this.projectRepository.findById(id); - assertThat(project.get().getName()).isEqualTo("name"); - assertThat(project.get().getLeadingEmployee()).isEqualTo(1); - assertThat(project.get().getContractor()).isEqualTo(4); - assertThat(project.get().getContractorName()).isEqualTo("Peter File"); - assertThat(project.get().getComment()).isEqualTo("goal of project"); - assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); - assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); - } -} +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectRepository; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class CreateProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void createProjectTest() throws Exception { + String content = """ + { + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" + } + """; + + final var contentAsString = this.mockMvc.perform( + post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("name", is("name"))) + .andExpect(jsonPath("leading_employee", is(1))) + .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) + .andExpect(jsonPath("contractor", is(4))) + .andExpect(jsonPath("contractor_name", is("Peter File"))) + .andExpect(jsonPath("comment", is("goal of project"))) + .andExpect(jsonPath("start_date", is("01.01.2000"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) + .andReturn() + .getResponse() + .getContentAsString(); + + final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + + final var project = this.projectRepository.findById(id); + assertThat(project.get().getName()).isEqualTo("name"); + assertThat(project.get().getLeadingEmployee()).isEqualTo(1); + assertThat(project.get().getContractor()).isEqualTo(4); + assertThat(project.get().getContractorName()).isEqualTo("Peter File"); + assertThat(project.get().getComment()).isEqualTo("goal of project"); + assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); + assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); + } +} From 013e964be65247ffb42d44f540fda88f4c03df55 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 25 Sep 2024 12:27:34 +0000 Subject: [PATCH 09/31] feat: Write Integration test for Create Project Route and ensure using Snake Case in JSON Format (SCRUM-7) (!14) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/14 Reviewed-by: Jan Gleytenhoover --- requests/createProject.http | 6 +- .../project/CreateProjectAction.java | 7 +- .../project/dto/CreateProjectDto.java | 3 + .../project/dto/GetProjectDto.java | 17 +---- .../project/CreateProjectActionTest.java | 71 +++++++++++++++++++ ...ss.java => ProjectFindAllSuccessTest.java} | 16 ++--- 6 files changed, 89 insertions(+), 31 deletions(-) create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java rename src/test/java/de/szut/lf8_starter/integration/project/{ProjectFindAllSuccess.java => ProjectFindAllSuccessTest.java} (80%) diff --git a/requests/createProject.http b/requests/createProject.http index add2394..7b08e28 100644 --- a/requests/createProject.http +++ b/requests/createProject.http @@ -8,8 +8,8 @@ Content-Type: application/json "leading_employee": 1, "employees": [2, 3], "contractor": 4, - "contractorName": "Peter File", + "contractor_name": "Peter File", "comment": "goal of project", - "startDate": "01.01.2000", - "plannedEndDate": "01.01.2001" + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java index c41b134..d54318b 100644 --- a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java @@ -8,10 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/projects") @@ -30,6 +28,7 @@ public class CreateProjectAction { @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) @PostMapping + @ResponseStatus(code = HttpStatus.CREATED) public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java index 4c6ab0d..ebf4393 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -1,6 +1,8 @@ package de.szut.lf8_starter.project.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -11,6 +13,7 @@ import java.util.List; @Getter @Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class CreateProjectDto { @NotBlank private String name; diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java index c90cd50..614ece1 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java @@ -1,6 +1,8 @@ package de.szut.lf8_starter.project.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -11,35 +13,22 @@ import java.util.List; @Getter @Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class GetProjectDto { private long id; - - @NotBlank private String name; - - @NotNull private long leadingEmployee; - private List employees; - - @NotNull private long contractor; - - @NotBlank private String contractorName; - - @NotBlank private String comment; - @NotNull @JsonFormat(pattern = "dd.MM.yyyy") private LocalDate startDate; - @NotNull @JsonFormat(pattern = "dd.MM.yyyy") private LocalDate plannedEndDate; - @NotNull @JsonFormat(pattern = "dd.MM.yyyy") private LocalDate endDate; } diff --git a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java new file mode 100644 index 0000000..36eee54 --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -0,0 +1,71 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectRepository; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class CreateProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void createProjectTest() throws Exception { + String content = """ + { + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" + } + """; + + final var contentAsString = this.mockMvc.perform( + post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("name", is("name"))) + .andExpect(jsonPath("leading_employee", is(1))) + .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) + .andExpect(jsonPath("contractor", is(4))) + .andExpect(jsonPath("contractor_name", is("Peter File"))) + .andExpect(jsonPath("comment", is("goal of project"))) + .andExpect(jsonPath("start_date", is("01.01.2000"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) + .andReturn() + .getResponse() + .getContentAsString(); + + final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + + final var project = this.projectRepository.findById(id); + assertThat(project.get().getName()).isEqualTo("name"); + assertThat(project.get().getLeadingEmployee()).isEqualTo(1); + assertThat(project.get().getContractor()).isEqualTo(4); + assertThat(project.get().getContractorName()).isEqualTo("Peter File"); + assertThat(project.get().getComment()).isEqualTo("goal of project"); + assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); + assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccess.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java similarity index 80% rename from src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccess.java rename to src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java index 906b63f..8f1974a 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccess.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; @@ -19,18 +18,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc(addFilters = false) -public class ProjectFindAllSuccess { +public class ProjectFindAllSuccessTest { @Autowired private ProjectRepository projectRepository; @Autowired private MockMvc mockMvc; - @Autowired - private TestRestTemplate restTemplate; - @Test - void findAllProjects() throws Exception { + void findAllProjectsTest() throws Exception { var project = new ProjectEntity(); project.setId(1); project.setComment("comment"); @@ -49,11 +45,11 @@ public class ProjectFindAllSuccess { .andExpect(jsonPath("$[0].id").value(1)) .andExpect(jsonPath("$[0].comment").value("comment")) .andExpect(jsonPath("$[0].contractor").value(1)) - .andExpect(jsonPath("$[0].contractorName").value("contractorName")) - .andExpect(jsonPath("$[0].endDate").value("01.01.2024")) - .andExpect(jsonPath("$[0].leadingEmployee").value(1)) + .andExpect(jsonPath("$[0].contractor_name").value("contractorName")) + .andExpect(jsonPath("$[0].end_date").value("01.01.2024")) + .andExpect(jsonPath("$[0].leading_employee").value(1)) .andExpect(jsonPath("$[0].name").value("name")) - .andExpect(jsonPath("$[0].startDate").value("01.01.2021")) + .andExpect(jsonPath("$[0].start_date").value("01.01.2021")) .andExpect(jsonPath("$[0].employees").isArray()) .andExpect(jsonPath("$[0].employees", hasSize(3))); } From 34609a6eed1930255e3392de8f6e8dab4c1e20f6 Mon Sep 17 00:00:00 2001 From: Jan Gleytenhoover Date: Wed, 25 Sep 2024 14:15:06 +0000 Subject: [PATCH 10/31] feat: Add sonarqube (!16) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/16 --- .gitea/workflows/build.yml | 38 ++++++++++++++++++++++++++++++++++++++ build.gradle.kts | 8 ++++++++ 2 files changed, 46 insertions(+) create mode 100644 .gitea/workflows/build.yml diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..f143c2a --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,38 @@ +name: Build + +on: + push: + branches: + - main + + +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + - name: Cache SonarQube packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Gradle packages + uses: actions/cache@v1 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + - name: Build and analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: ./gradlew build sonar --info \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 9c30a46..469dd94 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,14 @@ plugins { id("org.springframework.boot") version "3.3.4" id("io.spring.dependency-management") version "1.1.6" id("checkstyle") + id("org.sonarqube") version "5.0.0.4638" +} + +sonar { + properties { + property("sonar.projectKey", "LF8") + property("sonar.projectName", "LF8") + } } tasks.withType { From e5f7f192746db67a712085512765177c9214a6b4 Mon Sep 17 00:00:00 2001 From: Jan Gleytenhoover Date: Wed, 25 Sep 2024 15:15:45 +0000 Subject: [PATCH 11/31] fix: Fix Sonarqube java version (!17) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/17 --- .gitea/workflows/build.yml | 10 +++++++--- Readme.md | 3 +++ build.gradle.kts | 12 +++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index f143c2a..4321e8d 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -5,7 +5,6 @@ on: branches: - main - jobs: build: name: Build and analyze @@ -15,10 +14,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v1 with: - java-version: 17 + java-version: 21 - name: Cache SonarQube packages uses: actions/cache@v1 with: @@ -31,6 +30,11 @@ jobs: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle + - name: Coverage + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: ./gradlew test jacocoTestReport sonar - name: Build and analyze env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/Readme.md b/Readme.md index 1874b0a..8774ce3 100644 --- a/Readme.md +++ b/Readme.md @@ -3,6 +3,9 @@ ![Gitea Release](https://img.shields.io/gitea/v/release/jank/LF8?gitea_url=https%3A%2F%2Fgit.kjan.de&style=plastic) ![Gitea Last Commit](https://img.shields.io/gitea/last-commit/jank/LF8?gitea_url=https%3A%2F%2Fgit.kjan.de&style=plastic) +## Code analysis coverage issues and results of linting MAY be found here: +https://sonarqube.kjan.de/dashboard?id=LF8&codeScope=overall + # Starter für das LF08 Projekt ## Requirements diff --git a/build.gradle.kts b/build.gradle.kts index 469dd94..12eda3d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,15 @@ plugins { id("io.spring.dependency-management") version "1.1.6" id("checkstyle") id("org.sonarqube") version "5.0.0.4638" + id("jacoco") +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) // Ensure tests are run before generating the report + reports { + xml.required = true + csv.required = true + } } sonar { @@ -67,4 +76,5 @@ dependencies { tasks.withType { useJUnitPlatform() -} + finalizedBy(tasks.jacocoTestReport) // Run JaCoCo report after tests +} \ No newline at end of file From 921f5855984401ad50c4f1673eac9b922222f137 Mon Sep 17 00:00:00 2001 From: Renovate Date: Thu, 26 Sep 2024 05:25:14 +0000 Subject: [PATCH 12/31] chore(deps): update actions/cache action to v4 (!19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [actions/cache](https://github.com/actions/cache) | action | major | `v1` -> `v4` | --- ### Release Notes
actions/cache (actions/cache) ### [`v4`](https://github.com/actions/cache/compare/v3...v4) [Compare Source](https://github.com/actions/cache/compare/v3...v4) ### [`v3`](https://github.com/actions/cache/compare/v2...v3) [Compare Source](https://github.com/actions/cache/compare/v2...v3) ### [`v2`](https://github.com/actions/cache/compare/v1...v2) [Compare Source](https://github.com/actions/cache/compare/v1...v2)
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://git.kjan.de/jank/LF8/pulls/19 Reviewed-by: Jan Gleytenhoover Co-authored-by: Renovate Co-committed-by: Renovate --- .gitea/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 4321e8d..5e9e1cf 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -19,13 +19,13 @@ jobs: with: java-version: 21 - name: Cache SonarQube packages - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache Gradle packages - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} From 750041a9eb8d96b79aa4fbb4488a6bce134d2556 Mon Sep 17 00:00:00 2001 From: Jan Gleytenhoover Date: Fri, 27 Sep 2024 09:11:11 +0000 Subject: [PATCH 13/31] feat: SonarQube PR analysis (!21) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/21 --- .gitea/workflows/build-pr.yml | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .gitea/workflows/build-pr.yml diff --git a/.gitea/workflows/build-pr.yml b/.gitea/workflows/build-pr.yml new file mode 100644 index 0000000..ffb5436 --- /dev/null +++ b/.gitea/workflows/build-pr.yml @@ -0,0 +1,45 @@ +name: Build PR + +on: # test + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 21 + uses: actions/setup-java@v1 + with: + java-version: 21 + - name: Cache SonarQube packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + - name: Coverage + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: ./gradlew test jacocoTestReport sonar + - name: Build and analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + run: ./gradlew build sonar --info -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} -Dsonar.pullrequest.key=${{github.event.pull_request.number}} \ No newline at end of file From 1669b29bbd811c54f57a52a5ed39c8f2429811f1 Mon Sep 17 00:00:00 2001 From: Renovate Date: Fri, 27 Sep 2024 11:58:17 +0000 Subject: [PATCH 14/31] chore(deps): update postgres docker tag to v17 (!22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | |---|---|---| | postgres | major | `16.4` -> `17.0` | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://git.kjan.de/jank/LF8/pulls/22 Reviewed-by: Jan Gleytenhoover Co-authored-by: Renovate Co-committed-by: Renovate --- docker/local/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/local/docker-compose.yml b/docker/local/docker-compose.yml index 7b556ef..add2420 100644 --- a/docker/local/docker-compose.yml +++ b/docker/local/docker-compose.yml @@ -7,7 +7,7 @@ volumes: services: postgres_for_lf8_starter: container_name: lf8_starter_postgres_container - image: postgres:16.4 + image: postgres:17.0 volumes: - lf8_starter_postgres_data:/var/lib/postgresql/data environment: From e5b4e2c0120863288623a91a449f68eecba55b95 Mon Sep 17 00:00:00 2001 From: Renovate Date: Fri, 27 Sep 2024 12:52:40 +0000 Subject: [PATCH 15/31] chore(deps): update plugin org.sonarqube to v5.1.0.4882 (!18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | org.sonarqube | `5.0.0.4638` -> `5.1.0.4882` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.sonarqube/5.1.0.4882?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.sonarqube/5.1.0.4882?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.sonarqube/5.0.0.4638/5.1.0.4882?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.sonarqube/5.0.0.4638/5.1.0.4882?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://git.kjan.de/jank/LF8/pulls/18 Reviewed-by: Jan Gleytenhoover Co-authored-by: Renovate Co-committed-by: Renovate --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 12eda3d..985ba1c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("org.springframework.boot") version "3.3.4" id("io.spring.dependency-management") version "1.1.6" id("checkstyle") - id("org.sonarqube") version "5.0.0.4638" + id("org.sonarqube") version "5.1.0.4882" id("jacoco") } From 273202842c8252a570f26666200afb6f6e4b7817 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 2 Oct 2024 08:21:10 +0200 Subject: [PATCH 16/31] IDK --- Readme.md | 2 +- .../GetBearerToken.http | 0 requests/createProject.http | 28 ++-- .../project/CreateProjectAction.java | 78 +++++----- .../project/dto/CreateProjectDto.java | 90 +++++------ .../project/dto/GetProjectDto.java | 68 ++++----- .../project/CreateProjectActionTest.java | 142 +++++++++--------- .../project/ProjectFindAllSuccessTest.java | 112 +++++++------- 8 files changed, 260 insertions(+), 260 deletions(-) rename GetBearerToken.http => requests/GetBearerToken.http (100%) diff --git a/Readme.md b/Readme.md index 1874b0a..bcfc727 100644 --- a/Readme.md +++ b/Readme.md @@ -59,7 +59,7 @@ docker compose up # Keycloak ### Keycloak Token -1. Auf der Projektebene [GetBearerToken.http](GetBearerToken.http) öffnen. +1. Auf der Projektebene [GetBearerToken.http](requests/GetBearerToken.http) öffnen. 2. Neben der Request auf den grünen Pfeil drücken 3. Aus dem Reponse das access_token kopieren diff --git a/GetBearerToken.http b/requests/GetBearerToken.http similarity index 100% rename from GetBearerToken.http rename to requests/GetBearerToken.http diff --git a/requests/createProject.http b/requests/createProject.http index 7b08e28..885c49e 100644 --- a/requests/createProject.http +++ b/requests/createProject.http @@ -1,15 +1,15 @@ -### GET request to example server -POST http://localhost:8080/projects -Authorization: Bearer {{auth_token}} -Content-Type: application/json - -{ - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractor_name": "Peter File", - "comment": "goal of project", - "start_date": "01.01.2000", - "planned_end_date": "01.01.2001" +### GET request to example server +POST http://localhost:8080/projects +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java index d54318b..4158fee 100644 --- a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java @@ -1,39 +1,39 @@ -package de.szut.lf8_starter.project; - -import de.szut.lf8_starter.project.dto.CreateProjectDto; -import de.szut.lf8_starter.project.dto.GetProjectDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping(value = "/projects") -public class CreateProjectAction { - private final ProjectService projectService; - private final ProjectMapper projectMapper; - - public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { - this.projectService = projectService; - this.projectMapper = mappingService; - } - - @Operation(summary = "Creates a new Project") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), - @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) - @PostMapping - @ResponseStatus(code = HttpStatus.CREATED) - public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); - - projectEntity = this.projectService.create(projectEntity); - - return this.projectMapper.mapToGetDto(projectEntity); - } -} +package de.szut.lf8_starter.project; + +import de.szut.lf8_starter.project.dto.CreateProjectDto; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(value = "/projects") +public class CreateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Creates a new Project") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @PostMapping + @ResponseStatus(code = HttpStatus.CREATED) + public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + + projectEntity = this.projectService.create(projectEntity); + + return this.projectMapper.mapToGetDto(projectEntity); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java index ebf4393..64d22b7 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java @@ -1,45 +1,45 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class CreateProjectDto { - @NotBlank - private String name; - - @NotNull - private long leadingEmployee; - - private List employees; - - @NotNull - private long contractor; - - @NotBlank - private String contractorName; - - @NotBlank - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - @NotNull - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class CreateProjectDto { + @NotBlank + private String name; + + @NotNull + private long leadingEmployee; + + private List employees; + + @NotNull + private long contractor; + + @NotBlank + private String contractorName; + + @NotBlank + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + @NotNull + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java index 614ece1..250a6a0 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java @@ -1,34 +1,34 @@ -package de.szut.lf8_starter.project.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDate; -import java.util.List; - -@Getter -@Setter -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class GetProjectDto { - private long id; - private String name; - private long leadingEmployee; - private List employees; - private long contractor; - private String contractorName; - private String comment; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate startDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate plannedEndDate; - - @JsonFormat(pattern = "dd.MM.yyyy") - private LocalDate endDate; -} +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class GetProjectDto { + private long id; + private String name; + private long leadingEmployee; + private List employees; + private long contractor; + private String contractorName; + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java index 36eee54..06d02b6 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -1,71 +1,71 @@ -package de.szut.lf8_starter.integration.project; - -import de.szut.lf8_starter.project.ProjectRepository; -import org.json.JSONObject; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import java.time.LocalDate; -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc(addFilters = false) -public class CreateProjectActionTest { - @Autowired - private MockMvc mockMvc; - @Autowired - private ProjectRepository projectRepository; - - @Test - void createProjectTest() throws Exception { - String content = """ - { - "name": "name", - "leading_employee": 1, - "employees": [2, 3], - "contractor": 4, - "contractor_name": "Peter File", - "comment": "goal of project", - "start_date": "01.01.2000", - "planned_end_date": "01.01.2001" - } - """; - - final var contentAsString = this.mockMvc.perform( - post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isCreated()) - .andExpect(jsonPath("name", is("name"))) - .andExpect(jsonPath("leading_employee", is(1))) - .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) - .andExpect(jsonPath("contractor", is(4))) - .andExpect(jsonPath("contractor_name", is("Peter File"))) - .andExpect(jsonPath("comment", is("goal of project"))) - .andExpect(jsonPath("start_date", is("01.01.2000"))) - .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) - .andReturn() - .getResponse() - .getContentAsString(); - - final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); - - final var project = this.projectRepository.findById(id); - assertThat(project.get().getName()).isEqualTo("name"); - assertThat(project.get().getLeadingEmployee()).isEqualTo(1); - assertThat(project.get().getContractor()).isEqualTo(4); - assertThat(project.get().getContractorName()).isEqualTo("Peter File"); - assertThat(project.get().getComment()).isEqualTo("goal of project"); - assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); - assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); - } -} +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectRepository; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class CreateProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void createProjectTest() throws Exception { + String content = """ + { + "name": "name", + "leading_employee": 1, + "employees": [2, 3], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" + } + """; + + final var contentAsString = this.mockMvc.perform( + post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("name", is("name"))) + .andExpect(jsonPath("leading_employee", is(1))) + .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) + .andExpect(jsonPath("contractor", is(4))) + .andExpect(jsonPath("contractor_name", is("Peter File"))) + .andExpect(jsonPath("comment", is("goal of project"))) + .andExpect(jsonPath("start_date", is("01.01.2000"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2001"))) + .andReturn() + .getResponse() + .getContentAsString(); + + final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + + final var project = this.projectRepository.findById(id); + assertThat(project.get().getName()).isEqualTo("name"); + assertThat(project.get().getLeadingEmployee()).isEqualTo(1); + assertThat(project.get().getContractor()).isEqualTo(4); + assertThat(project.get().getContractorName()).isEqualTo("Peter File"); + assertThat(project.get().getComment()).isEqualTo("goal of project"); + assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); + assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java index 8f1974a..22981bb 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java @@ -1,56 +1,56 @@ -package de.szut.lf8_starter.integration.project; - -import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectRepository; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; - -import java.time.LocalDate; -import java.util.List; - -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc(addFilters = false) -public class ProjectFindAllSuccessTest { - @Autowired - private ProjectRepository projectRepository; - - @Autowired - private MockMvc mockMvc; - - @Test - void findAllProjectsTest() throws Exception { - var project = new ProjectEntity(); - project.setId(1); - project.setComment("comment"); - project.setContractor(1); - project.setContractorName("contractorName"); - project.setEndDate(LocalDate.of(2024, 1, 1)); - project.setLeadingEmployee(1); - project.setName("name"); - project.setStartDate(LocalDate.of(2021, 1, 1)); - project.setEmployees(List.of(1L,2L,3L)); - this.projectRepository.save(project); - - this.mockMvc.perform(get("/projects")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].id").value(1)) - .andExpect(jsonPath("$[0].comment").value("comment")) - .andExpect(jsonPath("$[0].contractor").value(1)) - .andExpect(jsonPath("$[0].contractor_name").value("contractorName")) - .andExpect(jsonPath("$[0].end_date").value("01.01.2024")) - .andExpect(jsonPath("$[0].leading_employee").value(1)) - .andExpect(jsonPath("$[0].name").value("name")) - .andExpect(jsonPath("$[0].start_date").value("01.01.2021")) - .andExpect(jsonPath("$[0].employees").isArray()) - .andExpect(jsonPath("$[0].employees", hasSize(3))); - } -} +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.List; + +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc(addFilters = false) +public class ProjectFindAllSuccessTest { + @Autowired + private ProjectRepository projectRepository; + + @Autowired + private MockMvc mockMvc; + + @Test + void findAllProjectsTest() throws Exception { + var project = new ProjectEntity(); + project.setId(1); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(1L,2L,3L)); + this.projectRepository.save(project); + + this.mockMvc.perform(get("/projects")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].comment").value("comment")) + .andExpect(jsonPath("$[0].contractor").value(1)) + .andExpect(jsonPath("$[0].contractor_name").value("contractorName")) + .andExpect(jsonPath("$[0].end_date").value("01.01.2024")) + .andExpect(jsonPath("$[0].leading_employee").value(1)) + .andExpect(jsonPath("$[0].name").value("name")) + .andExpect(jsonPath("$[0].start_date").value("01.01.2021")) + .andExpect(jsonPath("$[0].employees").isArray()) + .andExpect(jsonPath("$[0].employees", hasSize(3))); + } +} From fca0ab89ed5335b7457eb5a3db271977dca30d22 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 2 Oct 2024 08:24:04 +0200 Subject: [PATCH 17/31] REfactor --- requests/getAllProjects.http | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 requests/getAllProjects.http diff --git a/requests/getAllProjects.http b/requests/getAllProjects.http new file mode 100644 index 0000000..21a4ceb --- /dev/null +++ b/requests/getAllProjects.http @@ -0,0 +1,3 @@ +### GET request to example server +GET http://localhost:8080/projects +Authorization: Bearer {{auth_token}} \ No newline at end of file From d047bce4ec44b300c4498c833b9a52a4034d0a93 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 2 Oct 2024 06:34:16 +0000 Subject: [PATCH 18/31] refactor: Add and refactor http requests (!24) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/24 Reviewed-by: Jan Gleytenhoover Co-authored-by: Phan Huy Tran Co-committed-by: Phan Huy Tran --- Readme.md | 2 +- GetBearerToken.http => requests/GetBearerToken.http | 0 requests/getAllProjects.http | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) rename GetBearerToken.http => requests/GetBearerToken.http (100%) create mode 100644 requests/getAllProjects.http diff --git a/Readme.md b/Readme.md index 8774ce3..1e0c8d8 100644 --- a/Readme.md +++ b/Readme.md @@ -62,7 +62,7 @@ docker compose up # Keycloak ### Keycloak Token -1. Auf der Projektebene [GetBearerToken.http](GetBearerToken.http) öffnen. +1. Auf der Projektebene [GetBearerToken.http](requests/GetBearerToken.http) öffnen. 2. Neben der Request auf den grünen Pfeil drücken 3. Aus dem Reponse das access_token kopieren diff --git a/GetBearerToken.http b/requests/GetBearerToken.http similarity index 100% rename from GetBearerToken.http rename to requests/GetBearerToken.http diff --git a/requests/getAllProjects.http b/requests/getAllProjects.http new file mode 100644 index 0000000..b56f68d --- /dev/null +++ b/requests/getAllProjects.http @@ -0,0 +1,3 @@ +### GET request to example server +GET http://localhost:8080/projects +Authorization: Bearer {{auth_token}} \ No newline at end of file From c317b226fe7131a7c7b33076232fa74da2701e9e Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 2 Oct 2024 07:04:08 +0000 Subject: [PATCH 19/31] feat: Implement get project by ID Route (!25) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/25 Reviewed-by: Jan Gleytenhoover Co-authored-by: Phan Huy Tran Co-committed-by: Phan Huy Tran --- requests/getProject.http | 3 ++ .../lf8_starter/project/ProjectService.java | 12 +++++ .../{ => action}/CreateProjectAction.java | 5 +- .../GetAllProjectsAction.java} | 22 ++++---- .../project/action/GetProjectAction.java | 40 ++++++++++++++ .../project/FindProjectActionTest.java | 54 +++++++++++++++++++ 6 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 requests/getProject.http rename src/main/java/de/szut/lf8_starter/project/{ => action}/CreateProjectAction.java (89%) rename src/main/java/de/szut/lf8_starter/project/{ProjectController.java => action/GetAllProjectsAction.java} (60%) create mode 100644 src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/FindProjectActionTest.java diff --git a/requests/getProject.http b/requests/getProject.http new file mode 100644 index 0000000..5758af5 --- /dev/null +++ b/requests/getProject.http @@ -0,0 +1,3 @@ +### GET request to example server +GET http://localhost:8080/projects/2 +Authorization: Bearer {{auth_token}} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectService.java b/src/main/java/de/szut/lf8_starter/project/ProjectService.java index 4dc8688..a815350 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectService.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectService.java @@ -1,8 +1,10 @@ package de.szut.lf8_starter.project; +import de.szut.lf8_starter.exceptionHandling.ResourceNotFoundException; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; @Service public class ProjectService { @@ -19,4 +21,14 @@ public class ProjectService { public List readAll() { return this.projectRepository.findAll(); } + + public ProjectEntity findById(Long id) { + Optional articleEntity = projectRepository.findById(id); + + if (articleEntity.isEmpty()) { + throw new ResourceNotFoundException("Project with id " + id + " not found"); + } + + return articleEntity.get(); + } } diff --git a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/CreateProjectAction.java similarity index 89% rename from src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java rename to src/main/java/de/szut/lf8_starter/project/action/CreateProjectAction.java index d54318b..3e636b5 100644 --- a/src/main/java/de/szut/lf8_starter/project/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/CreateProjectAction.java @@ -1,5 +1,8 @@ -package de.szut.lf8_starter.project; +package de.szut.lf8_starter.project.action; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; import de.szut.lf8_starter.project.dto.CreateProjectDto; import de.szut.lf8_starter.project.dto.GetProjectDto; import io.swagger.v3.oas.annotations.Operation; diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectController.java b/src/main/java/de/szut/lf8_starter/project/action/GetAllProjectsAction.java similarity index 60% rename from src/main/java/de/szut/lf8_starter/project/ProjectController.java rename to src/main/java/de/szut/lf8_starter/project/action/GetAllProjectsAction.java index 61ee843..039a104 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectController.java +++ b/src/main/java/de/szut/lf8_starter/project/action/GetAllProjectsAction.java @@ -1,32 +1,36 @@ -package de.szut.lf8_starter.project; +package de.szut.lf8_starter.project.action; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; import de.szut.lf8_starter.project.dto.GetProjectDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping(value = "projects") -public class ProjectController { +public class GetAllProjectsAction { private final ProjectService service; private final ProjectMapper projectMapper; - public ProjectController(ProjectService service, ProjectMapper projectMapper) { + + public GetAllProjectsAction(ProjectService service, ProjectMapper projectMapper) { this.service = service; this.projectMapper = projectMapper; } @Operation(summary = "returns all projects") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "all projects", - content = {@Content(mediaType = "application/json", - schema = @Schema(implementation = GetProjectDto.class))}), - @ApiResponse(responseCode = "401", description = "not authorized", - content = @Content)}) + @ApiResponse(responseCode = "200", description = "all projects", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class)) + }), + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) @GetMapping public List findAll() { return this.service diff --git a/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java new file mode 100644 index 0000000..d04ac20 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java @@ -0,0 +1,40 @@ +package de.szut.lf8_starter.project.action; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(value = "projects") +public class GetProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public GetProjectAction(ProjectService projectService, ProjectMapper projectMapper) { + this.projectService = projectService; + this.projectMapper = projectMapper; + } + + @Operation(summary = "Find project by ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Project found", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class)) + }), + @ApiResponse(responseCode = "404", description = "Project not found", content = @Content) + }) + @GetMapping("/{id}") + public ResponseEntity findArticleById(@PathVariable Long id) { + ProjectEntity project = this.projectService.findById(id); + + return new ResponseEntity<>(this.projectMapper.mapToGetDto(project), HttpStatus.OK); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/FindProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/FindProjectActionTest.java new file mode 100644 index 0000000..dacb171 --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/FindProjectActionTest.java @@ -0,0 +1,54 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.List; + +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class FindProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void createProjectTest() throws Exception { + var project = new ProjectEntity(); + project.setId(1); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project); + + this.mockMvc.perform(get("/projects/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("id").value(1)) + .andExpect(jsonPath("comment").value("comment")) + .andExpect(jsonPath("contractor").value(1)) + .andExpect(jsonPath("contractor_name").value("contractorName")) + .andExpect(jsonPath("end_date").value("01.01.2024")) + .andExpect(jsonPath("leading_employee").value(1)) + .andExpect(jsonPath("name").value("name")) + .andExpect(jsonPath("start_date").value("01.01.2021")) + .andExpect(jsonPath("employees").isArray()) + .andExpect(jsonPath("employees", hasSize(3))); + } +} From a04a1807120e790bd78943eb50a324771fa4346e Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Wed, 2 Oct 2024 07:48:32 +0000 Subject: [PATCH 20/31] refactor: Fix some code smells (!26) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/26 Reviewed-by: Phan Huy Tran Co-authored-by: Jan Klattenhoff Co-committed-by: Jan Klattenhoff --- .../exceptionHandling/GlobalExceptionHandler.java | 2 +- .../lf8_starter/security/KeycloakSecurityConfig.java | 6 +++--- .../de/szut/lf8_starter/welcome/WelcomeController.java | 10 ++++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index 68a15a7..8ee2aaf 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -21,7 +21,7 @@ import java.util.Date; public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { + public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); } diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java index 8ab3fd8..7ff32cc 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java @@ -83,9 +83,9 @@ class KeycloakSecurityConfig { jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> { List grantedAuthorities = new ArrayList<>(); - Map realmAccess = jwt.getClaim("realm_access"); - if (realmAccess != null && realmAccess.containsKey("roles")) { - List roles = (List) realmAccess.get("roles"); + Map realmAccess = jwt.getClaim(REALM_ACCESS_CLAIM); + if (realmAccess != null && realmAccess.containsKey(ROLES_CLAIM)) { + List roles = (List) realmAccess.get(ROLES_CLAIM); for (String role : roles) { grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } diff --git a/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java b/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java index 6ed067b..4effa6b 100644 --- a/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java +++ b/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java @@ -1,14 +1,12 @@ package de.szut.lf8_starter.welcome; - - import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import java.security.Principal; +import java.util.Collection; @RestController public class WelcomeController { @@ -19,8 +17,8 @@ public class WelcomeController { } @GetMapping("/roles") - public ResponseEntity getRoles(Authentication authentication) { - return ResponseEntity.ok(authentication.getAuthorities()); + public ResponseEntity> getRoles(Authentication authentication) { + return ResponseEntity.ok((Collection) authentication.getAuthorities()); } From cbb77cc1ca3d5550aee36e41b59e4b273afed5a7 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Wed, 2 Oct 2024 08:43:01 +0000 Subject: [PATCH 21/31] refactor: Fix yellow code smells (!27) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/27 Reviewed-by: Phan Huy Tran Co-authored-by: Jan Klattenhoff Co-committed-by: Jan Klattenhoff --- build.gradle.kts | 17 ++++++--- .../config/OpenAPIConfiguration.java | 37 ++++++++++++++----- .../lf8_starter/hello/HelloController.java | 34 ----------------- .../security/KeycloakLogoutHandler.java | 4 +- .../security/KeycloakSecurityConfig.java | 6 +-- 5 files changed, 41 insertions(+), 57 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 985ba1c..d15cf48 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,20 +58,25 @@ repositories { mavenCentral() } +val springDocVersion = "2.6.0" +val oauth2Version = "3.3.4" + dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:$springDocVersion") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:$oauth2Version") + implementation("org.springframework.boot:spring-boot-starter-oauth2-client:$oauth2Version") + + testImplementation("com.h2database:h2") + testImplementation("org.springframework.boot:spring-boot-starter-test") + compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") - testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - implementation("org.springframework.boot:spring-boot-starter-security") - implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.3.4") - implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.3.4") runtimeOnly("org.postgresql:postgresql") - implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") - testImplementation("com.h2database:h2") } tasks.withType { diff --git a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java index 1b2282b..3c782d3 100644 --- a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java +++ b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java @@ -1,7 +1,6 @@ package de.szut.lf8_starter.config; - import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; @@ -33,15 +32,33 @@ public class OpenAPIConfiguration { .addServersItem(new Server().url(this.context.getContextPath())) .info(new Info() .title("LF8 project starter") - .description("\n## Auth\n" + - "\n## Authentication\n" + "\nThis Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on:\n\n" + - "\n" + - "```\nPOST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token\nContent-Type: application/x-www-form-urlencoded\ngrant_type=password&client_id=employee-management-service&username=user&password=test\n```\n" + - "\n" + - "\nor by CURL\n" + - "```\ncurl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token'\n--header 'Content-Type: application/x-www-form-urlencoded'\n--data-urlencode 'grant_type=password'\n--data-urlencode 'client_id=employee-management-service'\n--data-urlencode 'username=user'\n--data-urlencode 'password=test'\n```\n" + - "\nTo get a bearer-token in Postman, you have to follow the instructions in \n [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd).") - + .description(""" + ## Auth + + ## Authentication + + This Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on: + + ``` + POST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token + Content-Type: application/x-www-form-urlencoded + grant_type=password&client_id=employee-management-service&username=user&password=test + ``` + + or by CURL: + + ``` + curl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token' + --header 'Content-Type: application/x-www-form-urlencoded' + --data-urlencode 'grant_type=password' + --data-urlencode 'client_id=employee-management-service' + --data-urlencode 'username=user' + --data-urlencode 'password=test' + ``` + + To get a bearer-token in Postman, you have to follow the instructions in + [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd). + """) .version("0.1")) .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) .components( diff --git a/src/main/java/de/szut/lf8_starter/hello/HelloController.java b/src/main/java/de/szut/lf8_starter/hello/HelloController.java index 0f665f2..2ca47da 100644 --- a/src/main/java/de/szut/lf8_starter/hello/HelloController.java +++ b/src/main/java/de/szut/lf8_starter/hello/HelloController.java @@ -45,22 +45,6 @@ public class HelloController { return this.helloMapper.mapToGetDto(helloEntity); } - @Operation(summary = "delivers a list of hellos") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "list of hellos", - content = {@Content(mediaType = "application/json", - schema = @Schema(implementation = HelloGetDto.class))}), - @ApiResponse(responseCode = "401", description = "not authorized", - content = @Content)}) - @GetMapping - public List findAll() { - return this.service - .readAll() - .stream() - .map(e -> this.helloMapper.mapToGetDto(e)) - .collect(Collectors.toList()); - } - @Operation(summary = "deletes a Hello by id") @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "delete successful"), @@ -78,22 +62,4 @@ public class HelloController { this.service.delete(entity); } } - - @Operation(summary = "find hellos by message") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "List of hellos who have the given message", - content = {@Content(mediaType = "application/json", - schema = @Schema(implementation = HelloGetDto.class))}), - @ApiResponse(responseCode = "404", description = "qualification description does not exist", - content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", - content = @Content)}) - @GetMapping("/findByMessage") - public List findAllEmployeesByQualification(@RequestParam String message) { - return this.service - .findByMessage(message) - .stream() - .map(e -> this.helloMapper.mapToGetDto(e)) - .collect(Collectors.toList()); - } } diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java index 8555ef9..3051a80 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java @@ -25,10 +25,10 @@ public class KeycloakLogoutHandler implements LogoutHandler { @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { - logout(request, auth); + logout(auth); } - public void logout(HttpServletRequest request, Authentication auth) { + public void logout(Authentication auth) { logoutFromKeycloak((OidcUser) auth.getPrincipal()); } diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java index 7ff32cc..f64bdd6 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java @@ -29,14 +29,10 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @EnableWebSecurity class KeycloakSecurityConfig { - private static final String GROUPS = "groups"; private static final String REALM_ACCESS_CLAIM = "realm_access"; private static final String ROLES_CLAIM = "roles"; - private final KeycloakLogoutHandler keycloakLogoutHandler; - - KeycloakSecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) { - this.keycloakLogoutHandler = keycloakLogoutHandler; + KeycloakSecurityConfig() { } @Bean From 4eb7626c109d1cf888d9f9d207c8cd8f9d133e47 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 2 Oct 2024 08:52:19 +0000 Subject: [PATCH 22/31] feat: Implement Update Project Route (SCRUM-11) (!28) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/28 Reviewed-by: Jan Gleytenhoover Co-authored-by: Phan Huy Tran Co-committed-by: Phan Huy Tran --- build.gradle.kts | 168 ++++++++-------- requests/getProject.http | 6 +- requests/updateProject.http | 15 ++ .../config/OpenAPIConfiguration.java | 154 +++++++------- .../lf8_starter/config/SampleDataCreator.java | 66 +++--- .../GlobalExceptionHandler.java | 110 +++++----- .../lf8_starter/hello/HelloController.java | 130 ++++++------ .../szut/lf8_starter/hello/HelloEntity.java | 54 ++--- .../szut/lf8_starter/hello/HelloMapper.java | 40 ++-- .../lf8_starter/hello/HelloRepository.java | 24 +-- .../szut/lf8_starter/hello/HelloService.java | 80 ++++---- .../lf8_starter/hello/dto/HelloCreateDto.java | 40 ++-- .../lf8_starter/hello/dto/HelloGetDto.java | 34 ++-- .../lf8_starter/project/ProjectMapper.java | 102 ++++++---- .../lf8_starter/project/ProjectService.java | 67 +++---- .../project/action/GetProjectAction.java | 86 ++++---- .../project/action/UpdateProjectAction.java | 50 +++++ .../project/dto/UpdateProjectDto.java | 36 ++++ .../security/KeycloakLogoutHandler.java | 98 ++++----- .../security/KeycloakSecurityConfig.java | 188 +++++++++--------- .../welcome/WelcomeController.java | 50 ++--- ...ionTest.java => GetProjectActionTest.java} | 113 ++++++----- .../project/UpdateProjectActionTest.java | 140 +++++++++++++ 23 files changed, 1065 insertions(+), 786 deletions(-) create mode 100644 requests/updateProject.http create mode 100644 src/main/java/de/szut/lf8_starter/project/action/UpdateProjectAction.java create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/UpdateProjectDto.java rename src/test/java/de/szut/lf8_starter/integration/project/{FindProjectActionTest.java => GetProjectActionTest.java} (87%) create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java diff --git a/build.gradle.kts b/build.gradle.kts index d15cf48..0488a79 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,85 +1,85 @@ -plugins { - java - id("org.springframework.boot") version "3.3.4" - id("io.spring.dependency-management") version "1.1.6" - id("checkstyle") - id("org.sonarqube") version "5.1.0.4882" - id("jacoco") -} - -tasks.jacocoTestReport { - dependsOn(tasks.test) // Ensure tests are run before generating the report - reports { - xml.required = true - csv.required = true - } -} - -sonar { - properties { - property("sonar.projectKey", "LF8") - property("sonar.projectName", "LF8") - } -} - -tasks.withType { - reports { - // Disable HTML report - html.required.set(false) - - // Disable XML report - xml.required.set(false) - } -} - -group = "de.szut" -version = "0.0.1-SNAPSHOT" - -tasks.test { - useJUnitPlatform() - - // Activate the 'test' profile for Spring during tests - systemProperty("spring.profiles.active", "test") -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} - -configurations { - compileOnly { - extendsFrom(configurations.annotationProcessor.get()) - } -} - -repositories { - mavenCentral() -} - -val springDocVersion = "2.6.0" -val oauth2Version = "3.3.4" - -dependencies { - implementation("org.springframework.boot:spring-boot-starter-data-jpa") - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter-validation") - implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:$springDocVersion") - implementation("org.springframework.boot:spring-boot-starter-security") - implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:$oauth2Version") - implementation("org.springframework.boot:spring-boot-starter-oauth2-client:$oauth2Version") - - testImplementation("com.h2database:h2") - testImplementation("org.springframework.boot:spring-boot-starter-test") - - compileOnly("org.projectlombok:lombok") - annotationProcessor("org.projectlombok:lombok") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") - runtimeOnly("org.postgresql:postgresql") -} - -tasks.withType { - useJUnitPlatform() - finalizedBy(tasks.jacocoTestReport) // Run JaCoCo report after tests +plugins { + java + id("org.springframework.boot") version "3.3.4" + id("io.spring.dependency-management") version "1.1.6" + id("checkstyle") + id("org.sonarqube") version "5.1.0.4882" + id("jacoco") +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) // Ensure tests are run before generating the report + reports { + xml.required = true + csv.required = true + } +} + +sonar { + properties { + property("sonar.projectKey", "LF8") + property("sonar.projectName", "LF8") + } +} + +tasks.withType { + reports { + // Disable HTML report + html.required.set(false) + + // Disable XML report + xml.required.set(false) + } +} + +group = "de.szut" +version = "0.0.1-SNAPSHOT" + +tasks.test { + useJUnitPlatform() + + // Activate the 'test' profile for Spring during tests + systemProperty("spring.profiles.active", "test") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +configurations { + compileOnly { + extendsFrom(configurations.annotationProcessor.get()) + } +} + +repositories { + mavenCentral() +} + +val springDocVersion = "2.6.0" +val oauth2Version = "3.3.4" + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:$springDocVersion") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:$oauth2Version") + implementation("org.springframework.boot:spring-boot-starter-oauth2-client:$oauth2Version") + + testImplementation("com.h2database:h2") + testImplementation("org.springframework.boot:spring-boot-starter-test") + + compileOnly("org.projectlombok:lombok") + annotationProcessor("org.projectlombok:lombok") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + runtimeOnly("org.postgresql:postgresql") +} + +tasks.withType { + useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) // Run JaCoCo report after tests } \ No newline at end of file diff --git a/requests/getProject.http b/requests/getProject.http index 5758af5..1074d4a 100644 --- a/requests/getProject.http +++ b/requests/getProject.http @@ -1,3 +1,3 @@ -### GET request to example server -GET http://localhost:8080/projects/2 -Authorization: Bearer {{auth_token}} +### GET request to example server +GET http://localhost:8080/projects/1 +Authorization: Bearer {{auth_token}} diff --git a/requests/updateProject.http b/requests/updateProject.http new file mode 100644 index 0000000..13bf006 --- /dev/null +++ b/requests/updateProject.http @@ -0,0 +1,15 @@ +### GET request to example server +PUT http://localhost:8080/projects/1 +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "name": "newName", + "leading_employee": 2, + "employees": [], + "contractor": 9, + "contractor_name": "New Contractor name", + "comment": "new goal of project", + "start_date": "01.01.2010", + "planned_end_date": "01.01.2021" +} \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java index 3c782d3..17f6bda 100644 --- a/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java +++ b/src/main/java/de/szut/lf8_starter/config/OpenAPIConfiguration.java @@ -1,77 +1,77 @@ -package de.szut.lf8_starter.config; - - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; -import jakarta.servlet.ServletContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - -@Configuration -public class OpenAPIConfiguration { - - private ServletContext context; - - public OpenAPIConfiguration(ServletContext context) { - this.context = context; - } - - - @Bean - public OpenAPI springShopOpenAPI( - // @Value("${info.app.version}") String appVersion, - ) { - final String securitySchemeName = "bearerAuth"; - - return new OpenAPI() - .addServersItem(new Server().url(this.context.getContextPath())) - .info(new Info() - .title("LF8 project starter") - .description(""" - ## Auth - - ## Authentication - - This Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on: - - ``` - POST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token - Content-Type: application/x-www-form-urlencoded - grant_type=password&client_id=employee-management-service&username=user&password=test - ``` - - or by CURL: - - ``` - curl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token' - --header 'Content-Type: application/x-www-form-urlencoded' - --data-urlencode 'grant_type=password' - --data-urlencode 'client_id=employee-management-service' - --data-urlencode 'username=user' - --data-urlencode 'password=test' - ``` - - To get a bearer-token in Postman, you have to follow the instructions in - [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd). - """) - .version("0.1")) - .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) - .components( - new Components() - .addSecuritySchemes(securitySchemeName, - new SecurityScheme() - .name(securitySchemeName) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - ) - ); - } - - -} +package de.szut.lf8_starter.config; + + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import jakarta.servlet.ServletContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class OpenAPIConfiguration { + + private ServletContext context; + + public OpenAPIConfiguration(ServletContext context) { + this.context = context; + } + + + @Bean + public OpenAPI springShopOpenAPI( + // @Value("${info.app.version}") String appVersion, + ) { + final String securitySchemeName = "bearerAuth"; + + return new OpenAPI() + .addServersItem(new Server().url(this.context.getContextPath())) + .info(new Info() + .title("LF8 project starter") + .description(""" + ## Auth + + ## Authentication + + This Hello service uses JWTs to authenticate requests. You will receive a bearer token by making a POST-Request in IntelliJ on: + + ``` + POST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token + Content-Type: application/x-www-form-urlencoded + grant_type=password&client_id=employee-management-service&username=user&password=test + ``` + + or by CURL: + + ``` + curl -X POST 'http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token' + --header 'Content-Type: application/x-www-form-urlencoded' + --data-urlencode 'grant_type=password' + --data-urlencode 'client_id=employee-management-service' + --data-urlencode 'username=user' + --data-urlencode 'password=test' + ``` + + To get a bearer-token in Postman, you have to follow the instructions in + [Postman-Documentation](https://documenter.getpostman.com/view/7294517/SzmfZHnd). + """) + .version("0.1")) + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) + .components( + new Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } + + +} diff --git a/src/main/java/de/szut/lf8_starter/config/SampleDataCreator.java b/src/main/java/de/szut/lf8_starter/config/SampleDataCreator.java index 9a7b889..373ea11 100644 --- a/src/main/java/de/szut/lf8_starter/config/SampleDataCreator.java +++ b/src/main/java/de/szut/lf8_starter/config/SampleDataCreator.java @@ -1,33 +1,33 @@ -package de.szut.lf8_starter.config; - - -import de.szut.lf8_starter.hello.HelloEntity; -import de.szut.lf8_starter.hello.HelloRepository; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -@Component -public class SampleDataCreator implements ApplicationRunner { - - private HelloRepository repository; - - public SampleDataCreator(HelloRepository repository) { - this.repository = repository; - } - - public void run(ApplicationArguments args) { - repository.save(new HelloEntity("Hallo Welt!")); - repository.save(new HelloEntity("Schöner Tag heute")); - repository.save(new HelloEntity("FooBar")); - - } - - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - -} +package de.szut.lf8_starter.config; + + +import de.szut.lf8_starter.hello.HelloEntity; +import de.szut.lf8_starter.hello.HelloRepository; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class SampleDataCreator implements ApplicationRunner { + + private HelloRepository repository; + + public SampleDataCreator(HelloRepository repository) { + this.repository = repository; + } + + public void run(ApplicationArguments args) { + repository.save(new HelloEntity("Hallo Welt!")); + repository.save(new HelloEntity("Schöner Tag heute")); + repository.save(new HelloEntity("FooBar")); + + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +} diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index 8ee2aaf..f6415b5 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -1,51 +1,59 @@ -package de.szut.lf8_starter.exceptionHandling; - -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.ConstraintViolationException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; - -import java.util.Date; - -@ControllerAdvice -@ApiResponses(value = { - @ApiResponse(responseCode = "500", description = "invalid JSON posted", - content = @Content) -}) -public class GlobalExceptionHandler { - - @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { - ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { - String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); - - ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); - - return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); - } -} +package de.szut.lf8_starter.exceptionHandling; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +import java.util.Date; + +@ControllerAdvice +@ApiResponses(value = { + @ApiResponse(responseCode = "500", description = "invalid JSON posted", + content = @Content) +}) +public class GlobalExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleHelloEntityNotFoundException(ResourceNotFoundException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException ex, WebRequest request) { + ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { + String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); + + ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); + + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/de/szut/lf8_starter/hello/HelloController.java b/src/main/java/de/szut/lf8_starter/hello/HelloController.java index 2ca47da..3a4a993 100644 --- a/src/main/java/de/szut/lf8_starter/hello/HelloController.java +++ b/src/main/java/de/szut/lf8_starter/hello/HelloController.java @@ -1,65 +1,65 @@ -package de.szut.lf8_starter.hello; - - -import de.szut.lf8_starter.exceptionHandling.ResourceNotFoundException; -import de.szut.lf8_starter.hello.dto.HelloCreateDto; -import de.szut.lf8_starter.hello.dto.HelloGetDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.stream.Collectors; - -@RestController -@RequestMapping(value = "hello") -@PreAuthorize("hasAnyAuthority('user')") -public class HelloController { - private final HelloService service; - private final HelloMapper helloMapper; - - public HelloController(HelloService service, HelloMapper mappingService) { - this.service = service; - this.helloMapper = mappingService; - } - - @Operation(summary = "creates a new hello with its id and message") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "created hello", - content = {@Content(mediaType = "application/json", - schema = @Schema(implementation = HelloGetDto.class))}), - @ApiResponse(responseCode = "400", description = "invalid JSON posted", - content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", - content = @Content)}) - @PostMapping - public HelloGetDto create(@RequestBody @Valid HelloCreateDto helloCreateDto) { - HelloEntity helloEntity = this.helloMapper.mapCreateDtoToEntity(helloCreateDto); - helloEntity = this.service.create(helloEntity); - return this.helloMapper.mapToGetDto(helloEntity); - } - - @Operation(summary = "deletes a Hello by id") - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "delete successful"), - @ApiResponse(responseCode = "401", description = "not authorized", - content = @Content), - @ApiResponse(responseCode = "404", description = "resource not found", - content = @Content)}) - @DeleteMapping("/{id}") - @ResponseStatus(code = HttpStatus.NO_CONTENT) - public void deleteHelloById(@PathVariable long id) { - var entity = this.service.readById(id); - if (entity == null) { - throw new ResourceNotFoundException("HelloEntity not found on id = " + id); - } else { - this.service.delete(entity); - } - } -} +package de.szut.lf8_starter.hello; + + +import de.szut.lf8_starter.exceptionHandling.ResourceNotFoundException; +import de.szut.lf8_starter.hello.dto.HelloCreateDto; +import de.szut.lf8_starter.hello.dto.HelloGetDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping(value = "hello") +@PreAuthorize("hasAnyAuthority('user')") +public class HelloController { + private final HelloService service; + private final HelloMapper helloMapper; + + public HelloController(HelloService service, HelloMapper mappingService) { + this.service = service; + this.helloMapper = mappingService; + } + + @Operation(summary = "creates a new hello with its id and message") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "created hello", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = HelloGetDto.class))}), + @ApiResponse(responseCode = "400", description = "invalid JSON posted", + content = @Content), + @ApiResponse(responseCode = "401", description = "not authorized", + content = @Content)}) + @PostMapping + public HelloGetDto create(@RequestBody @Valid HelloCreateDto helloCreateDto) { + HelloEntity helloEntity = this.helloMapper.mapCreateDtoToEntity(helloCreateDto); + helloEntity = this.service.create(helloEntity); + return this.helloMapper.mapToGetDto(helloEntity); + } + + @Operation(summary = "deletes a Hello by id") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "delete successful"), + @ApiResponse(responseCode = "401", description = "not authorized", + content = @Content), + @ApiResponse(responseCode = "404", description = "resource not found", + content = @Content)}) + @DeleteMapping("/{id}") + @ResponseStatus(code = HttpStatus.NO_CONTENT) + public void deleteHelloById(@PathVariable long id) { + var entity = this.service.readById(id); + if (entity == null) { + throw new ResourceNotFoundException("HelloEntity not found on id = " + id); + } else { + this.service.delete(entity); + } + } +} diff --git a/src/main/java/de/szut/lf8_starter/hello/HelloEntity.java b/src/main/java/de/szut/lf8_starter/hello/HelloEntity.java index 1b42cfc..f238e2f 100644 --- a/src/main/java/de/szut/lf8_starter/hello/HelloEntity.java +++ b/src/main/java/de/szut/lf8_starter/hello/HelloEntity.java @@ -1,27 +1,27 @@ -package de.szut.lf8_starter.hello; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -@Entity -@Table(name = "hello") -public class HelloEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - private String message; - - public HelloEntity(String message) { - this.message = message; - } -} - +package de.szut.lf8_starter.hello; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "hello") +public class HelloEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String message; + + public HelloEntity(String message) { + this.message = message; + } +} + diff --git a/src/main/java/de/szut/lf8_starter/hello/HelloMapper.java b/src/main/java/de/szut/lf8_starter/hello/HelloMapper.java index d4feb7e..5639671 100644 --- a/src/main/java/de/szut/lf8_starter/hello/HelloMapper.java +++ b/src/main/java/de/szut/lf8_starter/hello/HelloMapper.java @@ -1,20 +1,20 @@ -package de.szut.lf8_starter.hello; - - -import de.szut.lf8_starter.hello.dto.HelloCreateDto; -import de.szut.lf8_starter.hello.dto.HelloGetDto; -import org.springframework.stereotype.Service; - -@Service -public class HelloMapper { - - public HelloGetDto mapToGetDto(HelloEntity entity) { - return new HelloGetDto(entity.getId(), entity.getMessage()); - } - - public HelloEntity mapCreateDtoToEntity(HelloCreateDto dto) { - var entity = new HelloEntity(); - entity.setMessage(dto.getMessage()); - return entity; - } -} +package de.szut.lf8_starter.hello; + + +import de.szut.lf8_starter.hello.dto.HelloCreateDto; +import de.szut.lf8_starter.hello.dto.HelloGetDto; +import org.springframework.stereotype.Service; + +@Service +public class HelloMapper { + + public HelloGetDto mapToGetDto(HelloEntity entity) { + return new HelloGetDto(entity.getId(), entity.getMessage()); + } + + public HelloEntity mapCreateDtoToEntity(HelloCreateDto dto) { + var entity = new HelloEntity(); + entity.setMessage(dto.getMessage()); + return entity; + } +} diff --git a/src/main/java/de/szut/lf8_starter/hello/HelloRepository.java b/src/main/java/de/szut/lf8_starter/hello/HelloRepository.java index 77c76e4..a030a27 100644 --- a/src/main/java/de/szut/lf8_starter/hello/HelloRepository.java +++ b/src/main/java/de/szut/lf8_starter/hello/HelloRepository.java @@ -1,12 +1,12 @@ -package de.szut.lf8_starter.hello; - - -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface HelloRepository extends JpaRepository { - - - List findByMessage(String message); -} +package de.szut.lf8_starter.hello; + + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface HelloRepository extends JpaRepository { + + + List findByMessage(String message); +} diff --git a/src/main/java/de/szut/lf8_starter/hello/HelloService.java b/src/main/java/de/szut/lf8_starter/hello/HelloService.java index eb254e8..b608ee2 100644 --- a/src/main/java/de/szut/lf8_starter/hello/HelloService.java +++ b/src/main/java/de/szut/lf8_starter/hello/HelloService.java @@ -1,40 +1,40 @@ -package de.szut.lf8_starter.hello; - -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Optional; - -@Service -public class HelloService { - private final HelloRepository repository; - - public HelloService(HelloRepository repository) { - this.repository = repository; - } - - public HelloEntity create(HelloEntity entity) { - return this.repository.save(entity); - } - - public List readAll() { - return this.repository.findAll(); - } - - public HelloEntity readById(long id) { - Optional optionalQualification = this.repository.findById(id); - if (optionalQualification.isEmpty()) { - return null; - } - return optionalQualification.get(); - } - - - public void delete(HelloEntity entity) { - this.repository.delete(entity); - } - - public List findByMessage(String message) { - return this.repository.findByMessage(message); - } -} +package de.szut.lf8_starter.hello; + +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class HelloService { + private final HelloRepository repository; + + public HelloService(HelloRepository repository) { + this.repository = repository; + } + + public HelloEntity create(HelloEntity entity) { + return this.repository.save(entity); + } + + public List readAll() { + return this.repository.findAll(); + } + + public HelloEntity readById(long id) { + Optional optionalQualification = this.repository.findById(id); + if (optionalQualification.isEmpty()) { + return null; + } + return optionalQualification.get(); + } + + + public void delete(HelloEntity entity) { + this.repository.delete(entity); + } + + public List findByMessage(String message) { + return this.repository.findByMessage(message); + } +} diff --git a/src/main/java/de/szut/lf8_starter/hello/dto/HelloCreateDto.java b/src/main/java/de/szut/lf8_starter/hello/dto/HelloCreateDto.java index fe47d1e..e0622d5 100644 --- a/src/main/java/de/szut/lf8_starter/hello/dto/HelloCreateDto.java +++ b/src/main/java/de/szut/lf8_starter/hello/dto/HelloCreateDto.java @@ -1,20 +1,20 @@ -package de.szut.lf8_starter.hello.dto; - -import com.fasterxml.jackson.annotation.JsonCreator; -import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.Setter; - - -@Getter -@Setter -public class HelloCreateDto { - - @Size(min = 3, message = "at least length of 3") - private String message; - - @JsonCreator - public HelloCreateDto(String message) { - this.message = message; - } -} +package de.szut.lf8_starter.hello.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + + +@Getter +@Setter +public class HelloCreateDto { + + @Size(min = 3, message = "at least length of 3") + private String message; + + @JsonCreator + public HelloCreateDto(String message) { + this.message = message; + } +} diff --git a/src/main/java/de/szut/lf8_starter/hello/dto/HelloGetDto.java b/src/main/java/de/szut/lf8_starter/hello/dto/HelloGetDto.java index babac4f..1cc51b2 100644 --- a/src/main/java/de/szut/lf8_starter/hello/dto/HelloGetDto.java +++ b/src/main/java/de/szut/lf8_starter/hello/dto/HelloGetDto.java @@ -1,17 +1,17 @@ -package de.szut.lf8_starter.hello.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -@AllArgsConstructor -@Getter -@Setter -public class HelloGetDto { - - private long id; - - private String message; - -} - +package de.szut.lf8_starter.hello.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@AllArgsConstructor +@Getter +@Setter +public class HelloGetDto { + + private long id; + + private String message; + +} + diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java b/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java index fa60491..cb43b84 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java @@ -1,41 +1,61 @@ -package de.szut.lf8_starter.project; - -import de.szut.lf8_starter.project.dto.CreateProjectDto; -import de.szut.lf8_starter.project.dto.GetProjectDto; -import org.springframework.stereotype.Service; - -@Service -public class ProjectMapper { - public ProjectEntity mapCreateDtoToEntity(CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = new ProjectEntity(); - - projectEntity.setName(createProjectDto.getName()); - projectEntity.setComment(createProjectDto.getComment()); - projectEntity.setLeadingEmployee(createProjectDto.getLeadingEmployee()); - projectEntity.setEmployees(createProjectDto.getEmployees()); - projectEntity.setContractor(createProjectDto.getContractor()); - projectEntity.setContractorName(createProjectDto.getContractorName()); - projectEntity.setStartDate(createProjectDto.getStartDate()); - projectEntity.setPlannedEndDate(createProjectDto.getPlannedEndDate()); - projectEntity.setEndDate(createProjectDto.getEndDate()); - - return projectEntity; - } - - public GetProjectDto mapToGetDto(ProjectEntity projectEntity) { - GetProjectDto getProjectDto = new GetProjectDto(); - - getProjectDto.setId(projectEntity.getId()); - getProjectDto.setName(projectEntity.getName()); - getProjectDto.setComment(projectEntity.getComment()); - getProjectDto.setLeadingEmployee(projectEntity.getLeadingEmployee()); - getProjectDto.setEmployees(projectEntity.getEmployees()); - getProjectDto.setContractor(projectEntity.getContractor()); - getProjectDto.setContractorName(projectEntity.getContractorName()); - getProjectDto.setStartDate(projectEntity.getStartDate()); - getProjectDto.setPlannedEndDate(projectEntity.getPlannedEndDate()); - getProjectDto.setEndDate(projectEntity.getEndDate()); - - return getProjectDto; - } -} +package de.szut.lf8_starter.project; + +import de.szut.lf8_starter.project.dto.CreateProjectDto; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import de.szut.lf8_starter.project.dto.UpdateProjectDto; +import org.springframework.stereotype.Service; + +@Service +public class ProjectMapper { + public ProjectEntity mapCreateDtoToEntity(CreateProjectDto createProjectDto) { + ProjectEntity projectEntity = new ProjectEntity(); + + projectEntity.setName(createProjectDto.getName()); + projectEntity.setComment(createProjectDto.getComment()); + projectEntity.setLeadingEmployee(createProjectDto.getLeadingEmployee()); + projectEntity.setEmployees(createProjectDto.getEmployees()); + projectEntity.setContractor(createProjectDto.getContractor()); + projectEntity.setContractorName(createProjectDto.getContractorName()); + projectEntity.setStartDate(createProjectDto.getStartDate()); + projectEntity.setPlannedEndDate(createProjectDto.getPlannedEndDate()); + projectEntity.setEndDate(createProjectDto.getEndDate()); + + return projectEntity; + } + + public GetProjectDto mapToGetDto(ProjectEntity projectEntity) { + GetProjectDto getProjectDto = new GetProjectDto(); + + getProjectDto.setId(projectEntity.getId()); + getProjectDto.setName(projectEntity.getName()); + getProjectDto.setComment(projectEntity.getComment()); + getProjectDto.setLeadingEmployee(projectEntity.getLeadingEmployee()); + getProjectDto.setEmployees(projectEntity.getEmployees()); + getProjectDto.setContractor(projectEntity.getContractor()); + getProjectDto.setContractorName(projectEntity.getContractorName()); + getProjectDto.setStartDate(projectEntity.getStartDate()); + getProjectDto.setPlannedEndDate(projectEntity.getPlannedEndDate()); + getProjectDto.setEndDate(projectEntity.getEndDate()); + + return getProjectDto; + } + + public ProjectEntity mapUpdateDtoToEntity(UpdateProjectDto updateProjectDto, ProjectEntity projectEntity) { + projectEntity.setName(updateProjectDto.getName() != null ? updateProjectDto.getName() : projectEntity.getName()); + projectEntity.setLeadingEmployee(updateProjectDto.getLeadingEmployee() != null ? updateProjectDto.getLeadingEmployee() : projectEntity.getLeadingEmployee()); + projectEntity.setContractor(updateProjectDto.getContractor() != null ? updateProjectDto.getContractor() : projectEntity.getContractor()); + projectEntity.setContractorName(updateProjectDto.getContractorName() != null ? updateProjectDto.getContractorName() : projectEntity.getContractorName()); + projectEntity.setComment(updateProjectDto.getComment() != null ? updateProjectDto.getComment() : projectEntity.getComment()); + projectEntity.setStartDate(updateProjectDto.getStartDate() != null ? updateProjectDto.getStartDate() : projectEntity.getStartDate()); + projectEntity.setPlannedEndDate(updateProjectDto.getPlannedEndDate() != null ? updateProjectDto.getPlannedEndDate() : projectEntity.getPlannedEndDate()); + projectEntity.setEndDate(updateProjectDto.getEndDate() != null ? updateProjectDto.getEndDate() : projectEntity.getEndDate()); + + if (updateProjectDto.getEmployees() != null) { + projectEntity.getEmployees().clear(); + + projectEntity.setEmployees(updateProjectDto.getEmployees()); + } + + return projectEntity; + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectService.java b/src/main/java/de/szut/lf8_starter/project/ProjectService.java index a815350..917607f 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectService.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectService.java @@ -1,34 +1,33 @@ -package de.szut.lf8_starter.project; - -import de.szut.lf8_starter.exceptionHandling.ResourceNotFoundException; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Optional; - -@Service -public class ProjectService { - private final ProjectRepository projectRepository; - - public ProjectService(ProjectRepository projectRepository) { - this.projectRepository = projectRepository; - } - - public ProjectEntity create(ProjectEntity projectEntity) { - return this.projectRepository.save(projectEntity); - } - - public List readAll() { - return this.projectRepository.findAll(); - } - - public ProjectEntity findById(Long id) { - Optional articleEntity = projectRepository.findById(id); - - if (articleEntity.isEmpty()) { - throw new ResourceNotFoundException("Project with id " + id + " not found"); - } - - return articleEntity.get(); - } -} +package de.szut.lf8_starter.project; + +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class ProjectService { + private final ProjectRepository projectRepository; + + public ProjectService(ProjectRepository projectRepository) { + this.projectRepository = projectRepository; + } + + public ProjectEntity create(ProjectEntity projectEntity) { + return this.projectRepository.save(projectEntity); + } + + public List readAll() { + return this.projectRepository.findAll(); + } + + public Optional findById(Long id) { + return projectRepository.findById(id); + } + + public ProjectEntity update(ProjectEntity project) { + this.projectRepository.save(project); + + return project; + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java index d04ac20..d8e4207 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java @@ -1,40 +1,46 @@ -package de.szut.lf8_starter.project.action; - -import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectMapper; -import de.szut.lf8_starter.project.ProjectService; -import de.szut.lf8_starter.project.dto.GetProjectDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping(value = "projects") -public class GetProjectAction { - private final ProjectService projectService; - private final ProjectMapper projectMapper; - - public GetProjectAction(ProjectService projectService, ProjectMapper projectMapper) { - this.projectService = projectService; - this.projectMapper = projectMapper; - } - - @Operation(summary = "Find project by ID") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Project found", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class)) - }), - @ApiResponse(responseCode = "404", description = "Project not found", content = @Content) - }) - @GetMapping("/{id}") - public ResponseEntity findArticleById(@PathVariable Long id) { - ProjectEntity project = this.projectService.findById(id); - - return new ResponseEntity<>(this.projectMapper.mapToGetDto(project), HttpStatus.OK); - } -} +package de.szut.lf8_starter.project.action; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +@RestController +@RequestMapping(value = "projects") +public class GetProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public GetProjectAction(ProjectService projectService, ProjectMapper projectMapper) { + this.projectService = projectService; + this.projectMapper = projectMapper; + } + + @Operation(summary = "Find project by ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Project found", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class)) + }), + @ApiResponse(responseCode = "404", description = "Project not found", content = @Content) + }) + @GetMapping("/{id}") + public ResponseEntity findArticleById(@PathVariable Long id) { + Optional project = this.projectService.findById(id); + + if (project.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(this.projectMapper.mapToGetDto(project.get()), HttpStatus.OK); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/action/UpdateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/UpdateProjectAction.java new file mode 100644 index 0000000..9c174e3 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/action/UpdateProjectAction.java @@ -0,0 +1,50 @@ +package de.szut.lf8_starter.project.action; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectMapper; +import de.szut.lf8_starter.project.ProjectService; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import de.szut.lf8_starter.project.dto.UpdateProjectDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +@RestController +@RequestMapping(value = "/projects") +public class UpdateProjectAction { + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public UpdateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + this.projectService = projectService; + this.projectMapper = mappingService; + } + + @Operation(summary = "Update a project by ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Project updated successfully", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))), + @ApiResponse(responseCode = "404", description = "Project not found", content = @Content) + }) + @PutMapping("/{id}") + public ResponseEntity updateSupplier(@PathVariable Long id, @Valid @RequestBody UpdateProjectDto updateProjectDto) { + Optional project = this.projectService.findById(id); + + if (project.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + ProjectEntity updatedProject = this.projectMapper.mapUpdateDtoToEntity(updateProjectDto, project.get()); + this.projectService.update(updatedProject); + + return new ResponseEntity<>(this.projectMapper.mapToGetDto(updatedProject), HttpStatus.OK); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/UpdateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/UpdateProjectDto.java new file mode 100644 index 0000000..6d94232 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/UpdateProjectDto.java @@ -0,0 +1,36 @@ +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class UpdateProjectDto { + private String name; + + private Long leadingEmployee; + + private List employees; + + private Long contractor; + + private String contractorName; + + private String comment; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate startDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate plannedEndDate; + + @JsonFormat(pattern = "dd.MM.yyyy") + private LocalDate endDate; +} diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java index 3051a80..b35df69 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakLogoutHandler.java @@ -1,49 +1,49 @@ -package de.szut.lf8_starter.security; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Slf4j -@Component -public class KeycloakLogoutHandler implements LogoutHandler { - - - private final RestTemplate restTemplate; - - public KeycloakLogoutHandler(RestTemplate restTemplate) { - this.restTemplate = restTemplate; - } - - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { - logout(auth); - } - - public void logout(Authentication auth) { - logoutFromKeycloak((OidcUser) auth.getPrincipal()); - } - - private void logoutFromKeycloak(OidcUser user) { - String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; - UriComponentsBuilder builder = UriComponentsBuilder - .fromUriString(endSessionEndpoint) - .queryParam("id_token_hint", user.getIdToken().getTokenValue()); - - ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); - if (logoutResponse.getStatusCode().is2xxSuccessful()) { - log.info("Successfulley logged out from Keycloak"); - } else { - log.error("Could not propagate logout to Keycloak"); - } - } - -} +package de.szut.lf8_starter.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Slf4j +@Component +public class KeycloakLogoutHandler implements LogoutHandler { + + + private final RestTemplate restTemplate; + + public KeycloakLogoutHandler(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { + logout(auth); + } + + public void logout(Authentication auth) { + logoutFromKeycloak((OidcUser) auth.getPrincipal()); + } + + private void logoutFromKeycloak(OidcUser user) { + String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; + UriComponentsBuilder builder = UriComponentsBuilder + .fromUriString(endSessionEndpoint) + .queryParam("id_token_hint", user.getIdToken().getTokenValue()); + + ResponseEntity logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); + if (logoutResponse.getStatusCode().is2xxSuccessful()) { + log.info("Successfulley logged out from Keycloak"); + } else { + log.error("Could not propagate logout to Keycloak"); + } + } + +} diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java index f64bdd6..cd03823 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java @@ -1,94 +1,94 @@ -package de.szut.lf8_starter.security; - -import java.util.*; -import java.util.stream.Collectors; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.http.HttpMethod; -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.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.security.web.session.HttpSessionEventPublisher; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; - -@Configuration -@EnableWebSecurity -class KeycloakSecurityConfig { - - private static final String REALM_ACCESS_CLAIM = "realm_access"; - private static final String ROLES_CLAIM = "roles"; - - KeycloakSecurityConfig() { - } - - @Bean - public SessionRegistry sessionRegistry() { - return new SessionRegistryImpl(); - } - - @Bean - protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { - return new RegisterSessionAuthenticationStrategy(sessionRegistry()); - } - - @Bean - public HttpSessionEventPublisher httpSessionEventPublisher() { - return new HttpSessionEventPublisher(); - } - - - @Bean - public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { - - http.authorizeHttpRequests(auth -> auth - .requestMatchers(new AntPathRequestMatcher("/welcome")) - .permitAll() - .requestMatchers( - new AntPathRequestMatcher("/swagger"), - new AntPathRequestMatcher("/swagger-ui/**"), - new AntPathRequestMatcher("/v3/api-docs/**")) - .permitAll() - .requestMatchers(new AntPathRequestMatcher("/hello/**")) - .hasRole("user") - .requestMatchers(new AntPathRequestMatcher("/roles")) - .authenticated() - .requestMatchers(new AntPathRequestMatcher("/")) - .permitAll() - .anyRequest() - .authenticated()).oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults())); - return http.build(); - } - - @Bean - public JwtAuthenticationConverter jwtAuthenticationConverter() { - JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); - jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> { - List grantedAuthorities = new ArrayList<>(); - - Map realmAccess = jwt.getClaim(REALM_ACCESS_CLAIM); - if (realmAccess != null && realmAccess.containsKey(ROLES_CLAIM)) { - List roles = (List) realmAccess.get(ROLES_CLAIM); - for (String role : roles) { - grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)); - } - } - - return grantedAuthorities; - }); - return jwtAuthenticationConverter; - } -} +package de.szut.lf8_starter.security; + +import java.util.*; +import java.util.stream.Collectors; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpMethod; +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.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; +import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.session.HttpSessionEventPublisher; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +class KeycloakSecurityConfig { + + private static final String REALM_ACCESS_CLAIM = "realm_access"; + private static final String ROLES_CLAIM = "roles"; + + KeycloakSecurityConfig() { + } + + @Bean + public SessionRegistry sessionRegistry() { + return new SessionRegistryImpl(); + } + + @Bean + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new RegisterSessionAuthenticationStrategy(sessionRegistry()); + } + + @Bean + public HttpSessionEventPublisher httpSessionEventPublisher() { + return new HttpSessionEventPublisher(); + } + + + @Bean + public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { + + http.authorizeHttpRequests(auth -> auth + .requestMatchers(new AntPathRequestMatcher("/welcome")) + .permitAll() + .requestMatchers( + new AntPathRequestMatcher("/swagger"), + new AntPathRequestMatcher("/swagger-ui/**"), + new AntPathRequestMatcher("/v3/api-docs/**")) + .permitAll() + .requestMatchers(new AntPathRequestMatcher("/hello/**")) + .hasRole("user") + .requestMatchers(new AntPathRequestMatcher("/roles")) + .authenticated() + .requestMatchers(new AntPathRequestMatcher("/")) + .permitAll() + .anyRequest() + .authenticated()).oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults())); + return http.build(); + } + + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> { + List grantedAuthorities = new ArrayList<>(); + + Map realmAccess = jwt.getClaim(REALM_ACCESS_CLAIM); + if (realmAccess != null && realmAccess.containsKey(ROLES_CLAIM)) { + List roles = (List) realmAccess.get(ROLES_CLAIM); + for (String role : roles) { + grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)); + } + } + + return grantedAuthorities; + }); + return jwtAuthenticationConverter; + } +} diff --git a/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java b/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java index 4effa6b..41293bc 100644 --- a/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java +++ b/src/main/java/de/szut/lf8_starter/welcome/WelcomeController.java @@ -1,25 +1,25 @@ -package de.szut.lf8_starter.welcome; - -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Collection; - -@RestController -public class WelcomeController { - - @GetMapping("/welcome") - public String welcome() { - return "welcome to lf8_starter"; - } - - @GetMapping("/roles") - public ResponseEntity> getRoles(Authentication authentication) { - return ResponseEntity.ok((Collection) authentication.getAuthorities()); - } - - -} +package de.szut.lf8_starter.welcome; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collection; + +@RestController +public class WelcomeController { + + @GetMapping("/welcome") + public String welcome() { + return "welcome to lf8_starter"; + } + + @GetMapping("/roles") + public ResponseEntity> getRoles(Authentication authentication) { + return ResponseEntity.ok((Collection) authentication.getAuthorities()); + } + + +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/FindProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java similarity index 87% rename from src/test/java/de/szut/lf8_starter/integration/project/FindProjectActionTest.java rename to src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java index dacb171..751d002 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/FindProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java @@ -1,54 +1,59 @@ -package de.szut.lf8_starter.integration.project; - -import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectRepository; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; - -import java.time.LocalDate; -import java.util.List; - -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc(addFilters = false) -class FindProjectActionTest { - @Autowired - private MockMvc mockMvc; - @Autowired - private ProjectRepository projectRepository; - - @Test - void createProjectTest() throws Exception { - var project = new ProjectEntity(); - project.setId(1); - project.setComment("comment"); - project.setContractor(1); - project.setContractorName("contractorName"); - project.setEndDate(LocalDate.of(2024, 1, 1)); - project.setLeadingEmployee(1); - project.setName("name"); - project.setStartDate(LocalDate.of(2021, 1, 1)); - project.setEmployees(List.of(1L, 2L, 3L)); - this.projectRepository.save(project); - - this.mockMvc.perform(get("/projects/1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("id").value(1)) - .andExpect(jsonPath("comment").value("comment")) - .andExpect(jsonPath("contractor").value(1)) - .andExpect(jsonPath("contractor_name").value("contractorName")) - .andExpect(jsonPath("end_date").value("01.01.2024")) - .andExpect(jsonPath("leading_employee").value(1)) - .andExpect(jsonPath("name").value("name")) - .andExpect(jsonPath("start_date").value("01.01.2021")) - .andExpect(jsonPath("employees").isArray()) - .andExpect(jsonPath("employees", hasSize(3))); - } -} +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.List; + +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class GetProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void getProjectTest() throws Exception { + var project = new ProjectEntity(); + project.setId(1); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project); + + this.mockMvc.perform(get("/projects/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("id").value(1)) + .andExpect(jsonPath("comment").value("comment")) + .andExpect(jsonPath("contractor").value(1)) + .andExpect(jsonPath("contractor_name").value("contractorName")) + .andExpect(jsonPath("end_date").value("01.01.2024")) + .andExpect(jsonPath("leading_employee").value(1)) + .andExpect(jsonPath("name").value("name")) + .andExpect(jsonPath("start_date").value("01.01.2021")) + .andExpect(jsonPath("employees").isArray()) + .andExpect(jsonPath("employees", hasSize(3))); + } + + @Test + void getProjectShouldReturnNotFoundResponseWhenProjectIsNotFound() throws Exception { + this.mockMvc.perform(get("/projects/2")).andExpect(status().isNotFound()); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java new file mode 100644 index 0000000..4e53c8c --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java @@ -0,0 +1,140 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class UpdateProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void updateProjectShouldUpdateProject() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setId(1); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project); + + String content = """ + { + "name": "updatedName", + "leading_employee": 2, + "employees": [3, 4, 5], + "contractor": 6, + "contractor_name": "Updated Contractor name", + "comment": "new goal of project", + "start_date": "01.01.2021", + "planned_end_date": "01.01.2022" + } + """; + + final var contentAsString = this.mockMvc.perform( + put("/projects/1").content(content).contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("name", is("updatedName"))) + .andExpect(jsonPath("leading_employee", is(2))) + .andExpect(jsonPath("employees", is(Arrays.asList(3, 4, 5)))) + .andExpect(jsonPath("contractor", is(6))) + .andExpect(jsonPath("contractor_name", is("Updated Contractor name"))) + .andExpect(jsonPath("comment", is("new goal of project"))) + .andExpect(jsonPath("start_date", is("01.01.2021"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2022"))) + .andReturn() + .getResponse() + .getContentAsString(); + + final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + + final var existingProject = this.projectRepository.findById(id); + assertThat(existingProject.get().getName()).isEqualTo("updatedName"); + assertThat(existingProject.get().getLeadingEmployee()).isEqualTo(2); + assertThat(existingProject.get().getContractor()).isEqualTo(6); + assertThat(existingProject.get().getContractorName()).isEqualTo("Updated Contractor name"); + assertThat(existingProject.get().getComment()).isEqualTo("new goal of project"); + assertThat(existingProject.get().getStartDate()).isEqualTo(LocalDate.of(2021, 1, 1)); + assertThat(existingProject.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2022, 1, 1)); + + } + + @Test + void updateProjectShouldUpdateProjectPartially() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setId(1); + project.setName("name"); + project.setLeadingEmployee(1); + project.setContractor(1); + project.setComment("comment"); + project.setEmployees(List.of(1L, 2L, 3L)); + project.setContractorName("contractorName"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setPlannedEndDate(LocalDate.of(2023, 1, 1)); + project.setEndDate(LocalDate.of(2024, 1, 1)); + this.projectRepository.save(project); + + String content = """ + {} + """; + + final var contentAsString = this.mockMvc.perform( + put("/projects/1").content(content).contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("name", is("name"))) + .andExpect(jsonPath("leading_employee", is(1))) + .andExpect(jsonPath("employees", is(List.of(1,2,3)))) + .andExpect(jsonPath("contractor", is(1))) + .andExpect(jsonPath("contractor_name", is("contractorName"))) + .andExpect(jsonPath("comment", is("comment"))) + .andExpect(jsonPath("start_date", is("01.01.2021"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2023"))) + .andExpect(jsonPath("end_date", is("01.01.2024"))) + .andReturn() + .getResponse() + .getContentAsString(); + + final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + + final var existingProject = this.projectRepository.findById(id); + assertThat(existingProject.get().getName()).isEqualTo("name"); + assertThat(existingProject.get().getLeadingEmployee()).isEqualTo(1); + assertThat(existingProject.get().getContractor()).isEqualTo(1); + assertThat(existingProject.get().getContractorName()).isEqualTo("contractorName"); + assertThat(existingProject.get().getComment()).isEqualTo("comment"); + assertThat(existingProject.get().getStartDate()).isEqualTo(LocalDate.of(2021, 1, 1)); + assertThat(existingProject.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2023, 1, 1)); + assertThat(existingProject.get().getEndDate()).isEqualTo(LocalDate.of(2024, 1, 1)); + } + + @Test + void updateProjectShouldReturnNotFoundResponseWhenProjectIsNotFound() throws Exception { + this.mockMvc.perform(put("/projects/2").content("{}").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isNotFound()); + } +} From b2ef8ce2abe610ca7263e82fc14bc8fec12ec23d Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Wed, 2 Oct 2024 09:43:10 +0000 Subject: [PATCH 23/31] feat(project): Delete project (SCRUM-35) (!29) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/29 Reviewed-by: Phan Huy Tran Co-authored-by: Jan Klattenhoff Co-committed-by: Jan Klattenhoff --- .../lf8_starter/project/ProjectService.java | 4 ++ .../project/action/RemoveProjectAction.java | 40 ++++++++++++++++ .../project/DeleteProjectActionTest.java | 47 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/DeleteProjectActionTest.java diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectService.java b/src/main/java/de/szut/lf8_starter/project/ProjectService.java index 917607f..31efbec 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectService.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectService.java @@ -30,4 +30,8 @@ public class ProjectService { return project; } + + public void delete(Long id) { + this.projectRepository.deleteById(id); + } } diff --git a/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java new file mode 100644 index 0000000..d14dbee --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java @@ -0,0 +1,40 @@ +package de.szut.lf8_starter.project.action; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectService; +import de.szut.lf8_starter.project.dto.GetProjectDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +@RestController +@RequestMapping(value = "projects") +public class RemoveProjectAction { + private final ProjectService projectService; + + public RemoveProjectAction(ProjectService projectService) { + this.projectService = projectService; + } + + @Operation(summary = "Remove project by ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Project deleted", content = {}), + @ApiResponse(responseCode = "404", description = "Project not found", content = @Content) + }) + @DeleteMapping("/{id}") + public ResponseEntity findArticleById(@PathVariable Long id) { + Optional project = this.projectService.findById(id); + if (project.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + this.projectService.delete(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/DeleteProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/DeleteProjectActionTest.java new file mode 100644 index 0000000..93285ab --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/DeleteProjectActionTest.java @@ -0,0 +1,47 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class RemoveProjectActionTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + + @Test + void deleteProjectTest() throws Exception { + var project = new ProjectEntity(); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project); + + this.mockMvc.perform(delete("/projects/" + project.getId())) + .andExpect(status().isNoContent()); + } + + @Test + void deleteProjectShouldReturnNotFoundResponseWhenProjectIsNotFound() throws Exception { + this.mockMvc.perform(get("/projects/2987")).andExpect(status().isNotFound()); + } +} From 8230337839677c146bd53a6ce14d4378d53368e6 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Wed, 2 Oct 2024 10:09:07 +0000 Subject: [PATCH 24/31] refactor: remove unused imports and clean up code (!30) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/30 Reviewed-by: Phan Huy Tran Co-authored-by: Jan Klattenhoff Co-committed-by: Jan Klattenhoff --- .../java/de/szut/lf8_starter/hello/HelloController.java | 3 --- .../de/szut/lf8_starter/project/dto/GetProjectDto.java | 2 -- .../szut/lf8_starter/security/KeycloakSecurityConfig.java | 7 ------- .../de/szut/lf8_starter/Lf8StarterApplicationTests.java | 1 - .../project/ProjectFindAllNotAuthenticated.java | 5 ----- ...ProjectActionTest.java => RemoveProjectActionTest.java} | 0 6 files changed, 18 deletions(-) rename src/test/java/de/szut/lf8_starter/integration/project/{DeleteProjectActionTest.java => RemoveProjectActionTest.java} (100%) diff --git a/src/main/java/de/szut/lf8_starter/hello/HelloController.java b/src/main/java/de/szut/lf8_starter/hello/HelloController.java index 3a4a993..f6baab4 100644 --- a/src/main/java/de/szut/lf8_starter/hello/HelloController.java +++ b/src/main/java/de/szut/lf8_starter/hello/HelloController.java @@ -14,9 +14,6 @@ import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.stream.Collectors; - @RestController @RequestMapping(value = "hello") @PreAuthorize("hasAnyAuthority('user')") diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java index 614ece1..90ebe5e 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java @@ -3,8 +3,6 @@ package de.szut.lf8_starter.project.dto; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java index cd03823..99fbc5d 100644 --- a/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java +++ b/src/main/java/de/szut/lf8_starter/security/KeycloakSecurityConfig.java @@ -1,24 +1,17 @@ package de.szut.lf8_starter.security; import java.util.*; -import java.util.stream.Collectors; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.http.HttpMethod; 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.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; diff --git a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java index b5c7f7b..942f2cb 100644 --- a/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java +++ b/src/test/java/de/szut/lf8_starter/Lf8StarterApplicationTests.java @@ -2,7 +2,6 @@ package de.szut.lf8_starter; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; @SpringBootTest class Lf8StarterApplicationTests { diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java index 8c62848..307ad4d 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java @@ -1,6 +1,5 @@ package de.szut.lf8_starter.integration.project; -import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -9,12 +8,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.web.servlet.MockMvc; -import java.time.LocalDate; -import java.util.List; -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) diff --git a/src/test/java/de/szut/lf8_starter/integration/project/DeleteProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java similarity index 100% rename from src/test/java/de/szut/lf8_starter/integration/project/DeleteProjectActionTest.java rename to src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java From 1e38efaa63f8b640c0b7ee99b8d5d07ab0bc5866 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Wed, 2 Oct 2024 10:29:39 +0000 Subject: [PATCH 25/31] refactor(test): remove access modifiers from test classes (!31) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/31 Reviewed-by: Phan Huy Tran Co-authored-by: Jan Klattenhoff Co-committed-by: Jan Klattenhoff --- .../integration/project/CreateProjectActionTest.java | 2 +- .../integration/project/ProjectFindAllNotAuthenticated.java | 2 +- .../integration/project/ProjectFindAllSuccessTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java index 36eee54..af10c64 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -20,7 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) -public class CreateProjectActionTest { +class CreateProjectActionTest { @Autowired private MockMvc mockMvc; @Autowired diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java index 307ad4d..bda3c51 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java @@ -14,7 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc(addFilters = true) -public class ProjectFindAllNotAuthenticated { +class ProjectFindAllNotAuthenticated { @Autowired private ProjectRepository projectRepository; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java index 8f1974a..3fa8bd8 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java @@ -18,7 +18,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc(addFilters = false) -public class ProjectFindAllSuccessTest { +class ProjectFindAllSuccessTest { @Autowired private ProjectRepository projectRepository; From 1008278fde124a5f4e345261f2b013be2414e96f Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 23 Oct 2024 08:39:25 +0000 Subject: [PATCH 26/31] feat: Implement add employee to project route (SCRUM-2) (!32) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/32 Reviewed-by: Jan Gleytenhoover Co-authored-by: Phan Huy Tran Co-committed-by: Phan Huy Tran --- requests/employee/createEmployee.http | 12 +++ requests/employee/deleteEmployee.http | 2 + requests/employee/getAllEmployees.http | 2 + requests/employee/getEmployee.http | 2 + requests/project/addEmployeeToProject.http | 2 + requests/{ => project}/createProject.http | 0 requests/{ => project}/getAllProjects.http | 0 requests/{ => project}/getProject.http | 0 requests/{ => project}/updateProject.http | 0 .../lf8_starter/employee/EmployeeService.java | 34 ++++++++ .../lf8_starter/project/ProjectEntity.java | 2 +- .../lf8_starter/project/ProjectService.java | 4 +- .../{ => crud}/CreateProjectAction.java | 2 +- .../{ => crud}/GetAllProjectsAction.java | 2 +- .../action/{ => crud}/GetProjectAction.java | 7 +- .../{ => crud}/UpdateProjectAction.java | 2 +- .../employee/AddEmployeeToProjectAction.java | 55 +++++++++++++ .../lf8_starter/project/dto/EmployeeDto.java | 22 ++++++ .../project/AddEmployeeToProjectTest.java | 77 +++++++++++++++++++ .../project/CreateProjectActionTest.java | 28 +++---- .../project/GetProjectActionTest.java | 4 +- .../project/ProjectFindAllSuccessTest.java | 4 +- .../project/RemoveProjectActionTest.java | 2 +- .../project/UpdateProjectActionTest.java | 15 ++-- 24 files changed, 246 insertions(+), 34 deletions(-) create mode 100644 requests/employee/createEmployee.http create mode 100644 requests/employee/deleteEmployee.http create mode 100644 requests/employee/getAllEmployees.http create mode 100644 requests/employee/getEmployee.http create mode 100644 requests/project/addEmployeeToProject.http rename requests/{ => project}/createProject.http (100%) rename requests/{ => project}/getAllProjects.http (100%) rename requests/{ => project}/getProject.http (100%) rename requests/{ => project}/updateProject.http (100%) create mode 100644 src/main/java/de/szut/lf8_starter/employee/EmployeeService.java rename src/main/java/de/szut/lf8_starter/project/action/{ => crud}/CreateProjectAction.java (97%) rename src/main/java/de/szut/lf8_starter/project/action/{ => crud}/GetAllProjectsAction.java (97%) rename src/main/java/de/szut/lf8_starter/project/action/{ => crud}/GetProjectAction.java (83%) rename src/main/java/de/szut/lf8_starter/project/action/{ => crud}/UpdateProjectAction.java (95%) create mode 100644 src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/EmployeeDto.java create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java diff --git a/requests/employee/createEmployee.http b/requests/employee/createEmployee.http new file mode 100644 index 0000000..a170fe2 --- /dev/null +++ b/requests/employee/createEmployee.http @@ -0,0 +1,12 @@ +POST https://employee.szut.dev/employees +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{ + "firstName": "Jan", + "lastName": "Klattenhoff", + "street": "Pirolweg 17", + "postcode": "27777", + "city": "Gandakersee", + "phone": "017684984816" +} \ No newline at end of file diff --git a/requests/employee/deleteEmployee.http b/requests/employee/deleteEmployee.http new file mode 100644 index 0000000..cf1d827 --- /dev/null +++ b/requests/employee/deleteEmployee.http @@ -0,0 +1,2 @@ +DELETE https://employee.szut.dev/employees/310 +Authorization: Bearer {{auth_token}} \ No newline at end of file diff --git a/requests/employee/getAllEmployees.http b/requests/employee/getAllEmployees.http new file mode 100644 index 0000000..c3911be --- /dev/null +++ b/requests/employee/getAllEmployees.http @@ -0,0 +1,2 @@ +GET https://employee.szut.dev/employees +Authorization: Bearer {{auth_token}} \ No newline at end of file diff --git a/requests/employee/getEmployee.http b/requests/employee/getEmployee.http new file mode 100644 index 0000000..eddf4d9 --- /dev/null +++ b/requests/employee/getEmployee.http @@ -0,0 +1,2 @@ +GET https://employee.szut.dev/employees/312 +Authorization: Bearer {{auth_token}} \ No newline at end of file diff --git a/requests/project/addEmployeeToProject.http b/requests/project/addEmployeeToProject.http new file mode 100644 index 0000000..981839a --- /dev/null +++ b/requests/project/addEmployeeToProject.http @@ -0,0 +1,2 @@ +POST http://localhost:8080/projects/1/employees/312 +Authorization: Bearer {{auth_token}} \ No newline at end of file diff --git a/requests/createProject.http b/requests/project/createProject.http similarity index 100% rename from requests/createProject.http rename to requests/project/createProject.http diff --git a/requests/getAllProjects.http b/requests/project/getAllProjects.http similarity index 100% rename from requests/getAllProjects.http rename to requests/project/getAllProjects.http diff --git a/requests/getProject.http b/requests/project/getProject.http similarity index 100% rename from requests/getProject.http rename to requests/project/getProject.http diff --git a/requests/updateProject.http b/requests/project/updateProject.http similarity index 100% rename from requests/updateProject.http rename to requests/project/updateProject.http diff --git a/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java b/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java new file mode 100644 index 0000000..a78f76b --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java @@ -0,0 +1,34 @@ +package de.szut.lf8_starter.employee; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +@Service +public class EmployeeService { + private final RestTemplate restTemplate; + + public EmployeeService(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + public boolean employeeExists(String accessToken, Long employeeId) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken.replace("Bearer ", "")); + + HttpEntity requestEntity = new HttpEntity<>(headers); + + String url = "https://employee.szut.dev/employees/" + employeeId; + + try { + restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); + } catch (HttpClientErrorException.NotFound e) { + return false; + } + + return true; + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java index d9c35a1..6aa7127 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectEntity.java @@ -25,7 +25,7 @@ public class ProjectEntity { private long leadingEmployee; - @ElementCollection + @ElementCollection(fetch = FetchType.EAGER) private List employees; private long contractor; diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectService.java b/src/main/java/de/szut/lf8_starter/project/ProjectService.java index 31efbec..17afcea 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectService.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectService.java @@ -25,10 +25,8 @@ public class ProjectService { return projectRepository.findById(id); } - public ProjectEntity update(ProjectEntity project) { + public void update(ProjectEntity project) { this.projectRepository.save(project); - - return project; } public void delete(Long id) { diff --git a/src/main/java/de/szut/lf8_starter/project/action/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java similarity index 97% rename from src/main/java/de/szut/lf8_starter/project/action/CreateProjectAction.java rename to src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java index 3e636b5..f21646d 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.action; +package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectMapper; diff --git a/src/main/java/de/szut/lf8_starter/project/action/GetAllProjectsAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/GetAllProjectsAction.java similarity index 97% rename from src/main/java/de/szut/lf8_starter/project/action/GetAllProjectsAction.java rename to src/main/java/de/szut/lf8_starter/project/action/crud/GetAllProjectsAction.java index 039a104..5fb346a 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/GetAllProjectsAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/GetAllProjectsAction.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.action; +package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectMapper; import de.szut.lf8_starter.project.ProjectService; diff --git a/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/GetProjectAction.java similarity index 83% rename from src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java rename to src/main/java/de/szut/lf8_starter/project/action/crud/GetProjectAction.java index d8e4207..eba31c1 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/GetProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/GetProjectAction.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.action; +package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectMapper; @@ -11,7 +11,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.util.Optional; diff --git a/src/main/java/de/szut/lf8_starter/project/action/UpdateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/UpdateProjectAction.java similarity index 95% rename from src/main/java/de/szut/lf8_starter/project/action/UpdateProjectAction.java rename to src/main/java/de/szut/lf8_starter/project/action/crud/UpdateProjectAction.java index 9c174e3..29a4ad5 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/UpdateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/UpdateProjectAction.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.action; +package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectMapper; diff --git a/src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java new file mode 100644 index 0000000..1e14ae0 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java @@ -0,0 +1,55 @@ +package de.szut.lf8_starter.project.action.employee; + +import de.szut.lf8_starter.employee.EmployeeService; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Optional; + +@RestController +public class AddEmployeeToProjectAction { + private final ProjectService projectService; + private final EmployeeService employeeService; + + public AddEmployeeToProjectAction(ProjectService projectService, EmployeeService employeeService) { + this.projectService = projectService; + this.employeeService = employeeService; + } + + @Operation(summary = "Add an employee to a project") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Employee added to project"), + @ApiResponse(responseCode = "404", description = "Project or employee not found", content = @Content) + }) + @PostMapping("/projects/{projectId}/employees/{employeeId}") + public ResponseEntity create( + @PathVariable Long projectId, + @PathVariable Long employeeId, + @RequestHeader("Authorization") String accessToken + ) { + Optional project = this.projectService.findById(projectId); + + if (project.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + if (!this.employeeService.employeeExists(accessToken, employeeId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + project.get().getEmployees().add(employeeId); + this.projectService.update(project.get()); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/EmployeeDto.java b/src/main/java/de/szut/lf8_starter/project/dto/EmployeeDto.java new file mode 100644 index 0000000..05909e1 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/EmployeeDto.java @@ -0,0 +1,22 @@ +package de.szut.lf8_starter.project.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class EmployeeDto { + private long id; + private String firstName; + private String lastName; + private String street; + private String postcode; + private String city; + private String phone; + private List skillSet; +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java b/src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java new file mode 100644 index 0000000..4193e77 --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java @@ -0,0 +1,77 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.*; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class AddEmployeeToProjectActionIntegrationTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ProjectRepository projectRepository; + + @Test + void addEmployeeToProjectTest() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project); + + + mockMvc.perform(post("/projects/{projectId}/employees/{employeeId}", 1, 312) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + ProjectEntity updatedProject = projectRepository.findById(1L).get(); + + assert updatedProject.getEmployees().contains(312L); + } + + private String getBearerToken() { + String url = "https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", "password"); + map.add("client_id", "employee-management-service"); + map.add("username", "user"); + map.add("password", "test"); + + HttpEntity> request = new HttpEntity<>(map, headers); + + ResponseEntity response = this.restTemplate.exchange(url, HttpMethod.POST, request, Map.class); + + return Objects.requireNonNull(response.getBody()).get("access_token").toString(); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java index af10c64..27fe186 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -1,5 +1,6 @@ package de.szut.lf8_starter.integration.project; +import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectRepository; import org.json.JSONObject; import org.junit.jupiter.api.Test; @@ -11,11 +12,12 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; import java.util.Arrays; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @@ -41,9 +43,9 @@ class CreateProjectActionTest { } """; - final var contentAsString = this.mockMvc.perform( - post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) - ) + final String contentAsString = this.mockMvc.perform( + post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) + ) .andExpect(status().isCreated()) .andExpect(jsonPath("name", is("name"))) .andExpect(jsonPath("leading_employee", is(1))) @@ -57,15 +59,15 @@ class CreateProjectActionTest { .getResponse() .getContentAsString(); - final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + final long id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); - final var project = this.projectRepository.findById(id); - assertThat(project.get().getName()).isEqualTo("name"); - assertThat(project.get().getLeadingEmployee()).isEqualTo(1); - assertThat(project.get().getContractor()).isEqualTo(4); - assertThat(project.get().getContractorName()).isEqualTo("Peter File"); - assertThat(project.get().getComment()).isEqualTo("goal of project"); - assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); - assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); + final Optional project = this.projectRepository.findById(id); + assertThat(project.get().getName()).isEqualTo("name"); + assertThat(project.get().getLeadingEmployee()).isEqualTo(1); + assertThat(project.get().getContractor()).isEqualTo(4); + assertThat(project.get().getContractorName()).isEqualTo("Peter File"); + assertThat(project.get().getComment()).isEqualTo("goal of project"); + assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); + assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); } } diff --git a/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java index 751d002..1b6ed3f 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java @@ -26,7 +26,7 @@ class GetProjectActionTest { @Test void getProjectTest() throws Exception { - var project = new ProjectEntity(); + ProjectEntity project = new ProjectEntity(); project.setId(1); project.setComment("comment"); project.setContractor(1); @@ -54,6 +54,6 @@ class GetProjectActionTest { @Test void getProjectShouldReturnNotFoundResponseWhenProjectIsNotFound() throws Exception { - this.mockMvc.perform(get("/projects/2")).andExpect(status().isNotFound()); + this.mockMvc.perform(get("/projects/1111")).andExpect(status().isNotFound()); } } diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java index 3fa8bd8..ff3a642 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java @@ -27,7 +27,7 @@ class ProjectFindAllSuccessTest { @Test void findAllProjectsTest() throws Exception { - var project = new ProjectEntity(); + ProjectEntity project = new ProjectEntity(); project.setId(1); project.setComment("comment"); project.setContractor(1); @@ -36,7 +36,7 @@ class ProjectFindAllSuccessTest { project.setLeadingEmployee(1); project.setName("name"); project.setStartDate(LocalDate.of(2021, 1, 1)); - project.setEmployees(List.of(1L,2L,3L)); + project.setEmployees(List.of(1L, 2L, 3L)); this.projectRepository.save(project); this.mockMvc.perform(get("/projects")) diff --git a/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java index 93285ab..c2c47dd 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java @@ -25,7 +25,7 @@ class RemoveProjectActionTest { @Test void deleteProjectTest() throws Exception { - var project = new ProjectEntity(); + ProjectEntity project = new ProjectEntity(); project.setComment("comment"); project.setContractor(1); project.setContractorName("contractorName"); diff --git a/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java index 4e53c8c..e03a680 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java @@ -13,6 +13,7 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; import java.util.Arrays; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; @@ -55,7 +56,7 @@ class UpdateProjectActionTest { } """; - final var contentAsString = this.mockMvc.perform( + final String contentAsString = this.mockMvc.perform( put("/projects/1").content(content).contentType(MediaType.APPLICATION_JSON) ) .andExpect(status().isOk()) @@ -71,9 +72,9 @@ class UpdateProjectActionTest { .getResponse() .getContentAsString(); - final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + final long id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); - final var existingProject = this.projectRepository.findById(id); + final Optional existingProject = this.projectRepository.findById(id); assertThat(existingProject.get().getName()).isEqualTo("updatedName"); assertThat(existingProject.get().getLeadingEmployee()).isEqualTo(2); assertThat(existingProject.get().getContractor()).isEqualTo(6); @@ -103,13 +104,13 @@ class UpdateProjectActionTest { {} """; - final var contentAsString = this.mockMvc.perform( + final String contentAsString = this.mockMvc.perform( put("/projects/1").content(content).contentType(MediaType.APPLICATION_JSON) ) .andExpect(status().isOk()) .andExpect(jsonPath("name", is("name"))) .andExpect(jsonPath("leading_employee", is(1))) - .andExpect(jsonPath("employees", is(List.of(1,2,3)))) + .andExpect(jsonPath("employees", is(List.of(1, 2, 3)))) .andExpect(jsonPath("contractor", is(1))) .andExpect(jsonPath("contractor_name", is("contractorName"))) .andExpect(jsonPath("comment", is("comment"))) @@ -120,9 +121,9 @@ class UpdateProjectActionTest { .getResponse() .getContentAsString(); - final var id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); + final long id = Long.parseLong(new JSONObject(contentAsString).get("id").toString()); - final var existingProject = this.projectRepository.findById(id); + final Optional existingProject = this.projectRepository.findById(id); assertThat(existingProject.get().getName()).isEqualTo("name"); assertThat(existingProject.get().getLeadingEmployee()).isEqualTo(1); assertThat(existingProject.get().getContractor()).isEqualTo(1); From 76b0201cf2fec3d2b40feefe80a1f55339278fef Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Wed, 23 Oct 2024 09:51:12 +0000 Subject: [PATCH 27/31] feat(employee): add action to remove employee from project (SCRUM-23) (!35) Reviewed-on: https://git.kjan.de/jank/LF8/pulls/35 Reviewed-by: Phan Huy Tran Co-authored-by: Jan Klattenhoff Co-committed-by: Jan Klattenhoff --- .../RemoveEmployeeFromProjectAction.java | 52 ++++++++++++ ...oveEmployeeFromProjectIntegrationTest.java | 85 +++++++++++++++++++ .../project/UpdateProjectActionTest.java | 2 +- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/szut/lf8_starter/project/action/employee/RemoveEmployeeFromProjectAction.java create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java diff --git a/src/main/java/de/szut/lf8_starter/project/action/employee/RemoveEmployeeFromProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/employee/RemoveEmployeeFromProjectAction.java new file mode 100644 index 0000000..4fec8e7 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/action/employee/RemoveEmployeeFromProjectAction.java @@ -0,0 +1,52 @@ +package de.szut.lf8_starter.project.action.employee; + +import de.szut.lf8_starter.employee.EmployeeService; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +@RestController +public class RemoveEmployeeFromProjectAction { + private final ProjectService projectService; + private final EmployeeService employeeService; + + public RemoveEmployeeFromProjectAction(ProjectService projectService, EmployeeService employeeService) { + this.projectService = projectService; + this.employeeService = employeeService; + } + + @Operation(summary = "Remove an employee from a project") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Employee removed from project"), + @ApiResponse(responseCode = "404", description = "Project or employee not found", content = @Content) + }) + @DeleteMapping("/projects/{projectId}/employees/{employeeId}") + public ResponseEntity remove( + @PathVariable Long projectId, + @PathVariable Long employeeId, + @RequestHeader("Authorization") String accessToken + ) { + Optional project = this.projectService.findById(projectId); + + if (project.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + if (!this.employeeService.employeeExists(accessToken, employeeId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + project.get().getEmployees().remove(employeeId); + this.projectService.update(project.get()); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java b/src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java new file mode 100644 index 0000000..7e55dd8 --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java @@ -0,0 +1,85 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.*; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class RemoveEmployeeFromProjectIntegrationTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ProjectRepository projectRepository; + + @Test + void removeEmployeeFromProject() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L, 312L)); + this.projectRepository.save(project); + + + mockMvc.perform(delete("/projects/{projectId}/employees/{employeeId}", 1, 312) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + ProjectEntity updatedProject = projectRepository.findById(1L).get(); + + assert !updatedProject.getEmployees().contains(312L); + } + + private String getBearerToken() { + String url = "https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", "password"); + map.add("client_id", "employee-management-service"); + map.add("username", "user"); + map.add("password", "test"); + + HttpEntity> request = new HttpEntity<>(map, headers); + + ResponseEntity response = this.restTemplate.exchange(url, HttpMethod.POST, request, Map.class); + + return Objects.requireNonNull(response.getBody()).get("access_token").toString(); + } + + @Test + void removeEmployeeShouldReturn404IfProjectNotFound() throws Exception { + mockMvc.perform(delete("/projects/{projectId}/employees/{employeeId}", 1987234987, 312) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java index e03a680..a38e233 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java @@ -136,6 +136,6 @@ class UpdateProjectActionTest { @Test void updateProjectShouldReturnNotFoundResponseWhenProjectIsNotFound() throws Exception { - this.mockMvc.perform(put("/projects/2").content("{}").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isNotFound()); + this.mockMvc.perform(put("/projects/98723498798").content("{}").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isNotFound()); } } From 202805e3dd5d6bcb5536434cbcc2a489553fbdd0 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 23 Oct 2024 10:19:33 +0000 Subject: [PATCH 28/31] feat: Implement feature to list all employees from a project (SCRUM-46) (!36) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/36 --- requests/employee/createEmployee.http | 13 +-- requests/employee/getEmployee.http | 2 +- requests/project/createProject.http | 2 +- requests/project/getEmployeesFromProject.http | 2 + .../lf8_starter/employee/EmployeeService.java | 17 ++++ .../GlobalExceptionHandler.java | 2 +- .../project/action/RemoveProjectAction.java | 7 +- .../action/crud/CreateProjectAction.java | 6 +- .../action/crud/GetAllProjectsAction.java | 4 +- .../project/action/crud/GetProjectAction.java | 4 +- .../action/crud/UpdateProjectAction.java | 6 +- .../GetEmployeeFromProjectAction.java | 51 ++++++++++++ .../dto/{ => employee}/EmployeeDto.java | 2 +- .../project/dto/employee/GetEmployeeDto.java | 11 +++ .../employee/GetEmployeesFromProjectDto.java | 17 ++++ .../project/dto/employee/GetSkillDto.java | 13 +++ .../dto/{ => project}/CreateProjectDto.java | 2 +- .../dto/{ => project}/GetProjectDto.java | 2 +- .../dto/{ => project}/UpdateProjectDto.java | 2 +- .../mapper/EmployeeFromProjectMapper.java | 35 ++++++++ .../project/{ => mapper}/ProjectMapper.java | 9 +- .../project/GetEmployeesFromProjectTest.java | 82 +++++++++++++++++++ 22 files changed, 263 insertions(+), 28 deletions(-) create mode 100644 requests/project/getEmployeesFromProject.http create mode 100644 src/main/java/de/szut/lf8_starter/project/action/employee/GetEmployeeFromProjectAction.java rename src/main/java/de/szut/lf8_starter/project/dto/{ => employee}/EmployeeDto.java (91%) create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeeDto.java create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeesFromProjectDto.java create mode 100644 src/main/java/de/szut/lf8_starter/project/dto/employee/GetSkillDto.java rename src/main/java/de/szut/lf8_starter/project/dto/{ => project}/CreateProjectDto.java (95%) rename src/main/java/de/szut/lf8_starter/project/dto/{ => project}/GetProjectDto.java (94%) rename src/main/java/de/szut/lf8_starter/project/dto/{ => project}/UpdateProjectDto.java (90%) create mode 100644 src/main/java/de/szut/lf8_starter/project/mapper/EmployeeFromProjectMapper.java rename src/main/java/de/szut/lf8_starter/project/{ => mapper}/ProjectMapper.java (90%) create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java diff --git a/requests/employee/createEmployee.http b/requests/employee/createEmployee.http index a170fe2..b3f4591 100644 --- a/requests/employee/createEmployee.http +++ b/requests/employee/createEmployee.http @@ -3,10 +3,13 @@ Authorization: Bearer {{auth_token}} Content-Type: application/json { - "firstName": "Jan", - "lastName": "Klattenhoff", - "street": "Pirolweg 17", + "firstName": "Jan1", + "lastName": "Klattenhoff1", + "street": "Pirolweg 171", "postcode": "27777", - "city": "Gandakersee", - "phone": "017684984816" + "city": "Gandakersee1", + "phone": "0176849848161", + "skillSet": [ + "1" + ] } \ No newline at end of file diff --git a/requests/employee/getEmployee.http b/requests/employee/getEmployee.http index eddf4d9..350a7fc 100644 --- a/requests/employee/getEmployee.http +++ b/requests/employee/getEmployee.http @@ -1,2 +1,2 @@ -GET https://employee.szut.dev/employees/312 +GET https://employee.szut.dev/employees/315 Authorization: Bearer {{auth_token}} \ No newline at end of file diff --git a/requests/project/createProject.http b/requests/project/createProject.http index 7b08e28..3338a2c 100644 --- a/requests/project/createProject.http +++ b/requests/project/createProject.http @@ -6,7 +6,7 @@ Content-Type: application/json { "name": "name", "leading_employee": 1, - "employees": [2, 3], + "employees": [315, 312], "contractor": 4, "contractor_name": "Peter File", "comment": "goal of project", diff --git a/requests/project/getEmployeesFromProject.http b/requests/project/getEmployeesFromProject.http new file mode 100644 index 0000000..d12dd96 --- /dev/null +++ b/requests/project/getEmployeesFromProject.http @@ -0,0 +1,2 @@ +GET http://localhost:8080/projects/2/employees +Authorization: Bearer {{auth_token}} \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java b/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java index a78f76b..907de80 100644 --- a/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java +++ b/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java @@ -1,5 +1,7 @@ package de.szut.lf8_starter.employee; +import de.szut.lf8_starter.exceptionHandling.ResourceNotFoundException; +import de.szut.lf8_starter.project.dto.employee.GetEmployeeDto; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -31,4 +33,19 @@ public class EmployeeService { return true; } + + public GetEmployeeDto getEmployee(String accessToken, Long employeeId) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken.replace("Bearer ", "")); + + HttpEntity requestEntity = new HttpEntity<>(headers); + + String url = "https://employee.szut.dev/employees/" + employeeId; + + try { + return restTemplate.exchange(url, HttpMethod.GET, requestEntity, GetEmployeeDto.class).getBody(); + } catch (HttpClientErrorException.NotFound e) { + throw new ResourceNotFoundException("Employee not found"); + } + } } diff --git a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java index f6415b5..5327d4f 100644 --- a/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java +++ b/src/main/java/de/szut/lf8_starter/exceptionHandling/GlobalExceptionHandler.java @@ -30,7 +30,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity handleAllOtherExceptions(Exception ex, WebRequest request) { ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); - + return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); } diff --git a/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java index d14dbee..efbd48d 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java @@ -2,14 +2,17 @@ package de.szut.lf8_starter.project.action; import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectService; -import de.szut.lf8_starter.project.dto.GetProjectDto; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.util.Optional; diff --git a/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java index f21646d..1725ba8 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java @@ -1,10 +1,10 @@ package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectMapper; import de.szut.lf8_starter.project.ProjectService; -import de.szut.lf8_starter.project.dto.CreateProjectDto; -import de.szut.lf8_starter.project.dto.GetProjectDto; +import de.szut.lf8_starter.project.dto.project.CreateProjectDto; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; +import de.szut.lf8_starter.project.mapper.ProjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/de/szut/lf8_starter/project/action/crud/GetAllProjectsAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/GetAllProjectsAction.java index 5fb346a..97f5b9a 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/crud/GetAllProjectsAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/GetAllProjectsAction.java @@ -1,8 +1,8 @@ package de.szut.lf8_starter.project.action.crud; -import de.szut.lf8_starter.project.ProjectMapper; import de.szut.lf8_starter.project.ProjectService; -import de.szut.lf8_starter.project.dto.GetProjectDto; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; +import de.szut.lf8_starter.project.mapper.ProjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/de/szut/lf8_starter/project/action/crud/GetProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/GetProjectAction.java index eba31c1..041a26d 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/crud/GetProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/GetProjectAction.java @@ -1,9 +1,9 @@ package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectMapper; import de.szut.lf8_starter.project.ProjectService; -import de.szut.lf8_starter.project.dto.GetProjectDto; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; +import de.szut.lf8_starter.project.mapper.ProjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/de/szut/lf8_starter/project/action/crud/UpdateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/UpdateProjectAction.java index 29a4ad5..3a169cf 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/crud/UpdateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/UpdateProjectAction.java @@ -1,10 +1,10 @@ package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectEntity; -import de.szut.lf8_starter.project.ProjectMapper; import de.szut.lf8_starter.project.ProjectService; -import de.szut.lf8_starter.project.dto.GetProjectDto; -import de.szut.lf8_starter.project.dto.UpdateProjectDto; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; +import de.szut.lf8_starter.project.dto.project.UpdateProjectDto; +import de.szut.lf8_starter.project.mapper.ProjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/de/szut/lf8_starter/project/action/employee/GetEmployeeFromProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/employee/GetEmployeeFromProjectAction.java new file mode 100644 index 0000000..f9d339c --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/action/employee/GetEmployeeFromProjectAction.java @@ -0,0 +1,51 @@ +package de.szut.lf8_starter.project.action.employee; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectService; +import de.szut.lf8_starter.project.dto.employee.GetEmployeesFromProjectDto; +import de.szut.lf8_starter.project.mapper.EmployeeFromProjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Optional; + +@RestController +public class GetEmployeeFromProjectAction { + private final ProjectService projectService; + private final EmployeeFromProjectMapper employeeFromProjectMapper; + + public GetEmployeeFromProjectAction(ProjectService projectService, EmployeeFromProjectMapper employeeFromProjectMapper) { + this.projectService = projectService; + this.employeeFromProjectMapper = employeeFromProjectMapper; + } + + @Operation(summary = "Get all employees from project") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Outputs projects with employees"), + @ApiResponse(responseCode = "404", description = "Project not found", content = @Content) + }) + @GetMapping("/projects/{projectId}/employees") + public ResponseEntity create( + @PathVariable Long projectId, + @RequestHeader("Authorization") String accessToken + ) { + Optional project = projectService.findById(projectId); + + if (project.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + GetEmployeesFromProjectDto getEmployeesFromProjectDto = this.employeeFromProjectMapper + .mapToGetEmployeesFromProjectDto(project.get(), accessToken); + + return new ResponseEntity<>(getEmployeesFromProjectDto, HttpStatus.OK); + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/EmployeeDto.java b/src/main/java/de/szut/lf8_starter/project/dto/employee/EmployeeDto.java similarity index 91% rename from src/main/java/de/szut/lf8_starter/project/dto/EmployeeDto.java rename to src/main/java/de/szut/lf8_starter/project/dto/employee/EmployeeDto.java index 05909e1..cc4e872 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/EmployeeDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/employee/EmployeeDto.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.dto; +package de.szut.lf8_starter.project.dto.employee; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; diff --git a/src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeeDto.java b/src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeeDto.java new file mode 100644 index 0000000..fdb13a1 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeeDto.java @@ -0,0 +1,11 @@ +package de.szut.lf8_starter.project.dto.employee; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GetEmployeeDto { + private long id; + private GetSkillDto[] skillSet; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeesFromProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeesFromProjectDto.java new file mode 100644 index 0000000..df24a76 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/employee/GetEmployeesFromProjectDto.java @@ -0,0 +1,17 @@ +package de.szut.lf8_starter.project.dto.employee; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class GetEmployeesFromProjectDto { + private long projectId; + private String projectDescription; + private ArrayList employees; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/employee/GetSkillDto.java b/src/main/java/de/szut/lf8_starter/project/dto/employee/GetSkillDto.java new file mode 100644 index 0000000..ba91c03 --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/dto/employee/GetSkillDto.java @@ -0,0 +1,13 @@ +package de.szut.lf8_starter.project.dto.employee; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class GetSkillDto { + private String skill; +} diff --git a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/project/CreateProjectDto.java similarity index 95% rename from src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java rename to src/main/java/de/szut/lf8_starter/project/dto/project/CreateProjectDto.java index ebf4393..8e2a2aa 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/CreateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/project/CreateProjectDto.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.dto; +package de.szut.lf8_starter.project.dto.project; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.PropertyNamingStrategies; diff --git a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/project/GetProjectDto.java similarity index 94% rename from src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java rename to src/main/java/de/szut/lf8_starter/project/dto/project/GetProjectDto.java index 90ebe5e..89f043b 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/GetProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/project/GetProjectDto.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.dto; +package de.szut.lf8_starter.project.dto.project; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.PropertyNamingStrategies; diff --git a/src/main/java/de/szut/lf8_starter/project/dto/UpdateProjectDto.java b/src/main/java/de/szut/lf8_starter/project/dto/project/UpdateProjectDto.java similarity index 90% rename from src/main/java/de/szut/lf8_starter/project/dto/UpdateProjectDto.java rename to src/main/java/de/szut/lf8_starter/project/dto/project/UpdateProjectDto.java index 6d94232..943a62b 100644 --- a/src/main/java/de/szut/lf8_starter/project/dto/UpdateProjectDto.java +++ b/src/main/java/de/szut/lf8_starter/project/dto/project/UpdateProjectDto.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.dto; +package de.szut.lf8_starter.project.dto.project; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.PropertyNamingStrategies; diff --git a/src/main/java/de/szut/lf8_starter/project/mapper/EmployeeFromProjectMapper.java b/src/main/java/de/szut/lf8_starter/project/mapper/EmployeeFromProjectMapper.java new file mode 100644 index 0000000..039f23c --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/mapper/EmployeeFromProjectMapper.java @@ -0,0 +1,35 @@ +package de.szut.lf8_starter.project.mapper; + +import de.szut.lf8_starter.employee.EmployeeService; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.dto.employee.GetEmployeeDto; +import de.szut.lf8_starter.project.dto.employee.GetEmployeesFromProjectDto; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; + +@Service +public class EmployeeFromProjectMapper { + private final EmployeeService employeeService; + + public EmployeeFromProjectMapper(EmployeeService employeeService) { + this.employeeService = employeeService; + } + + public GetEmployeesFromProjectDto mapToGetEmployeesFromProjectDto(ProjectEntity projectEntity, String accessToken) { + GetEmployeesFromProjectDto getEmployeesFromProjectDto = new GetEmployeesFromProjectDto(); + getEmployeesFromProjectDto.setProjectId(projectEntity.getId()); + getEmployeesFromProjectDto.setProjectDescription(projectEntity.getComment()); + + ArrayList employeeDtos = new ArrayList<>(); + for (long id : projectEntity.getEmployees()) { + GetEmployeeDto getEmployeeDto = this.employeeService.getEmployee(accessToken, id); + + employeeDtos.add(getEmployeeDto); + } + + getEmployeesFromProjectDto.setEmployees(employeeDtos); + + return getEmployeesFromProjectDto; + } +} diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java b/src/main/java/de/szut/lf8_starter/project/mapper/ProjectMapper.java similarity index 90% rename from src/main/java/de/szut/lf8_starter/project/ProjectMapper.java rename to src/main/java/de/szut/lf8_starter/project/mapper/ProjectMapper.java index cb43b84..0d3b0af 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectMapper.java +++ b/src/main/java/de/szut/lf8_starter/project/mapper/ProjectMapper.java @@ -1,8 +1,9 @@ -package de.szut.lf8_starter.project; +package de.szut.lf8_starter.project.mapper; -import de.szut.lf8_starter.project.dto.CreateProjectDto; -import de.szut.lf8_starter.project.dto.GetProjectDto; -import de.szut.lf8_starter.project.dto.UpdateProjectDto; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.dto.project.CreateProjectDto; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; +import de.szut.lf8_starter.project.dto.project.UpdateProjectDto; import org.springframework.stereotype.Service; @Service diff --git a/src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java b/src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java new file mode 100644 index 0000000..985fd2f --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java @@ -0,0 +1,82 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.*; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class GetEmployeesFromProjectTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + @Autowired + private RestTemplate restTemplate; + + @Test + void getEmployeesFromProjectTest() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setId(1); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(315L)); + this.projectRepository.save(project); + + this.mockMvc.perform(get("/projects/1/employees") + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ).andExpect(status().isOk()) + .andExpect(jsonPath("project_id").value(1)) + .andExpect(jsonPath("project_description").value("comment")) + .andExpect(jsonPath("employees[0].id").value(315)) + .andExpect(jsonPath("employees[0].skillSet[0].skill").value("Product Owner")); + } + + @Test + void getEmployeesFromProjectShouldReturnNotFoundResponseWhenProjectIsNotFound() throws Exception { + this.mockMvc.perform(get("/projects/1111/employees") + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ).andExpect(status().isNotFound()); + } + + private String getBearerToken() { + String url = "https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", "password"); + map.add("client_id", "employee-management-service"); + map.add("username", "user"); + map.add("password", "test"); + + HttpEntity> request = new HttpEntity<>(map, headers); + + ResponseEntity response = this.restTemplate.exchange(url, HttpMethod.POST, request, Map.class); + + return Objects.requireNonNull(response.getBody()).get("access_token").toString(); + } +} From 97e211946faf2027b1fffdf6b03bfc7fce57d413 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 23 Oct 2024 10:50:49 +0000 Subject: [PATCH 29/31] feat: Implement feature to get all projects from an employee (SCRUM-31) (!37) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/37 Reviewed-by: Jan Gleytenhoover --- requests/employee/getProjects.http | 3 + .../lf8_starter/employee/EmployeeService.java | 24 +++++- .../project/ProjectRepository.java | 6 ++ .../employee/GetProjectsFromEmployee.java | 39 +++++++++ .../project/GetProjectsFromEmployeeTest.java | 86 +++++++++++++++++++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 requests/employee/getProjects.http create mode 100644 src/main/java/de/szut/lf8_starter/project/action/employee/GetProjectsFromEmployee.java create mode 100644 src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java diff --git a/requests/employee/getProjects.http b/requests/employee/getProjects.http new file mode 100644 index 0000000..8689798 --- /dev/null +++ b/requests/employee/getProjects.http @@ -0,0 +1,3 @@ +GET http://localhost:8080/employees/315/projects +Authorization: Bearer {{auth_token}} +Content-Type: application/json \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java b/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java index 907de80..d126288 100644 --- a/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java +++ b/src/main/java/de/szut/lf8_starter/employee/EmployeeService.java @@ -1,7 +1,11 @@ package de.szut.lf8_starter.employee; import de.szut.lf8_starter.exceptionHandling.ResourceNotFoundException; +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; import de.szut.lf8_starter.project.dto.employee.GetEmployeeDto; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; +import de.szut.lf8_starter.project.mapper.ProjectMapper; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -9,12 +13,19 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import java.util.ArrayList; +import java.util.List; + @Service public class EmployeeService { private final RestTemplate restTemplate; + private final ProjectRepository projectRepository; + private final ProjectMapper projectMapper; - public EmployeeService(RestTemplate restTemplate) { + public EmployeeService(RestTemplate restTemplate, ProjectRepository projectRepository, ProjectMapper projectMapper) { this.restTemplate = restTemplate; + this.projectRepository = projectRepository; + this.projectMapper = projectMapper; } public boolean employeeExists(String accessToken, Long employeeId) { @@ -48,4 +59,15 @@ public class EmployeeService { throw new ResourceNotFoundException("Employee not found"); } } + + public List getProjects(Long employeeId) { + ArrayList projects = this.projectRepository.findAllByEmployeeId(employeeId); + ArrayList projectDtos = new ArrayList<>(); + + for (ProjectEntity project : projects) { + projectDtos.add(this.projectMapper.mapToGetDto(project)); + } + + return projectDtos; + } } diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectRepository.java b/src/main/java/de/szut/lf8_starter/project/ProjectRepository.java index 6f7e33f..3b9dfb0 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectRepository.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectRepository.java @@ -1,8 +1,14 @@ package de.szut.lf8_starter.project; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.ArrayList; + @Repository public interface ProjectRepository extends JpaRepository { + @Query("SELECT p FROM ProjectEntity p WHERE :employeeId MEMBER OF p.employees") + ArrayList findAllByEmployeeId(@Param("employeeId") Long employeeId); } diff --git a/src/main/java/de/szut/lf8_starter/project/action/employee/GetProjectsFromEmployee.java b/src/main/java/de/szut/lf8_starter/project/action/employee/GetProjectsFromEmployee.java new file mode 100644 index 0000000..baa4dea --- /dev/null +++ b/src/main/java/de/szut/lf8_starter/project/action/employee/GetProjectsFromEmployee.java @@ -0,0 +1,39 @@ +package de.szut.lf8_starter.project.action.employee; + +import de.szut.lf8_starter.employee.EmployeeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class GetProjectsFromEmployee { + private final EmployeeService employeeService; + + public GetProjectsFromEmployee(EmployeeService employeeService) { + this.employeeService = employeeService; + } + + @Operation(summary = "Get all projects from employee") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Get all projects from employee", content = @Content), + @ApiResponse(responseCode = "404", description = "Employee not found", content = @Content) + }) + @GetMapping("/employees/{employeeId}/projects") + public ResponseEntity create( + @PathVariable Long employeeId, + @RequestHeader("Authorization") String accessToken + ) { + if (!this.employeeService.employeeExists(accessToken, employeeId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(this.employeeService.getProjects(employeeId), HttpStatus.OK); + } +} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java new file mode 100644 index 0000000..769bd69 --- /dev/null +++ b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java @@ -0,0 +1,86 @@ +package de.szut.lf8_starter.integration.project; + +import de.szut.lf8_starter.project.ProjectEntity; +import de.szut.lf8_starter.project.ProjectRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.*; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +class GetProjectsFromEmployeeTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ProjectRepository projectRepository; + @Autowired + private RestTemplate restTemplate; + + @Test + void getProjectsFromEmployeeTest() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setId(1); + project.setComment("comment"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setEndDate(LocalDate.of(2024, 1, 1)); + project.setLeadingEmployee(1); + project.setName("name"); + project.setStartDate(LocalDate.of(2021, 1, 1)); + project.setEmployees(List.of(315L)); + this.projectRepository.save(project); + + this.mockMvc.perform(get("/employees/315/projects") + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ).andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].name").value("name")) + .andExpect(jsonPath("$[0].leading_employee").value(1)) + .andExpect(jsonPath("$[0].employees[0]").value(315)) + .andExpect(jsonPath("$[0].contractor").value(1)) + .andExpect(jsonPath("$[0].contractor_name").value("contractorName")) + .andExpect(jsonPath("$[0].comment").value("comment")) + .andExpect(jsonPath("$[0].start_date").value("01.01.2021")); + } + + @Test + void GetProjectsFromEmployeeShouldReturnNotFoundResponseWhenEmployeeIsNotFound() throws Exception { + this.mockMvc.perform(get("/employees/1111/projects") + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ).andExpect(status().isNotFound()); + } + + private String getBearerToken() { + String url = "https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", "password"); + map.add("client_id", "employee-management-service"); + map.add("username", "user"); + map.add("password", "test"); + + HttpEntity> request = new HttpEntity<>(map, headers); + + ResponseEntity response = this.restTemplate.exchange(url, HttpMethod.POST, request, Map.class); + + return Objects.requireNonNull(response.getBody()).get("access_token").toString(); + } +} From 5f6caa7fc23819451ca670ca938d2c30e7bb6c19 Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Fri, 25 Oct 2024 05:01:10 +0000 Subject: [PATCH 30/31] feat: Implement feature to acknowledge project time frames when assigning employees to projects (SCRUM-7 SCRUM-2) (!38) Co-authored-by: Phan Huy Tran Reviewed-on: https://git.kjan.de/jank/LF8/pulls/38 Co-authored-by: Phan Huy Tran Co-committed-by: Phan Huy Tran --- requests/project/createProject.http | 6 +- .../lf8_starter/project/ProjectService.java | 15 ++ .../action/crud/CreateProjectAction.java | 33 +++- .../{ => crud}/RemoveProjectAction.java | 2 +- .../employee/AddEmployeeToProjectAction.java | 20 +- .../project/AddEmployeeToProjectTest.java | 85 ++++++++- .../project/CreateProjectActionTest.java | 173 +++++++++++++++++- .../project/GetEmployeesFromProjectTest.java | 2 + .../project/GetProjectActionTest.java | 2 + .../project/GetProjectsFromEmployeeTest.java | 2 + .../ProjectFindAllNotAuthenticated.java | 32 ---- .../project/ProjectFindAllSuccessTest.java | 2 + ...oveEmployeeFromProjectIntegrationTest.java | 4 +- .../project/RemoveProjectActionTest.java | 2 + .../project/UpdateProjectActionTest.java | 2 + .../resources/application-test.properties | 6 + 16 files changed, 334 insertions(+), 54 deletions(-) rename src/main/java/de/szut/lf8_starter/project/action/{ => crud}/RemoveProjectAction.java (97%) delete mode 100644 src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java create mode 100644 src/test/resources/application-test.properties diff --git a/requests/project/createProject.http b/requests/project/createProject.http index 3338a2c..1e044b5 100644 --- a/requests/project/createProject.http +++ b/requests/project/createProject.http @@ -6,10 +6,10 @@ Content-Type: application/json { "name": "name", "leading_employee": 1, - "employees": [315, 312], + "employees": [312], "contractor": 4, "contractor_name": "Peter File", "comment": "goal of project", - "start_date": "01.01.2000", - "planned_end_date": "01.01.2001" + "start_date": "02.01.2020", + "planned_end_date": "01.01.2023" } \ No newline at end of file diff --git a/src/main/java/de/szut/lf8_starter/project/ProjectService.java b/src/main/java/de/szut/lf8_starter/project/ProjectService.java index 17afcea..5a50454 100644 --- a/src/main/java/de/szut/lf8_starter/project/ProjectService.java +++ b/src/main/java/de/szut/lf8_starter/project/ProjectService.java @@ -1,5 +1,6 @@ package de.szut.lf8_starter.project; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; import org.springframework.stereotype.Service; import java.util.List; @@ -32,4 +33,18 @@ public class ProjectService { public void delete(Long id) { this.projectRepository.deleteById(id); } + + public boolean isOverlapping(GetProjectDto getProjectDto, ProjectEntity existingProjectEntity) { + return isDateRangeOverlapping(getProjectDto, existingProjectEntity) || isDateEqual(getProjectDto, existingProjectEntity); + } + + private boolean isDateRangeOverlapping(GetProjectDto getProjectDto, ProjectEntity existingProjectEntity) { + return getProjectDto.getStartDate().isBefore(existingProjectEntity.getPlannedEndDate()) + && getProjectDto.getPlannedEndDate().isAfter(existingProjectEntity.getStartDate()); + } + + private boolean isDateEqual(GetProjectDto getProjectDto, ProjectEntity existingProjectEntity) { + return getProjectDto.getStartDate().isEqual(existingProjectEntity.getStartDate()) + || getProjectDto.getPlannedEndDate().isEqual(existingProjectEntity.getPlannedEndDate()); + } } diff --git a/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java index 1725ba8..8319011 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/CreateProjectAction.java @@ -1,5 +1,6 @@ package de.szut.lf8_starter.project.action.crud; +import de.szut.lf8_starter.employee.EmployeeService; import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectService; import de.szut.lf8_starter.project.dto.project.CreateProjectDto; @@ -12,15 +13,18 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/projects") public class CreateProjectAction { + private final EmployeeService employeeService; private final ProjectService projectService; private final ProjectMapper projectMapper; - public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { + public CreateProjectAction(EmployeeService employeeService, ProjectService projectService, ProjectMapper mappingService) { + this.employeeService = employeeService; this.projectService = projectService; this.projectMapper = mappingService; } @@ -29,14 +33,31 @@ public class CreateProjectAction { @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), @ApiResponse(responseCode = "400", description = "invalid JSON posted", content = @Content), - @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) + @ApiResponse(responseCode = "401", description = "not authorized", content = @Content), + @ApiResponse(responseCode = "409", description = "Project dates conflict", content = @Content) + }) @PostMapping @ResponseStatus(code = HttpStatus.CREATED) - public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { - ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); + public ResponseEntity create( + @RequestBody @Valid CreateProjectDto createProjectDto, + @RequestHeader("Authorization") String accessToken + ) { + ProjectEntity project = this.projectMapper.mapCreateDtoToEntity(createProjectDto); - projectEntity = this.projectService.create(projectEntity); + for (Long employeeId : createProjectDto.getEmployees()) { + if (!this.employeeService.employeeExists(accessToken, employeeId)) { + return new ResponseEntity<>("Employee with ID: " + employeeId + " not found", HttpStatus.NOT_FOUND); + } - return this.projectMapper.mapToGetDto(projectEntity); + for (GetProjectDto getProjectDto : this.employeeService.getProjects(employeeId)) { + if (projectService.isOverlapping(getProjectDto, project)) { + return new ResponseEntity<>("Project dates conflict with an existing project for Employee with ID: " + employeeId, HttpStatus.CONFLICT); + } + } + } + + this.projectService.create(project); + + return new ResponseEntity<>(this.projectMapper.mapToGetDto(project), HttpStatus.CREATED); } } diff --git a/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/crud/RemoveProjectAction.java similarity index 97% rename from src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java rename to src/main/java/de/szut/lf8_starter/project/action/crud/RemoveProjectAction.java index efbd48d..49aebab 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/RemoveProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/crud/RemoveProjectAction.java @@ -1,4 +1,4 @@ -package de.szut.lf8_starter.project.action; +package de.szut.lf8_starter.project.action.crud; import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectService; diff --git a/src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java b/src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java index 1e14ae0..d9862e8 100644 --- a/src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java +++ b/src/main/java/de/szut/lf8_starter/project/action/employee/AddEmployeeToProjectAction.java @@ -3,6 +3,7 @@ package de.szut.lf8_starter.project.action.employee; import de.szut.lf8_starter.employee.EmployeeService; import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectService; +import de.szut.lf8_starter.project.dto.project.GetProjectDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -29,7 +30,8 @@ public class AddEmployeeToProjectAction { @Operation(summary = "Add an employee to a project") @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "Employee added to project"), - @ApiResponse(responseCode = "404", description = "Project or employee not found", content = @Content) + @ApiResponse(responseCode = "404", description = "Project or employee not found", content = @Content), + @ApiResponse(responseCode = "409", description = "Project dates conflict", content = @Content) }) @PostMapping("/projects/{projectId}/employees/{employeeId}") public ResponseEntity create( @@ -37,18 +39,26 @@ public class AddEmployeeToProjectAction { @PathVariable Long employeeId, @RequestHeader("Authorization") String accessToken ) { - Optional project = this.projectService.findById(projectId); + Optional optionalProject = this.projectService.findById(projectId); - if (project.isEmpty()) { + if (optionalProject.isEmpty()) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } + ProjectEntity projectEntity = optionalProject.get(); + if (!this.employeeService.employeeExists(accessToken, employeeId)) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - project.get().getEmployees().add(employeeId); - this.projectService.update(project.get()); + for (GetProjectDto getProjectDto : this.employeeService.getProjects(employeeId)) { + if (this.projectService.isOverlapping(getProjectDto, projectEntity)) { + return new ResponseEntity<>("Project dates conflict with an existing project for Employee with ID: " + employeeId, HttpStatus.CONFLICT); + } + } + + projectEntity.getEmployees().add(employeeId); + this.projectService.update(projectEntity); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } diff --git a/src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java b/src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java index 4193e77..3efc563 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/AddEmployeeToProjectTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.*; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -22,7 +23,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) -class AddEmployeeToProjectActionIntegrationTest { +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class AddEmployeeToProjectTest { @Autowired private MockMvc mockMvc; @@ -56,6 +58,87 @@ class AddEmployeeToProjectActionIntegrationTest { assert updatedProject.getEmployees().contains(312L); } + @Test + void addEmployeeToPastProjectTest() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setComment("past project"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setPlannedEndDate(LocalDate.of(1991, 1, 1)); + project.setLeadingEmployee(1); + project.setName("past project"); + project.setStartDate(LocalDate.of(1990, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project); + + mockMvc.perform(post("/projects/{projectId}/employees/{employeeId}", project.getId(), 312) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + ProjectEntity updatedProject = projectRepository.findById(project.getId()).get(); + + assert updatedProject.getEmployees().contains(312L); + } + + @Test + void addEmployeeToFutureProjectTest() throws Exception { + ProjectEntity project = new ProjectEntity(); + project.setComment("future project"); + project.setContractor(1); + project.setContractorName("contractorName"); + project.setPlannedEndDate(LocalDate.of(2101, 1, 1)); + project.setLeadingEmployee(1); + project.setName("future project"); + project.setStartDate(LocalDate.of(2100, 1, 1)); + project.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project); + + mockMvc.perform(post("/projects/{projectId}/employees/{employeeId}", project.getId(), 312) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + ProjectEntity updatedProject = projectRepository.findById(project.getId()).get(); + + assert updatedProject.getEmployees().contains(312L); + } + + @Test + void addEmployeeToProjectWithOverlappingDatesTest() throws Exception { + ProjectEntity project1 = new ProjectEntity(); + project1.setComment("project 1"); + project1.setContractor(1); + project1.setContractorName("contractorName"); + project1.setPlannedEndDate(LocalDate.of(2023, 1, 1)); + project1.setLeadingEmployee(1); + project1.setName("project 1"); + project1.setStartDate(LocalDate.of(2022, 1, 1)); + project1.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project1); + + ProjectEntity project2 = new ProjectEntity(); + project2.setComment("project 2"); + project2.setContractor(1); + project2.setContractorName("contractorName"); + project2.setPlannedEndDate(LocalDate.of(2023, 6, 1)); + project2.setLeadingEmployee(1); + project2.setName("project 2"); + project2.setStartDate(LocalDate.of(2022, 6, 1)); + project2.setEmployees(List.of(1L, 2L, 3L)); + this.projectRepository.save(project2); + + mockMvc.perform(post("/projects/{projectId}/employees/{employeeId}", project1.getId(), 312) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + mockMvc.perform(post("/projects/{projectId}/employees/{employeeId}", project2.getId(), 312) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()); + } + private String getBearerToken() { String url = "https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token"; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java index 27fe186..881b71a 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/CreateProjectActionTest.java @@ -7,11 +7,17 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; +import org.springframework.http.*; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; import java.time.LocalDate; -import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -22,11 +28,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class CreateProjectActionTest { @Autowired private MockMvc mockMvc; @Autowired private ProjectRepository projectRepository; + @Autowired + private RestTemplate restTemplate; @Test void createProjectTest() throws Exception { @@ -34,7 +43,7 @@ class CreateProjectActionTest { { "name": "name", "leading_employee": 1, - "employees": [2, 3], + "employees": [312], "contractor": 4, "contractor_name": "Peter File", "comment": "goal of project", @@ -44,12 +53,15 @@ class CreateProjectActionTest { """; final String contentAsString = this.mockMvc.perform( - post("/projects").content(content).contentType(MediaType.APPLICATION_JSON) + post("/projects") + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) ) .andExpect(status().isCreated()) .andExpect(jsonPath("name", is("name"))) .andExpect(jsonPath("leading_employee", is(1))) - .andExpect(jsonPath("employees", is(Arrays.asList(2, 3)))) + .andExpect(jsonPath("employees", is(List.of(312)))) .andExpect(jsonPath("contractor", is(4))) .andExpect(jsonPath("contractor_name", is("Peter File"))) .andExpect(jsonPath("comment", is("goal of project"))) @@ -70,4 +82,155 @@ class CreateProjectActionTest { assertThat(project.get().getStartDate()).isEqualTo(LocalDate.of(2000, 1, 1)); assertThat(project.get().getPlannedEndDate()).isEqualTo(LocalDate.of(2001, 1, 1)); } + + @Test + void createProjectShouldReturnConflictResponseOnConflictingProjects() throws Exception { + String content = """ + { + "name": "name", + "leading_employee": 1, + "employees": [312], + "contractor": 4, + "contractor_name": "Peter File", + "comment": "goal of project", + "start_date": "01.01.2000", + "planned_end_date": "01.01.2001" + } + """; + + this.mockMvc.perform( + post("/projects") + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ) + .andExpect(status().isCreated()); + + this.mockMvc.perform( + post("/projects") + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ) + .andExpect(status().isConflict()); + } + + @Test + void createProjectWithPastStartDate() throws Exception { + String content = """ + { + "name": "past project", + "leading_employee": 1, + "employees": [312], + "contractor": 4, + "contractor_name": "Past Contractor", + "comment": "past project", + "start_date": "01.01.1990", + "planned_end_date": "01.01.1991" + } + """; + + this.mockMvc.perform( + post("/projects") + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("name", is("past project"))) + .andExpect(jsonPath("start_date", is("01.01.1990"))) + .andExpect(jsonPath("planned_end_date", is("01.01.1991"))); + } + + @Test + void createProjectWithFutureStartDate() throws Exception { + String content = """ + { + "name": "future project", + "leading_employee": 1, + "employees": [312], + "contractor": 4, + "contractor_name": "Future Contractor", + "comment": "future project", + "start_date": "01.01.2100", + "planned_end_date": "01.01.2101" + } + """; + + this.mockMvc.perform( + post("/projects") + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("name", is("future project"))) + .andExpect(jsonPath("start_date", is("01.01.2100"))) + .andExpect(jsonPath("planned_end_date", is("01.01.2101"))); + } + + @Test + void createProjectWithOverlappingDates() throws Exception { + String content1 = """ + { + "name": "project 1", + "leading_employee": 1, + "employees": [312], + "contractor": 4, + "contractor_name": "Contractor 1", + "comment": "project 1", + "start_date": "01.01.2022", + "planned_end_date": "01.01.2023" + } + """; + + String content2 = """ + { + "name": "project 2", + "leading_employee": 1, + "employees": [312], + "contractor": 4, + "contractor_name": "Contractor 2", + "comment": "project 2", + "start_date": "01.06.2022", + "planned_end_date": "01.06.2023" + } + """; + + this.mockMvc.perform( + post("/projects") + .content(content1) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ) + .andExpect(status().isCreated()); + + this.mockMvc.perform( + post("/projects") + .content(content2) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, getBearerToken()) + ) + .andExpect(status().isConflict()); + } + + private String getBearerToken() { + String url = "https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", "password"); + map.add("client_id", "employee-management-service"); + map.add("username", "user"); + map.add("password", "test"); + + HttpEntity> request = new HttpEntity<>(map, headers); + + ResponseEntity response = this.restTemplate.exchange(url, HttpMethod.POST, request, Map.class); + + return Objects.requireNonNull(response.getBody()).get("access_token").toString(); + } } + diff --git a/src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java b/src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java index 985fd2f..8b73e06 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/GetEmployeesFromProjectTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.*; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -23,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class GetEmployeesFromProjectTest { @Autowired private MockMvc mockMvc; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java index 1b6ed3f..c3d2235 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectActionTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; @@ -18,6 +19,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class GetProjectActionTest { @Autowired private MockMvc mockMvc; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java index 769bd69..f7282ee 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/GetProjectsFromEmployeeTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.*; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -23,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class GetProjectsFromEmployeeTest { @Autowired private MockMvc mockMvc; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java deleted file mode 100644 index bda3c51..0000000 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllNotAuthenticated.java +++ /dev/null @@ -1,32 +0,0 @@ -package de.szut.lf8_starter.integration.project; - -import de.szut.lf8_starter.project.ProjectRepository; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.test.web.servlet.MockMvc; - - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc(addFilters = true) -class ProjectFindAllNotAuthenticated { - @Autowired - private ProjectRepository projectRepository; - - @Autowired - private MockMvc mockMvc; - - @Autowired - private TestRestTemplate restTemplate; - - @Test - void findAllProjects() throws Exception { - this.mockMvc.perform(get("/projects")) - .andExpect(status().isUnauthorized()); - } -} diff --git a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java index ff3a642..e424a92 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/ProjectFindAllSuccessTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; @@ -18,6 +19,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class ProjectFindAllSuccessTest { @Autowired private ProjectRepository projectRepository; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java b/src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java index 7e55dd8..4008ddb 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/RemoveEmployeeFromProjectIntegrationTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.*; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -17,11 +18,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class RemoveEmployeeFromProjectIntegrationTest { @Autowired private MockMvc mockMvc; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java index c2c47dd..0dd94fe 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/RemoveProjectActionTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; @@ -17,6 +18,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class RemoveProjectActionTest { @Autowired private MockMvc mockMvc; diff --git a/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java index a38e233..74d889b 100644 --- a/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java +++ b/src/test/java/de/szut/lf8_starter/integration/project/UpdateProjectActionTest.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; @@ -23,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc(addFilters = false) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class UpdateProjectActionTest { @Autowired private MockMvc mockMvc; diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..f45162f --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,6 @@ +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop \ No newline at end of file From 8c6f6301d374f5751b8c292470a707872dbbf860 Mon Sep 17 00:00:00 2001 From: Renovate Date: Fri, 20 Dec 2024 11:50:43 +0000 Subject: [PATCH 31/31] chore(deps): update plugin org.sonarqube to v6 (!40) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | org.sonarqube | `5.1.0.4882` -> `6.0.1.5171` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.sonarqube:org.sonarqube.gradle.plugin/6.0.1.5171?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.sonarqube:org.sonarqube.gradle.plugin/6.0.1.5171?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.sonarqube:org.sonarqube.gradle.plugin/5.1.0.4882/6.0.1.5171?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.sonarqube:org.sonarqube.gradle.plugin/5.1.0.4882/6.0.1.5171?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://git.kjan.de/jank/LF8/pulls/40 Co-authored-by: Renovate Co-committed-by: Renovate --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0488a79..80769b3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("org.springframework.boot") version "3.3.4" id("io.spring.dependency-management") version "1.1.6" id("checkstyle") - id("org.sonarqube") version "5.1.0.4882" + id("org.sonarqube") version "6.0.1.5171" id("jacoco") }