feat: Implement create project route (SCRUM-7) #11

Merged
ptran merged 5 commits from feature/create-project into main 2024-09-25 11:03:47 +00:00
22 changed files with 1079 additions and 1079 deletions
Showing only changes of commit ca337f8bd8 - Show all commits

@ -1,50 +1,50 @@
name: "Quality Check" name: "Quality Check"
on: on:
- pull_request - pull_request
jobs: jobs:
test: test:
name: "Tests" name: "Tests"
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
container: container:
image: "cimg/openjdk:21.0.2-node" image: "cimg/openjdk:21.0.2-node"
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v3
- uses: actions/cache@v4 - uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
~/.gradle/wrapper ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: "Prepare Gradle" - name: "Prepare Gradle"
run: gradle clean run: gradle clean
- name: "Check" - name: "Check"
run: gradle testClasses run: gradle testClasses
- name: "Stop Gradle" - name: "Stop Gradle"
run: gradle --stop run: gradle --stop
checkstyle: checkstyle:
name: "Checkstyle Main" name: "Checkstyle Main"
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
container: container:
image: "cimg/openjdk:21.0.2-node" image: "cimg/openjdk:21.0.2-node"
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: actions/checkout@v3 uses: actions/checkout@v3
- uses: actions/cache@v4 - uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
~/.gradle/wrapper ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: "Prepare Gradle" - name: "Prepare Gradle"
run: gradle clean run: gradle clean
- name: "Check" - name: "Check"
run: gradle check run: gradle check
- name: "Stop Gradle" - name: "Stop Gradle"
run: gradle --stop run: gradle --stop

@ -1,6 +1,6 @@
POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=employee-management-service&username=user&password=test grant_type=password&client_id=employee-management-service&username=user&password=test
> {% client.global.set("auth_token", response.body.access_token); %} > {% client.global.set("auth_token", response.body.access_token); %}

128
Readme.md

