From 83ffeb107fe97a396c30f62f0c1374ce1e8ba695 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Sun, 23 Mar 2025 03:00:14 +0100 Subject: [PATCH] feat: added complete example for spring boot rest service with devcontainer --- .../.devcontainer/.dockerignore | 8 + .../spring-boot-todo/.devcontainer/.gitignore | 27 ++ .../.devcontainer/Dockerfile.dev | 15 + .../.devcontainer/devcontainer.json | 15 + .../.devcontainer/docker-compose.yml | 39 +++ .../spring-boot-todo/.devcontainer/init.sql | 19 ++ cmd/jws/projects/spring-boot-todo/Dockerfile | 24 -- .../projects/spring-boot-todo/compose.yaml | 36 --- cmd/jws/projects/spring-boot-todo/mvnw | 259 ------------------ cmd/jws/projects/spring-boot-todo/mvnw.cmd | 149 ---------- cmd/jws/projects/spring-boot-todo/pom.xml | 144 ++++++---- .../todo_spring/TodoSpringApplication.java | 13 - .../controllers/TaskController.java | 50 ---- .../com/example/todo_spring/models/Task.java | 46 ---- .../repositories/TaskRepository.java | 20 -- .../todo_spring/services/TaskService.java | 47 ---- .../java/com/todoapp/TodoAppApplication.java | 12 + .../todoapp/controller/AuthController.java | 91 ++++++ .../todoapp/controller/TodoController.java | 144 ++++++++++ .../java/com/todoapp/dto/LoginRequest.java | 23 ++ .../com/todoapp/dto/RegistrationRequest.java | 32 +++ .../src/main/java/com/todoapp/model/Todo.java | 109 ++++++++ .../src/main/java/com/todoapp/model/User.java | 101 +++++++ .../todoapp/repository/TodoRepository.java | 13 + .../todoapp/repository/UserRepository.java | 13 + .../com/todoapp/security/SecurityConfig.java | 66 +++++ .../security/UserDetailsServiceImpl.java | 33 +++ .../java/com/todoapp/service/TodoService.java | 65 +++++ .../java/com/todoapp/service/UserService.java | 55 ++++ .../main/resources/META-INF/persistence.xml | 20 ++ .../src/main/resources/application.properties | 33 ++- .../src/main/resources/hibernate.cfg.xml | 26 ++ .../src/main/resources/schema.sql | 0 33 files changed, 1030 insertions(+), 717 deletions(-) create mode 100644 cmd/jws/projects/spring-boot-todo/.devcontainer/.dockerignore create mode 100644 cmd/jws/projects/spring-boot-todo/.devcontainer/.gitignore create mode 100644 cmd/jws/projects/spring-boot-todo/.devcontainer/Dockerfile.dev create mode 100644 cmd/jws/projects/spring-boot-todo/.devcontainer/devcontainer.json create mode 100644 cmd/jws/projects/spring-boot-todo/.devcontainer/docker-compose.yml create mode 100644 cmd/jws/projects/spring-boot-todo/.devcontainer/init.sql delete mode 100644 cmd/jws/projects/spring-boot-todo/Dockerfile delete mode 100644 cmd/jws/projects/spring-boot-todo/compose.yaml delete mode 100755 cmd/jws/projects/spring-boot-todo/mvnw delete mode 100644 cmd/jws/projects/spring-boot-todo/mvnw.cmd delete mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/TodoSpringApplication.java delete mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/controllers/TaskController.java delete mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/models/Task.java delete mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/repositories/TaskRepository.java delete mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/services/TaskService.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/TodoAppApplication.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/AuthController.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/TodoController.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/LoginRequest.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/Todo.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/User.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/TodoRepository.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/UserRepository.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/SecurityConfig.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/UserDetailsServiceImpl.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/TodoService.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/UserService.java create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/resources/META-INF/persistence.xml create mode 100644 cmd/jws/projects/spring-boot-todo/src/main/resources/hibernate.cfg.xml delete mode 100644 cmd/jws/projects/spring-boot-todo/src/main/resources/schema.sql diff --git a/cmd/jws/projects/spring-boot-todo/.devcontainer/.dockerignore b/cmd/jws/projects/spring-boot-todo/.devcontainer/.dockerignore new file mode 100644 index 0000000..017b280 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/.devcontainer/.dockerignore @@ -0,0 +1,8 @@ +.git +.github +.settings +target +*.iml +.idea +.vscode +node_modules diff --git a/cmd/jws/projects/spring-boot-todo/.devcontainer/.gitignore b/cmd/jws/projects/spring-boot-todo/.devcontainer/.gitignore new file mode 100644 index 0000000..82b069a --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/.devcontainer/.gitignore @@ -0,0 +1,27 @@ +# Maven +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +# IDE-spezifische Dateien +.idea/ +*.iws +*.iml +*.ipr +.vscode/ +.settings/ +.classpath +.project +.factorypath + +# Temporäre Dateien +*.log +*.tmp +*.temp + +# Lokale Konfigurationsdateien +src/main/resources/application-local.properties + +# Devcontainer Volumen +.devcontainer/postgres-data/ diff --git a/cmd/jws/projects/spring-boot-todo/.devcontainer/Dockerfile.dev b/cmd/jws/projects/spring-boot-todo/.devcontainer/Dockerfile.dev new file mode 100644 index 0000000..4fc5dbe --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/.devcontainer/Dockerfile.dev @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/devcontainers/java:1-21-bullseye + +RUN apt-get update && apt-get install -y curl maven + +RUN curl -o /tmp/postgresql.jar https://jdbc.postgresql.org/download/postgresql-42.6.0.jar && \ + mkdir -p /usr/local/lib/ && mv /tmp/postgresql.jar /usr/local/lib/postgresql.jar + +WORKDIR /app + +COPY . . + +RUN mvn clean install -DskipTests + +CMD ["mvn", "spring-boot:run"] + diff --git a/cmd/jws/projects/spring-boot-todo/.devcontainer/devcontainer.json b/cmd/jws/projects/spring-boot-todo/.devcontainer/devcontainer.json new file mode 100644 index 0000000..49af787 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +{ + "name": "Java & PostgreSQL", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "forwardPorts": [9080, 5432], + "postCreateCommand": "mvn clean install -DskipTests", + "remoteUser": "vscode", + "features": { + "ghcr.io/devcontainers-extra/features/maven-sdkman:2": { + "version": "latest" + }, + "ghcr.io/devcontainers/features/git:1": { "version": "latest" } + } +} diff --git a/cmd/jws/projects/spring-boot-todo/.devcontainer/docker-compose.yml b/cmd/jws/projects/spring-boot-todo/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..c7078c7 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/.devcontainer/docker-compose.yml @@ -0,0 +1,39 @@ +services: + app: + container_name: javadev + build: + context: ../ + dockerfile: .devcontainer/Dockerfile.dev + volumes: + - ../..:/workspaces:cached + - ./app:/app + environment: + POSTGRES_HOST: postgresdb + POSTGRES_PORT: 5432 + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + networks: + - my_network + command: sleep infinity + + db: + container_name: postgresdb + image: postgres:latest + restart: unless-stopped + volumes: + - postgres-data:/var/lib/postgresql/data + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + POSTGRES_DB: todo_db + networks: + - my_network + +volumes: + postgres-data: + +networks: + my_network: + driver: bridge diff --git a/cmd/jws/projects/spring-boot-todo/.devcontainer/init.sql b/cmd/jws/projects/spring-boot-todo/.devcontainer/init.sql new file mode 100644 index 0000000..818eadb --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/.devcontainer/init.sql @@ -0,0 +1,19 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL +); + +CREATE TABLE todos ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description VARCHAR(1000), + target_date DATE, + completed BOOLEAN, + user_id BIGINT NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) +); + +INSERT INTO users (username, password, email) VALUES +('test', '$2a$12$nt7xQKNDKZhxQFZGD5Wy0.Uh0wdPtWDgwfnWnPLgBWnQDGGkNLKBi', 'test@example.com'); diff --git a/cmd/jws/projects/spring-boot-todo/Dockerfile b/cmd/jws/projects/spring-boot-todo/Dockerfile deleted file mode 100644 index f002fa2..0000000 --- a/cmd/jws/projects/spring-boot-todo/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM maven:3.9-eclipse-temurin-17 as builder - -WORKDIR /app -COPY . . -# RUN mvn clean package - -# Baue die Anwendung mit Maven -RUN mvn clean install -DskipTests - -# Verwende ein schlankes OpenJDK-Image für die Ausführung -FROM openjdk:17-jdk-slim - -# Setze das Arbeitsverzeichnis im Container -WORKDIR /app - -# Kopiere die JAR-Datei aus dem Builder-Image -COPY --from=builder /app/target/*.jar app.jar - -# Mache Port 8080 im Container verfügbar (oder den Port, den deine Anwendung verwendet) -EXPOSE 8090 - -# Definiere den Befehl zum Ausführen der Anwendung -ENTRYPOINT ["java", "-jar", "app.jar"] - diff --git a/cmd/jws/projects/spring-boot-todo/compose.yaml b/cmd/jws/projects/spring-boot-todo/compose.yaml deleted file mode 100644 index 02856b5..0000000 --- a/cmd/jws/projects/spring-boot-todo/compose.yaml +++ /dev/null @@ -1,36 +0,0 @@ -version: "3.8" -services: - db: - image: postgres:latest - container_name: todolist-db - restart: unless-stopped - environment: - POSTGRES_DB: todolist - POSTGRES_USER: myuser - POSTGRES_PASSWORD: mypassword - ports: - - "5432:5432" - volumes: - - db_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U myuser -d todolist"] - interval: 10s - timeout: 5s - retries: 5 - - app: - build: . - container_name: todo-spring-app - ports: - - "8090:8090" - depends_on: - db: - condition: service_healthy - environment: - SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/todolist - SPRING_DATASOURCE_USERNAME: myuser - SPRING_DATASOURCE_PASSWORD: mypassword - restart: unless-stopped - -volumes: - db_data: diff --git a/cmd/jws/projects/spring-boot-todo/mvnw b/cmd/jws/projects/spring-boot-todo/mvnw deleted file mode 100755 index 19529dd..0000000 --- a/cmd/jws/projects/spring-boot-todo/mvnw +++ /dev/null @@ -1,259 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/cmd/jws/projects/spring-boot-todo/mvnw.cmd b/cmd/jws/projects/spring-boot-todo/mvnw.cmd deleted file mode 100644 index 249bdf3..0000000 --- a/cmd/jws/projects/spring-boot-todo/mvnw.cmd +++ /dev/null @@ -1,149 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/cmd/jws/projects/spring-boot-todo/pom.xml b/cmd/jws/projects/spring-boot-todo/pom.xml index d8de2b7..a490b5c 100644 --- a/cmd/jws/projects/spring-boot-todo/pom.xml +++ b/cmd/jws/projects/spring-boot-todo/pom.xml @@ -1,63 +1,87 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.4.3 - - - com.example - todo-spring - 0.0.1-SNAPSHOT - todo-spring - Demo Todo App for Spring Boot - - - - - - - - - - - - - - - 17 - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-web - - - - org.postgresql - postgresql - runtime - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + com.todoapp + spring-todo-app + 0.0.1-SNAPSHOT + spring-todo-app + Spring Boot Todo App + + + 17 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.postgresql + postgresql + runtime + + + + + at.favre.lib + bcrypt + 0.10.2 + + + + + org.springframework.boot + spring-boot-starter-json + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/TodoSpringApplication.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/TodoSpringApplication.java deleted file mode 100644 index d97a5d3..0000000 --- a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/TodoSpringApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.todo_spring; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class TodoSpringApplication { - - public static void main(String[] args) { - SpringApplication.run(TodoSpringApplication.class, args); - } - -} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/controllers/TaskController.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/controllers/TaskController.java deleted file mode 100644 index c6a7593..0000000 --- a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/controllers/TaskController.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.example.todo_spring.controllers; - -import com.example.todo_spring.models.Task; -import com.example.todo_spring.services.TaskService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@Controller -@RequestMapping("/api/v1/tasks") -public class TaskController { - - @Autowired - private TaskService taskService; - - @GetMapping("/") - public ResponseEntity> getAllTasks() { - return ResponseEntity.ok(taskService.getAllTask()); - } - - @GetMapping("/completed") - public ResponseEntity> getAllCompletedTasks() { - return ResponseEntity.ok(taskService.findAllCompletedTask()); - } - - @GetMapping("/incomplete") - public ResponseEntity> getAllIncompleteTasks() { - return ResponseEntity.ok(taskService.findAllInCompleteTask()); - } - - @PostMapping("/") - public ResponseEntity createTask(@RequestBody Task task) { - return ResponseEntity.ok(taskService.createNewTask(task)); - } - - @PutMapping("/{id}") - public ResponseEntity updateTask(@PathVariable Long id, @RequestBody Task task) { - task.setId(id); - return ResponseEntity.ok(taskService.updateTask(task)); - } - - @DeleteMapping("/{id}") - public ResponseEntity getAllTasks(@PathVariable Long id) { - taskService.deleteTaskById(id); - return ResponseEntity.ok(true); - } -} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/models/Task.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/models/Task.java deleted file mode 100644 index a2565e6..0000000 --- a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/models/Task.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.example.todo_spring.models; - -import jakarta.persistence.*; - -@Entity -public class Task { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - private String task; - private boolean completed; - - public Task(String task, boolean completed) { - this.task = task; - this.completed = completed; - } - - public Task() { - this.task = ""; - this.completed = false; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTask() { - return task; - } - - public void setTask(String task) { - this.task = task; - } - - public boolean isCompleted() { - return completed; - } - - public void setCompleted(boolean completed) { - this.completed = completed; - } -} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/repositories/TaskRepository.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/repositories/TaskRepository.java deleted file mode 100644 index be73235..0000000 --- a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/repositories/TaskRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.todo_spring.repositories; - -import com.example.todo_spring.models.Task; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface TaskRepository extends JpaRepository { - public Task findByTask(String task); - - public List findByCompletedTrue(); - - public List findByCompletedFalse(); - - public List findAll(); - - public Task getById(Long id); -} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/services/TaskService.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/services/TaskService.java deleted file mode 100644 index aff11a1..0000000 --- a/cmd/jws/projects/spring-boot-todo/src/main/java/com/example/todo_spring/services/TaskService.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.example.todo_spring.services; - -import com.example.todo_spring.models.Task; -import com.example.todo_spring.repositories.TaskRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public class TaskService { - @Autowired - private TaskRepository taskRepository; - - public Task createNewTask(Task task) { - return taskRepository.save(task); - } - - public List getAllTask() { - return taskRepository.findAll(); - } - - public Task findTaskById(Long id) { - return taskRepository.getById(id); - } - - public List findAllCompletedTask() { - return taskRepository.findByCompletedTrue(); - } - - public List findAllInCompleteTask() { - return taskRepository.findByCompletedFalse(); - } - - public void deleteTask(Task task) { - taskRepository.delete(task); - } - - public void deleteTaskById(Long id) { - Task task = taskRepository.getById(id); - taskRepository.delete(task); - } - - public Task updateTask(Task task) { - return taskRepository.save(task); - } -} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/TodoAppApplication.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/TodoAppApplication.java new file mode 100644 index 0000000..b356f72 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/TodoAppApplication.java @@ -0,0 +1,12 @@ +package com.todoapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TodoAppApplication { + + public static void main(String[] args) { + SpringApplication.run(TodoAppApplication.class, args); + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/AuthController.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/AuthController.java new file mode 100644 index 0000000..29dcd72 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/AuthController.java @@ -0,0 +1,91 @@ +package com.todoapp.controller; + +import com.todoapp.dto.LoginRequest; +import com.todoapp.dto.RegistrationRequest; +import com.todoapp.model.User; +import com.todoapp.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +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 java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + private final UserService userService; + private final AuthenticationManager authenticationManager; + + @Autowired + public AuthController(UserService userService, AuthenticationManager authenticationManager) { + this.userService = userService; + this.authenticationManager = authenticationManager; + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegistrationRequest request) { + if (request.getUsername() == null || request.getPassword() == null || request.getEmail() == null) { + Map response = new HashMap<>(); + response.put("error", "Username, password, and email are required"); + return ResponseEntity.badRequest().body(response); + } + + if (userService.usernameExists(request.getUsername())) { + Map response = new HashMap<>(); + response.put("error", "Username already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + if (userService.emailExists(request.getEmail())) { + Map response = new HashMap<>(); + response.put("error", "Email already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + User user = new User(request.getUsername(), request.getPassword(), request.getEmail()); + userService.saveUser(user); + + Map response = new HashMap<>(); + response.put("message", "User registered successfully"); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest request) { + if (request.getUsername() == null || request.getPassword() == null) { + Map response = new HashMap<>(); + response.put("error", "Username and password are required"); + return ResponseEntity.badRequest().body(response); + } + + try { + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + User user = userService.getUserByUsername(request.getUsername()) + .orElseThrow(() -> new RuntimeException("User not found")); + + Map response = new HashMap<>(); + response.put("message", "Login successful"); + response.put("username", user.getUsername()); + response.put("email", user.getEmail()); + + return ResponseEntity.ok(response); + } catch (Exception e) { + Map response = new HashMap<>(); + response.put("error", "Invalid credentials"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); + } + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/TodoController.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/TodoController.java new file mode 100644 index 0000000..2cdb7aa --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/controller/TodoController.java @@ -0,0 +1,144 @@ +package com.todoapp.controller; + +import com.todoapp.model.Todo; +import com.todoapp.service.TodoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/todos") +public class TodoController { + + private final TodoService todoService; + + @Autowired + public TodoController(TodoService todoService) { + this.todoService = todoService; + } + + @GetMapping + public ResponseEntity> getAllTodos(Authentication authentication) { + String username = authentication.getName(); + List todos = todoService.getTodosByUsername(username); + return ResponseEntity.ok(todos); + } + + @GetMapping("/{id}") + public ResponseEntity getTodoById(@PathVariable("id") Long id, Authentication authentication) { + String username = authentication.getName(); + Optional todoOptional = todoService.getTodoById(id); + if (todoOptional.isPresent()) { + Todo todo = todoOptional.get(); + if (!todo.getUser().getUsername().equals(username)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + return ResponseEntity.ok(todo); + } else { + return ResponseEntity.notFound().build(); + } + } + + @GetMapping("/completed") + public ResponseEntity> getCompletedTodos(Authentication authentication) { + String username = authentication.getName(); + List todos = todoService.getCompletedTodosByUsername(username); + return ResponseEntity.ok(todos); + } + + @GetMapping("/incomplete") + public ResponseEntity> getIncompleteTodos(Authentication authentication) { + String username = authentication.getName(); + List todos = todoService.getIncompleteTodosByUsername(username); + return ResponseEntity.ok(todos); + } + + @PostMapping + public ResponseEntity createTodo(@RequestBody Todo todo, Authentication authentication) { + String username = authentication.getName(); + Todo savedTodo = todoService.saveTodo(todo, username); + return ResponseEntity.status(HttpStatus.CREATED).body(savedTodo); + } + + @PutMapping("/{id}") + public ResponseEntity updateTodo(@PathVariable("id") Long id, @RequestBody Todo updatedTodo, + Authentication authentication) { + String username = authentication.getName(); + + Optional existingTodoOptional = todoService.getTodoById(id); + + if (existingTodoOptional.isPresent()) { + Todo existingTodo = existingTodoOptional.get(); + + if (!existingTodo.getUser().getUsername().equals(username)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + updatedTodo.setId(id); + updatedTodo.setUser(existingTodo.getUser()); + + Optional updatedTodoOptional = todoService.updateTodo(updatedTodo); + if (updatedTodoOptional.isPresent()) { + return ResponseEntity.ok(updatedTodoOptional.get()); + } else { + return ResponseEntity.notFound().build(); + } + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteTodo(@PathVariable("id") Long id, Authentication authentication) { + String username = authentication.getName(); + + Optional todoOptional = todoService.getTodoById(id); + + if (todoOptional.isPresent()) { + Todo todo = todoOptional.get(); + + if (!todo.getUser().getUsername().equals(username)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + boolean isDeleted = todoService.deleteTodo(id); + if (isDeleted) { + return ResponseEntity.noContent().build(); + } else { + return ResponseEntity.notFound().build(); + } + } else { + return ResponseEntity.notFound().build(); + } + } + + @PutMapping("/{id}/complete") + public ResponseEntity markTodoComplete(@PathVariable("id") Long id, Authentication authentication) { + String username = authentication.getName(); + + Optional todoOptional = todoService.getTodoById(id); + + if (todoOptional.isPresent()) { + Todo todo = todoOptional.get(); + + if (!todo.getUser().getUsername().equals(username)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + todo.setCompleted(true); + + Optional updatedTodoOptional = todoService.updateTodo(todo); + if (updatedTodoOptional.isPresent()) { + return ResponseEntity.ok(updatedTodoOptional.get()); + } else { + return ResponseEntity.notFound().build(); + } + } else { + return ResponseEntity.notFound().build(); + } + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/LoginRequest.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/LoginRequest.java new file mode 100644 index 0000000..5608a6e --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/LoginRequest.java @@ -0,0 +1,23 @@ +package com.todoapp.dto; + +public class LoginRequest { + private String username; + private String password; + + // Getter und Setter + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java new file mode 100644 index 0000000..04561ec --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java @@ -0,0 +1,32 @@ +package com.todoapp.dto; + +public class RegistrationRequest { + private String username; + private String password; + private String email; + + // Getter und Setter + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/Todo.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/Todo.java new file mode 100644 index 0000000..0abf071 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/Todo.java @@ -0,0 +1,109 @@ +package com.todoapp.model; + +import jakarta.persistence.*; +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.util.Date; + +@Entity +@Table(name = "todos") +public class Todo implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + @Column(length = 1000) + private String description; + + @Column(name = "target_date") + @Temporal(TemporalType.DATE) + private Date targetDate; + + @Column + private boolean completed; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + @JsonIgnore + private User user; + + // Konstruktoren + public Todo() { + } + + public Todo(String title, String description, Date targetDate, boolean completed) { + this.title = title; + this.description = description; + this.targetDate = targetDate; + this.completed = completed; + } + + // Getter und Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + // Zusätzliche Methode für REST-Antworten + public Long getUserId() { + return user != null ? user.getId() : null; + } + + @Override + public String toString() { + return "Todo{" + + "id=" + id + + ", title='" + title + '\'' + + ", description='" + description + '\'' + + ", targetDate=" + targetDate + + ", completed=" + completed + + '}'; + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/User.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/User.java new file mode 100644 index 0000000..9af8c5c --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/model/User.java @@ -0,0 +1,101 @@ +package com.todoapp.model; + +import jakarta.persistence.*; +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "users") +public class User implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + @Column(nullable = false) + @JsonIgnore // Verhindert, dass das Passwort in JSON serialisiert wird + private String password; + + @Column(nullable = false) + private String email; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore + private List todos = new ArrayList<>(); + + // Konstruktoren + public User() { + } + + public User(String username, String password, String email) { + this.username = username; + this.password = password; + this.email = email; + } + + // Getter und Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getTodos() { + return todos; + } + + public void setTodos(List todos) { + this.todos = todos; + } + + // Hilfsmethoden + public void addTodo(Todo todo) { + todos.add(todo); + todo.setUser(this); + } + + public void removeTodo(Todo todo) { + todos.remove(todo); + todo.setUser(null); + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", username='" + username + '\'' + + ", email='" + email + '\'' + + '}'; + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/TodoRepository.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/TodoRepository.java new file mode 100644 index 0000000..d6c229e --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/TodoRepository.java @@ -0,0 +1,13 @@ +package com.todoapp.repository; + +import com.todoapp.model.Todo; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface TodoRepository extends JpaRepository { + List findByUserUsernameOrderByCompletedAscTargetDateAsc(String username); + + List findByUserUsernameAndCompletedTrue(String username); + + List findByUserUsernameAndCompletedFalseOrderByTargetDateAsc(String username); +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/UserRepository.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/UserRepository.java new file mode 100644 index 0000000..470532f --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/repository/UserRepository.java @@ -0,0 +1,13 @@ +package com.todoapp.repository; + +import com.todoapp.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); + + boolean existsByUsername(String username); + + boolean existsByEmail(String email); +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/SecurityConfig.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/SecurityConfig.java new file mode 100644 index 0000000..3e9b544 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/SecurityConfig.java @@ -0,0 +1,66 @@ +package com.todoapp.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.util.Arrays; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Autowired + private UserDetailsService userDetailsService; + + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + return http.getSharedObject(AuthenticationManagerBuilder.class) + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()) + .and() + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf().disable() + .cors().and() + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/auth/register").permitAll() + .requestMatchers("/api/auth/login").permitAll() + .anyRequest().authenticated()) + .httpBasic(); + + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList("*")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization")); + configuration.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/UserDetailsServiceImpl.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..d821693 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/security/UserDetailsServiceImpl.java @@ -0,0 +1,33 @@ +package com.todoapp.security; + +import com.todoapp.model.User; +import com.todoapp.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import java.util.Collections; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserRepository userRepository; + + @Autowired + public UserDetailsServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/TodoService.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/TodoService.java new file mode 100644 index 0000000..0140554 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/TodoService.java @@ -0,0 +1,65 @@ +package com.todoapp.service; + +import com.todoapp.model.Todo; +import com.todoapp.model.User; +import com.todoapp.repository.TodoRepository; +import com.todoapp.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + +@Service +public class TodoService { + + private final TodoRepository todoRepository; + private final UserRepository userRepository; + + @Autowired + public TodoService(TodoRepository todoRepository, UserRepository userRepository) { + this.todoRepository = todoRepository; + this.userRepository = userRepository; + } + + public Todo saveTodo(Todo todo, String username) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("User not found")); + todo.setUser(user); + return todoRepository.save(todo); + } + + public Optional updateTodo(Todo todo) { + if (todoRepository.existsById(todo.getId())) { + return Optional.of(todoRepository.save(todo)); + } + return Optional.empty(); + } + + public boolean deleteTodo(Long id) { + if (todoRepository.existsById(id)) { + todoRepository.deleteById(id); + return true; + } + return false; + } + + public Optional getTodoById(Long id) { + return todoRepository.findById(id); + } + + public List getAllTodos() { + return todoRepository.findAll(); + } + + public List getTodosByUsername(String username) { + return todoRepository.findByUserUsernameOrderByCompletedAscTargetDateAsc(username); + } + + public List getCompletedTodosByUsername(String username) { + return todoRepository.findByUserUsernameAndCompletedTrue(username); + } + + public List getIncompleteTodosByUsername(String username) { + return todoRepository.findByUserUsernameAndCompletedFalseOrderByTargetDateAsc(username); + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/UserService.java b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/UserService.java new file mode 100644 index 0000000..30cf5ac --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/java/com/todoapp/service/UserService.java @@ -0,0 +1,55 @@ +package com.todoapp.service; + +import com.todoapp.model.User; +import com.todoapp.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + +@Service +public class UserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Autowired + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + public User saveUser(User user) { + user.setPassword(passwordEncoder.encode(user.getPassword())); + return userRepository.save(user); + } + + public Optional getUserById(Long id) { + return userRepository.findById(id); + } + + public Optional getUserByUsername(String username) { + return userRepository.findByUsername(username); + } + + public List getAllUsers() { + return userRepository.findAll(); + } + + public boolean deleteUser(Long id) { + if (userRepository.existsById(id)) { + userRepository.deleteById(id); + return true; + } + return false; + } + + public boolean usernameExists(String username) { + return userRepository.existsByUsername(username); + } + + public boolean emailExists(String email) { + return userRepository.existsByEmail(email); + } +} diff --git a/cmd/jws/projects/spring-boot-todo/src/main/resources/META-INF/persistence.xml b/cmd/jws/projects/spring-boot-todo/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..1e6b316 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,20 @@ + + + + org.hibernate.jpa.HibernatePersistenceProvider + com.todoapp.model.User + com.todoapp.model.Todo + + + + + + + + + + + diff --git a/cmd/jws/projects/spring-boot-todo/src/main/resources/application.properties b/cmd/jws/projects/spring-boot-todo/src/main/resources/application.properties index e52e9af..1228279 100644 --- a/cmd/jws/projects/spring-boot-todo/src/main/resources/application.properties +++ b/cmd/jws/projects/spring-boot-todo/src/main/resources/application.properties @@ -1,14 +1,21 @@ -spring.application.name=todo-spring -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect -spring.jpa.hibernate.ddl-auto=update -#spring.jpa.hibernate.ddl-auto=none # Entferne oder kommentiere diese Zeile aus -spring.jpa.hibernate.show-sql=true -spring.datasource.url=jdbc:postgresql://localhost:5432/todolist -spring.datasource.username=myuser -spring.datasource.password=mypassword -spring.datasource.initialization-mode=never -#spring.datasource.initialize=true # Entferne oder kommentiere diese Zeile aus -#spring.datasource.schema=classpath:/schema.sql # Entferne oder kommentiere diese Zeile aus -spring.datasource.continue-on-error=true -server.port=8090 +# Datenbankverbindung +spring.datasource.url=jdbc:postgresql://postgresdb:5432/todo_db +spring.datasource.username=postgres +spring.datasource.password=postgres +spring.datasource.driver-class-name=org.postgresql.Driver +# JPA/Hibernate Einstellungen +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true + +# Server-Port +server.port=9080 + +# Logging (optional) +logging.level.org.springframework.web=INFO +logging.level.com.todoapp=DEBUG +logging.level.org.hibernate=ERROR + +# Security Debug (nur für Entwicklung) +logging.level.org.springframework.security=DEBUG diff --git a/cmd/jws/projects/spring-boot-todo/src/main/resources/hibernate.cfg.xml b/cmd/jws/projects/spring-boot-todo/src/main/resources/hibernate.cfg.xml new file mode 100644 index 0000000..33bdd20 --- /dev/null +++ b/cmd/jws/projects/spring-boot-todo/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,26 @@ + + + + + + jdbc/PostgresDataSource + + + org.hibernate.dialect.PostgreSQLDialect + + + true + + + thread + + + update + + + + + + diff --git a/cmd/jws/projects/spring-boot-todo/src/main/resources/schema.sql b/cmd/jws/projects/spring-boot-todo/src/main/resources/schema.sql deleted file mode 100644 index e69de29..0000000