@ -1,65 +1,65 @@
# Starter für das LF08 Projekt # Starter für das LF08 Projekt
## Requirements ## Requirements
* Docker https://docs.docker.com/get-docker/ * Docker https://docs.docker.com/get-docker/
* Docker compose (bei Windows und Mac schon in Docker enthalten) https://docs.docker.com/compose/install/ * Docker compose (bei Windows und Mac schon in Docker enthalten) https://docs.docker.com/compose/install/
## Endpunkt ## Endpunkt
``` ```
http://localhost:8080 http://localhost:8080
``` ```
## Swagger ## Swagger
``` ```
http://localhost:8080/swagger http://localhost:8080/swagger
``` ```
# Postgres # Postgres
### Terminal öffnen ### Terminal öffnen
für alles gilt, im Terminal im Ordner docker/local sein für alles gilt, im Terminal im Ordner docker/local sein
```bash ```bash
cd docker/local cd docker/local
``` ```
### Postgres starten ### Postgres starten
```bash ```bash
docker compose up docker compose up
``` ```
Achtung: Der Docker-Container läuft dauerhaft! Wenn er nicht mehr benötigt wird, sollten Sie ihn stoppen. Achtung: Der Docker-Container läuft dauerhaft! Wenn er nicht mehr benötigt wird, sollten Sie ihn stoppen.
### Postgres stoppen ### Postgres stoppen
```bash ```bash
docker compose down docker compose down
``` ```
### Postgres Datenbank wipen, z.B. bei Problemen ### Postgres Datenbank wipen, z.B. bei Problemen
```bash ```bash
docker compose down docker compose down
docker volume rm local_lf8_starter_postgres_data docker volume rm local_lf8_starter_postgres_data
docker compose up docker compose up
``` ```
### Intellij-Ansicht für Postgres Datenbank einrichten ### Intellij-Ansicht für Postgres Datenbank einrichten
```bash ```bash
1. Lasse den Docker-Container mit der PostgreSQL-Datenbank laufen 1. Lasse den Docker-Container mit der PostgreSQL-Datenbank laufen
2. im Ordner resources die Datei application.properties öffnen und die URL der Datenbank kopieren 2. im Ordner resources die Datei application.properties öffnen und die URL der Datenbank kopieren
3. rechts im Fenster den Reiter Database öffnen 3. rechts im Fenster den Reiter Database öffnen
4. In der Database-Symbolleiste auf das Datenbanksymbol mit dem Schlüssel klicken 4. In der Database-Symbolleiste auf das Datenbanksymbol mit dem Schlüssel klicken
5. auf das Pluszeichen klicken 5. auf das Pluszeichen klicken
6. Datasource from URL auswählen 6. Datasource from URL auswählen
7. URL der DB einfügen und PostgreSQL-Treiber auswählen, mit OK bestätigen 7. URL der DB einfügen und PostgreSQL-Treiber auswählen, mit OK bestätigen
8. Username lf8_starter und Passwort secret eintragen (siehe application.properties), mit Apply bestätigen 8. Username lf8_starter und Passwort secret eintragen (siehe application.properties), mit Apply bestätigen
9. im Reiter Schemas alle Häkchen entfernen und lediglich vor lf8_starter_db und public Häkchen setzen 9. im Reiter Schemas alle Häkchen entfernen und lediglich vor lf8_starter_db und public Häkchen setzen
10. mit Apply und ok bestätigen 10. mit Apply und ok bestätigen
``` ```
# Keycloak # Keycloak
### Keycloak Token ### Keycloak Token
1. Auf der Projektebene [GetBearerToken.http](GetBearerToken.http) öffnen. 1. Auf der Projektebene [GetBearerToken.http](GetBearerToken.http) öffnen.
2. Neben der Request auf den grünen Pfeil drücken 2. Neben der Request auf den grünen Pfeil drücken
3. Aus dem Reponse das access_token kopieren 3. Aus dem Reponse das access_token kopieren
# Conventions # Conventions
### Commits ### Commits
Commits and merge request names MUST be done as documented here: https://www.conventionalcommits.org/en/v1.0.0/ Commits and merge request names MUST be done as documented here: https://www.conventionalcommits.org/en/v1.0.0/
Merge request titles MAY also include the ticket id from our jira tickets if the Merge request is part of a ticket. The jira board can be opened through the issues tab. Merge request titles MAY also include the ticket id from our jira tickets if the Merge request is part of a ticket. The jira board can be opened through the issues tab.

@ -1,62 +1,62 @@
plugins { plugins {
java java
id("org.springframework.boot") version "3.3.4" id("org.springframework.boot") version "3.3.4"
id("io.spring.dependency-management") version "1.1.6" id("io.spring.dependency-management") version "1.1.6"
id("checkstyle") id("checkstyle")
} }
tasks.withType<Checkstyle> { tasks.withType<Checkstyle> {
reports { reports {
// Disable HTML report // Disable HTML report
html.required.set(false) html.required.set(false)
// Disable XML report // Disable XML report
xml.required.set(false) xml.required.set(false)
} }
} }
group = "de.szut" group = "de.szut"
version = "0.0.1-SNAPSHOT" version = "0.0.1-SNAPSHOT"
tasks.test { tasks.test {
useJUnitPlatform() useJUnitPlatform()
// Activate the 'test' profile for Spring during tests // Activate the 'test' profile for Spring during tests
systemProperty("spring.profiles.active", "test") systemProperty("spring.profiles.active", "test")
} }
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(21) languageVersion = JavaLanguageVersion.of(21)
} }
} }
configurations { configurations {
compileOnly { compileOnly {
extendsFrom(configurations.annotationProcessor.get()) extendsFrom(configurations.annotationProcessor.get())
} }
} }
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-validation")
compileOnly("org.projectlombok:lombok") compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.springframework.boot:spring-boot-starter-security") 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-resource-server:3.3.4")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.3.4") implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.3.4")
runtimeOnly("org.postgresql:postgresql") runtimeOnly("org.postgresql:postgresql")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
testImplementation("com.h2database:h2") testImplementation("com.h2database:h2")
} }
tasks.withType<Test> { tasks.withType<Test> {
useJUnitPlatform() useJUnitPlatform()
} }

@ -1,9 +1,9 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC <!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.2//EN" "-//Puppy Crawl//DTD Suppressions 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd" "https://checkstyle.org/dtds/suppressions_1_2.dtd"
> >
<suppressions> <suppressions>
<suppress files="[\\/]de[\\/]hmmh[\\/]pmt[\\/]oas" checks="."/> <suppress files="[\\/]de[\\/]hmmh[\\/]pmt[\\/]oas" checks="."/>
</suppressions> </suppressions>

@ -1,23 +1,23 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!DOCTYPE module PUBLIC <!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN" "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd" "https://checkstyle.org/dtds/configuration_1_3.dtd"
> >
<module name="Checker"> <module name="Checker">
<module name="SuppressionFilter"> <module name="SuppressionFilter">
<property name="file" value="${config_loc}/checkstyle-ignore.xml"/> <property name="file" value="${config_loc}/checkstyle-ignore.xml"/>
</module> </module>
<property name="severity" value="error"/> <property name="severity" value="error"/>
<property name="tabWidth" value="4"/> <property name="tabWidth" value="4"/>
<module name="LineLength"> <module name="LineLength">
<property name="max" value="500"/> <property name="max" value="500"/>
</module> </module>
<module name="FileTabCharacter"> <module name="FileTabCharacter">
<property name="eachLine" value="true"/> <property name="eachLine" value="true"/>
</module> </module>
<module name="NewlineAtEndOfFile"/> <module name="NewlineAtEndOfFile"/>
</module> </module>

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

504
gradlew vendored

@ -1,252 +1,252 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# https://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
############################################################################## ##############################################################################
# #
# Gradle start up script for POSIX generated by Gradle. # Gradle start up script for POSIX generated by Gradle.
# #
# Important for running: # Important for running:
# #
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # (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 # 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 # bash, then to run this script, type that shell name before the whole
# command line, like: # command line, like:
# #
# ksh Gradle # ksh Gradle
# #
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
# * functions; # * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»; # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»; # * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit». # * various built-in commands including «command», «set», and «ulimit».
# #
# Important for patching: # Important for patching:
# #
# (2) This script targets any POSIX shell, so it avoids extensions provided # (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided. # by Bash, Ksh, etc; in particular arrays are avoided.
# #
# The "traditional" practice of packing multiple parameters into a # The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security # space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating # problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java. # options in "$@", and eventually passing that to Java.
# #
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details. # see the in-line comments for details.
# #
# There are tweaks for specific operating systems such as AIX, CygWin, # There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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 # 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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
# #
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
app_path=$0 app_path=$0
# Need this for daisy-chained symlinks. # Need this for daisy-chained symlinks.
while while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ] [ -h "$app_path" ]
do do
ls=$( ls -ld "$app_path" ) ls=$( ls -ld "$app_path" )
link=${ls#*' -> '} link=${ls#*' -> '}
case $link in #( case $link in #(
/*) app_path=$link ;; #( /*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;; *) app_path=$APP_HOME$link ;;
esac esac
done done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # 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 APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit ' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} >&2 } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} >&2 } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "$( uname )" in #( case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #( CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #( Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #( MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD=$JAVA_HOME/bin/java JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
if ! command -v java >/dev/null 2>&1 if ! command -v java >/dev/null 2>&1
then then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045 # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045 # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
fi fi
# Collect all arguments for the java command, stacking in reverse order: # Collect all arguments for the java command, stacking in reverse order:
# * args from the command line # * args from the command line
# * the main class name # * the main class name
# * -classpath # * -classpath
# * -D...appname settings # * -D...appname settings
# * --module-path (only if needed) # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh # Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do for arg do
if if
case $arg in #( case $arg in #(
-*) false ;; # don't mess with options #( -*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #( [ -e "$t" ] ;; #(
*) false ;; *) false ;;
esac esac
then then
arg=$( cygpath --path --ignore --mixed "$arg" ) arg=$( cygpath --path --ignore --mixed "$arg" )
fi fi
# Roll the args list around exactly as many times as the number of # 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 # args, so each arg winds up back in the position where it started, but
# possibly modified. # possibly modified.
# #
# NB: a `for` loop captures its iteration list before it begins, so # NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of # changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`. # iterations, nor the values presented in `arg`.
shift # remove old arg shift # remove old arg
set -- "$@" "$arg" # push replacement arg set -- "$@" "$arg" # push replacement arg
done done
fi fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # 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"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # 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 # * 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. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1 if ! command -v xargs >/dev/null 2>&1
then then
die "xargs is not available" die "xargs is not available"
fi fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
# #
# In Bash we could simply go: # In Bash we could simply go:
# #
# readarray ARGS < <( xargs -n1 <<<"$var" ) && # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@" # set -- "${ARGS[@]}" "$@"
# #
# but POSIX shell has neither arrays nor command substitution, so instead we # 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 # 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 # character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap # that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement. # the whole thing up as a single "set" statement.
# #
# This will of course break if any of these variables contains a newline or # This will of course break if any of these variables contains a newline or
# an unmatched quote. # an unmatched quote.
# #
eval "set -- $( eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 | xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' ' tr '\n' ' '
)" '"$@"' )" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

188
gradlew.bat vendored

@ -1,94 +1,94 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0 @rem SPDX-License-Identifier: Apache-2.0
@rem @rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused @rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter. @rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2 echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2 echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE% exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega

@ -1,15 +1,15 @@
### GET request to example server ### GET request to example server
POST http://localhost:8080/projects POST http://localhost:8080/projects
Authorization: Bearer {{auth_token}} Authorization: Bearer {{auth_token}}
Content-Type: application/json Content-Type: application/json
{ {
"name": "name", "name": "name",
"leading_employee": 1, "leading_employee": 1,
"employees": [2, 3], "employees": [2, 3],
"contractor": 4, "contractor": 4,
"contractorName": "Peter File", "contractorName": "Peter File",
"comment": "goal of project", "comment": "goal of project",
"startDate": "01.01.2000", "startDate": "01.01.2000",
"plannedEndDate": "01.01.2001" "plannedEndDate": "01.01.2001"
} }

@ -1,60 +1,60 @@
package de.szut.lf8_starter.config; package de.szut.lf8_starter.config;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.servers.Server;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class OpenAPIConfiguration { public class OpenAPIConfiguration {
private ServletContext context; private ServletContext context;
public OpenAPIConfiguration(ServletContext context) { public OpenAPIConfiguration(ServletContext context) {
this.context = context; this.context = context;
} }
@Bean @Bean
public OpenAPI springShopOpenAPI( public OpenAPI springShopOpenAPI(
// @Value("${info.app.version}") String appVersion, // @Value("${info.app.version}") String appVersion,
) { ) {
final String securitySchemeName = "bearerAuth"; final String securitySchemeName = "bearerAuth";
return new OpenAPI() return new OpenAPI()
.addServersItem(new Server().url(this.context.getContextPath())) .addServersItem(new Server().url(this.context.getContextPath()))
.info(new Info() .info(new Info()
.title("LF8 project starter") .title("LF8 project starter")
.description("\n## Auth\n" + .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## 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" + "\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" + "```\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" + "\n" +
"\nor by CURL\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" + "```\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).") "\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")) .version("0.1"))
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
.components( .components(
new Components() new Components()
.addSecuritySchemes(securitySchemeName, .addSecuritySchemes(securitySchemeName,
new SecurityScheme() new SecurityScheme()
.name(securitySchemeName) .name(securitySchemeName)
.type(SecurityScheme.Type.HTTP) .type(SecurityScheme.Type.HTTP)
.scheme("bearer") .scheme("bearer")
.bearerFormat("JWT") .bearerFormat("JWT")
) )
); );
} }
} }

@ -1,51 +1,51 @@
package de.szut.lf8_starter.exceptionHandling; package de.szut.lf8_starter.exceptionHandling;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
import java.util.Date; import java.util.Date;
@ControllerAdvice @ControllerAdvice
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "500", description = "invalid JSON posted", @ApiResponse(responseCode = "500", description = "invalid JSON posted",
content = @Content) content = @Content)
}) })
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class) @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)); ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDetails> handleAllOtherExceptions(Exception ex, WebRequest request) { public ResponseEntity<ErrorDetails> handleAllOtherExceptions(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false)); ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getClass() + " " + ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
} }
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorDetails> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { public ResponseEntity<ErrorDetails> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
} }
@ExceptionHandler(ConstraintViolationException.class) @ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorDetails> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) { public ResponseEntity<ErrorDetails> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage(); String errorMessage = ex.getConstraintViolations().stream().findFirst().get().getMessage();
ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false)); ErrorDetails errorDetails = new ErrorDetails(new Date(), errorMessage, request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
} }
} }

@ -1,43 +1,43 @@
package de.szut.lf8_starter.project; package de.szut.lf8_starter.project;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
@Getter @Getter
@Setter @Setter
public class GetProjectDto { public class GetProjectDto {
@NotBlank @NotBlank
private String name; private String name;
@NotNull @NotNull
private long leadingEmployee; private long leadingEmployee;
private List<Long> employees; private List<Long> employees;
@NotNull @NotNull
private long contractor; private long contractor;
@NotBlank @NotBlank
private String contractorName; private String contractorName;
@NotBlank @NotBlank
private String comment; private String comment;
@NotNull @NotNull
@JsonFormat(pattern = "dd.MM.yyyy") @JsonFormat(pattern = "dd.MM.yyyy")
private LocalDate startDate; private LocalDate startDate;
@NotNull @NotNull
@JsonFormat(pattern = "dd.MM.yyyy") @JsonFormat(pattern = "dd.MM.yyyy")
private LocalDate plannedEndDate; private LocalDate plannedEndDate;
@NotNull @NotNull
@JsonFormat(pattern = "dd.MM.yyyy") @JsonFormat(pattern = "dd.MM.yyyy")
private LocalDate endDate; private LocalDate endDate;
} }

@ -1,43 +1,43 @@
package de.szut.lf8_starter.project; package de.szut.lf8_starter.project;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
@Setter @Setter
@Entity @Entity
@Table(name = "projects") @Table(name = "projects")
public class ProjectEntity { public class ProjectEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id; private long id;
private String name; private String name;
private long leadingEmployee; private long leadingEmployee;
@ElementCollection @ElementCollection
private List<Long> employees; private List<Long> employees;
private long contractor; private long contractor;
private String contractorName; private String contractorName;
private String comment; private String comment;
@CreatedDate @CreatedDate
private LocalDate startDate; private LocalDate startDate;
private LocalDate plannedEndDate; private LocalDate plannedEndDate;
private LocalDate endDate; private LocalDate endDate;
} }

@ -1,39 +1,39 @@
package de.szut.lf8_starter.project; package de.szut.lf8_starter.project;
import de.szut.lf8_starter.project.dto.CreateProjectDto; import de.szut.lf8_starter.project.dto.CreateProjectDto;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
public class ProjectMapper { public class ProjectMapper {
public ProjectEntity mapCreateDtoToEntity(CreateProjectDto createProjectDto) { public ProjectEntity mapCreateDtoToEntity(CreateProjectDto createProjectDto) {
ProjectEntity projectEntity = new ProjectEntity(); ProjectEntity projectEntity = new ProjectEntity();
projectEntity.setName(createProjectDto.getName()); projectEntity.setName(createProjectDto.getName());
projectEntity.setComment(createProjectDto.getComment()); projectEntity.setComment(createProjectDto.getComment());
projectEntity.setLeadingEmployee(createProjectDto.getLeadingEmployee()); projectEntity.setLeadingEmployee(createProjectDto.getLeadingEmployee());
projectEntity.setEmployees(createProjectDto.getEmployees()); projectEntity.setEmployees(createProjectDto.getEmployees());
projectEntity.setContractor(createProjectDto.getContractor()); projectEntity.setContractor(createProjectDto.getContractor());
projectEntity.setContractorName(createProjectDto.getContractorName()); projectEntity.setContractorName(createProjectDto.getContractorName());
projectEntity.setStartDate(createProjectDto.getStartDate()); projectEntity.setStartDate(createProjectDto.getStartDate());
projectEntity.setPlannedEndDate(createProjectDto.getPlannedEndDate()); projectEntity.setPlannedEndDate(createProjectDto.getPlannedEndDate());
projectEntity.setEndDate(createProjectDto.getEndDate()); projectEntity.setEndDate(createProjectDto.getEndDate());
return projectEntity; return projectEntity;
} }
public GetProjectDto mapToGetDto(ProjectEntity projectEntity) { public GetProjectDto mapToGetDto(ProjectEntity projectEntity) {
GetProjectDto getProjectDto = new GetProjectDto(); GetProjectDto getProjectDto = new GetProjectDto();
getProjectDto.setName(projectEntity.getName()); getProjectDto.setName(projectEntity.getName());
getProjectDto.setComment(projectEntity.getComment()); getProjectDto.setComment(projectEntity.getComment());
getProjectDto.setLeadingEmployee(projectEntity.getLeadingEmployee()); getProjectDto.setLeadingEmployee(projectEntity.getLeadingEmployee());
getProjectDto.setEmployees(projectEntity.getEmployees()); getProjectDto.setEmployees(projectEntity.getEmployees());
getProjectDto.setContractor(projectEntity.getContractor()); getProjectDto.setContractor(projectEntity.getContractor());
getProjectDto.setContractorName(projectEntity.getContractorName()); getProjectDto.setContractorName(projectEntity.getContractorName());
getProjectDto.setStartDate(projectEntity.getStartDate()); getProjectDto.setStartDate(projectEntity.getStartDate());
getProjectDto.setPlannedEndDate(projectEntity.getPlannedEndDate()); getProjectDto.setPlannedEndDate(projectEntity.getPlannedEndDate());
getProjectDto.setEndDate(projectEntity.getEndDate()); getProjectDto.setEndDate(projectEntity.getEndDate());
return getProjectDto; return getProjectDto;
} }
} }

@ -1,16 +1,16 @@
package de.szut.lf8_starter.project; package de.szut.lf8_starter.project;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
public class ProjectService { public class ProjectService {
private final ProjectRepository projectRepository; private final ProjectRepository projectRepository;
public ProjectService(ProjectRepository projectRepository) { public ProjectService(ProjectRepository projectRepository) {
this.projectRepository = projectRepository; this.projectRepository = projectRepository;
} }
public ProjectEntity create(ProjectEntity projectEntity) { public ProjectEntity create(ProjectEntity projectEntity) {
return this.projectRepository.save(projectEntity); return this.projectRepository.save(projectEntity);
} }
} }

@ -1,42 +1,42 @@
package de.szut.lf8_starter.project.dto; package de.szut.lf8_starter.project.dto;
import de.szut.lf8_starter.project.GetProjectDto; import de.szut.lf8_starter.project.GetProjectDto;
import de.szut.lf8_starter.project.ProjectEntity; import de.szut.lf8_starter.project.ProjectEntity;
import de.szut.lf8_starter.project.ProjectMapper; import de.szut.lf8_starter.project.ProjectMapper;
import de.szut.lf8_starter.project.ProjectService; import de.szut.lf8_starter.project.ProjectService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping(value = "/projects") @RequestMapping(value = "/projects")
public class CreateProjectAction { public class CreateProjectAction {
private final ProjectService projectService; private final ProjectService projectService;
private final ProjectMapper projectMapper; private final ProjectMapper projectMapper;
public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) { public CreateProjectAction(ProjectService projectService, ProjectMapper mappingService) {
this.projectService = projectService; this.projectService = projectService;
this.projectMapper = mappingService; this.projectMapper = mappingService;
} }
@Operation(summary = "Creates a new Project") @Operation(summary = "Creates a new Project")
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "created project", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = GetProjectDto.class))}), @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 = "400", description = "invalid JSON posted", content = @Content),
@ApiResponse(responseCode = "401", description = "not authorized", content = @Content)}) @ApiResponse(responseCode = "401", description = "not authorized", content = @Content)})
@PostMapping @PostMapping
public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) { public GetProjectDto create(@RequestBody @Valid CreateProjectDto createProjectDto) {
ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto); ProjectEntity projectEntity = this.projectMapper.mapCreateDtoToEntity(createProjectDto);
projectEntity = this.projectService.create(projectEntity); projectEntity = this.projectService.create(projectEntity);
return this.projectMapper.mapToGetDto(projectEntity); return this.projectMapper.mapToGetDto(projectEntity);
} }
} }

@ -1,42 +1,42 @@
package de.szut.lf8_starter.project.dto; package de.szut.lf8_starter.project.dto;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
@Getter @Getter
@Setter @Setter
public class CreateProjectDto { public class CreateProjectDto {
@NotBlank @NotBlank
private String name; private String name;
@NotNull @NotNull
private long leadingEmployee; private long leadingEmployee;
private List<Long> employees; private List<Long> employees;
@NotNull @NotNull
private long contractor; private long contractor;
@NotBlank @NotBlank
private String contractorName; private String contractorName;
@NotBlank @NotBlank
private String comment; private String comment;
@JsonFormat(pattern = "dd.MM.yyyy") @JsonFormat(pattern = "dd.MM.yyyy")
@NotNull @NotNull
private LocalDate startDate; private LocalDate startDate;
@JsonFormat(pattern = "dd.MM.yyyy") @JsonFormat(pattern = "dd.MM.yyyy")
@NotNull @NotNull
private LocalDate plannedEndDate; private LocalDate plannedEndDate;
@JsonFormat(pattern = "dd.MM.yyyy") @JsonFormat(pattern = "dd.MM.yyyy")
private LocalDate endDate; private LocalDate endDate;
} }

@ -1,49 +1,49 @@
package de.szut.lf8_starter.security; package de.szut.lf8_starter.security;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@Slf4j @Slf4j
@Component @Component
public class KeycloakLogoutHandler implements LogoutHandler { public class KeycloakLogoutHandler implements LogoutHandler {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
public KeycloakLogoutHandler(RestTemplate restTemplate) { public KeycloakLogoutHandler(RestTemplate restTemplate) {
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
} }
@Override @Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) { public void logout(HttpServletRequest request, HttpServletResponse response, Authentication auth) {
logout(request, auth); logout(request, auth);
} }
public void logout(HttpServletRequest request, Authentication auth) { public void logout(HttpServletRequest request, Authentication auth) {
logoutFromKeycloak((OidcUser) auth.getPrincipal()); logoutFromKeycloak((OidcUser) auth.getPrincipal());
} }
private void logoutFromKeycloak(OidcUser user) { private void logoutFromKeycloak(OidcUser user) {
String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout"; String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout";
UriComponentsBuilder builder = UriComponentsBuilder UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(endSessionEndpoint) .fromUriString(endSessionEndpoint)
.queryParam("id_token_hint", user.getIdToken().getTokenValue()); .queryParam("id_token_hint", user.getIdToken().getTokenValue());
ResponseEntity<String> logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class); ResponseEntity<String> logoutResponse = restTemplate.getForEntity(builder.toUriString(), String.class);
if (logoutResponse.getStatusCode().is2xxSuccessful()) { if (logoutResponse.getStatusCode().is2xxSuccessful()) {
log.info("Successfulley logged out from Keycloak"); log.info("Successfulley logged out from Keycloak");
} else { } else {
log.error("Could not propagate logout to Keycloak"); log.error("Could not propagate logout to Keycloak");
} }
} }
} }

@ -1,97 +1,97 @@
package de.szut.lf8_starter.security; package de.szut.lf8_starter.security;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; 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.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
class KeycloakSecurityConfig { class KeycloakSecurityConfig {
private static final String GROUPS = "groups"; private static final String GROUPS = "groups";
private static final String REALM_ACCESS_CLAIM = "realm_access"; private static final String REALM_ACCESS_CLAIM = "realm_access";
private static final String ROLES_CLAIM = "roles"; private static final String ROLES_CLAIM = "roles";
private final KeycloakLogoutHandler keycloakLogoutHandler; private final KeycloakLogoutHandler keycloakLogoutHandler;
KeycloakSecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) { KeycloakSecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) {
this.keycloakLogoutHandler = keycloakLogoutHandler; this.keycloakLogoutHandler = keycloakLogoutHandler;
} }
@Bean @Bean
public SessionRegistry sessionRegistry() { public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl(); return new SessionRegistryImpl();
} }
@Bean @Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(sessionRegistry()); return new RegisterSessionAuthenticationStrategy(sessionRegistry());
} }
@Bean @Bean
public HttpSessionEventPublisher httpSessionEventPublisher() { public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher(); return new HttpSessionEventPublisher();
} }
@Bean @Bean
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth http.authorizeHttpRequests(auth -> auth
.requestMatchers(new AntPathRequestMatcher("/welcome")) .requestMatchers(new AntPathRequestMatcher("/welcome"))
.permitAll() .permitAll()
.requestMatchers( .requestMatchers(
new AntPathRequestMatcher("/swagger"), new AntPathRequestMatcher("/swagger"),
new AntPathRequestMatcher("/swagger-ui/**"), new AntPathRequestMatcher("/swagger-ui/**"),
new AntPathRequestMatcher("/v3/api-docs/**")) new AntPathRequestMatcher("/v3/api-docs/**"))
.permitAll() .permitAll()
.requestMatchers(new AntPathRequestMatcher("/hello/**")) .requestMatchers(new AntPathRequestMatcher("/hello/**"))
.hasRole("user") .hasRole("user")
.requestMatchers(new AntPathRequestMatcher("/roles")) .requestMatchers(new AntPathRequestMatcher("/roles"))
.authenticated() .authenticated()
.requestMatchers(new AntPathRequestMatcher("/")) .requestMatchers(new AntPathRequestMatcher("/"))
.permitAll() .permitAll()
.anyRequest() .anyRequest()
.authenticated()).oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults())); .authenticated()).oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults()));
return http.build(); return http.build();
} }
@Bean @Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() { public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> { jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
Map<String, Object> realmAccess = jwt.getClaim("realm_access"); Map<String, Object> realmAccess = jwt.getClaim("realm_access");
if (realmAccess != null && realmAccess.containsKey("roles")) { if (realmAccess != null && realmAccess.containsKey("roles")) {
List<String> roles = (List<String>) realmAccess.get("roles"); List<String> roles = (List<String>) realmAccess.get("roles");
for (String role : roles) { for (String role : roles) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)); grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role));
} }
} }
return grantedAuthorities; return grantedAuthorities;
}); });
return jwtAuthenticationConverter; return jwtAuthenticationConverter;
} }
} }

@ -1,5 +1,5 @@
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.driver-class-name=org.h2.Driver spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa spring.datasource.username=sa
spring.datasource.password= spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

@ -1,14 +1,14 @@
package de.szut.lf8_starter; package de.szut.lf8_starter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@SpringBootTest @SpringBootTest
class Lf8StarterApplicationTests { class Lf8StarterApplicationTests {
@Test @Test
void contextLoads() { void contextLoads() {
} }
} }