diff --git a/build.gradle.kts b/build.gradle.kts index f39b1f8c..1e745013 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,15 +80,15 @@ tasks.register("generateOpenApiDocs") { "openapi": "3.0.3", "info": { "title": "${moduleName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }} API", - "description": "REST API for $moduleName management", + "description": "REST API für $moduleName Verwaltung", "version": "1.0.0", "contact": { "name": "Meldestelle Development Team" } }, "servers": [ - { "url": "http://localhost:8080", "description": "Development server" }, - { "url": "https://api.meldestelle.at", "description": "Production server" } + { "url": "http://localhost:8080", "description": "Entwicklungs-Server" }, + { "url": "https://api.meldestelle.at", "description": "Produktions-Server" } ], "paths": {}, "components": { diff --git a/client/common-ui/build.gradle.kts b/client/common-ui/build.gradle.kts index bca26bdd..c1225a75 100644 --- a/client/common-ui/build.gradle.kts +++ b/client/common-ui/build.gradle.kts @@ -17,6 +17,9 @@ kotlin { implementation(compose.foundation) implementation(compose.material3) + // Skiko - explicitly declared in common-ui + implementation("org.jetbrains.skiko:skiko:0.9.4.2") + // Serialization implementation(libs.kotlinx.serialization.json) diff --git a/docker-compose.clients.yml b/docker-compose.clients.yml index bf029fce..01ff6165 100644 --- a/docker-compose.clients.yml +++ b/docker-compose.clients.yml @@ -8,7 +8,7 @@ # Clients only: docker-compose -f docker-compose.clients.yml up # =================================================================== -version: '3.8' +#version: '3.8' services: # =================================================================== diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 47455364..4bb35bd5 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -13,7 +13,7 @@ # - Faster startup times # =================================================================== -version: '3.8' +#version: '3.8' services: # =================================================================== diff --git a/docker-compose.services.yml b/docker-compose.services.yml index a3c70b5f..cbb75ac1 100644 --- a/docker-compose.services.yml +++ b/docker-compose.services.yml @@ -380,6 +380,394 @@ services: - "service.version=1.0.0" - "service.environment=${SPRING_PROFILES_ACTIVE:-docker}" + # =================================================================== + # Members Service + # =================================================================== + members-service: + build: + context: . + dockerfile: dockerfiles/services/members-service/Dockerfile + args: + GRADLE_VERSION: ${GRADLE_VERSION:-8.14} + JAVA_VERSION: ${JAVA_VERSION:-21} + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-docker} + APP_USER: ${MEMBERS_APP_USER:-membersuser} + APP_UID: ${MEMBERS_APP_UID:-1004} + APP_GID: ${MEMBERS_APP_GID:-1004} + image: meldestelle/members-service:latest + container_name: meldestelle-members-service + ports: + - "${MEMBERS_SERVICE_PORT:-8084}:8084" + - "${MEMBERS_DEBUG_PORT:-5004}:5004" # Debug port + depends_on: + postgres: + condition: service_healthy + consul: + condition: service_healthy + redis: + condition: service_healthy + auth-server: + condition: service_healthy + environment: + # Spring Boot Configuration + - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-docker} + - SERVER_PORT=8084 + - MANAGEMENT_SERVER_PORT=8084 + - DEBUG=${DEBUG:-false} + + # Service Discovery + - SPRING_CLOUD_CONSUL_HOST=consul + - SPRING_CLOUD_CONSUL_PORT=8500 + - SPRING_APPLICATION_NAME=members-service + + # Database Configuration + - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/${POSTGRES_DB:-meldestelle} + - SPRING_DATASOURCE_USERNAME=${POSTGRES_USER:-meldestelle} + - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD:-meldestelle} + - SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=${MEMBERS_DB_POOL_SIZE:-20} + - SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE=${MEMBERS_DB_MIN_IDLE:-10} + + # Redis Configuration + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PORT=6379 + - SPRING_REDIS_PASSWORD=${REDIS_PASSWORD:-} + - SPRING_REDIS_TIMEOUT=${REDIS_TIMEOUT:-2000ms} + + # Security Configuration + - AUTH_SERVER_URL=http://auth-server:8081 + - JWT_SECRET=${JWT_SECRET:-meldestelle-auth-secret-key-change-in-production} + + # Monitoring & Observability + - MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics,prometheus,configprops + - MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=always + - MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED=true + - MANAGEMENT_TRACING_SAMPLING_PROBABILITY=${TRACING_SAMPLING:-0.1} + - MANAGEMENT_ZIPKIN_TRACING_ENDPOINT=http://zipkin:9411/api/v2/spans + + # Performance Tuning + - JAVA_OPTS=-XX:MaxRAMPercentage=80.0 -XX:+UseG1GC -XX:+UseStringDeduplication + - LOGGING_LEVEL_ROOT=${LOG_LEVEL:-INFO} + - LOGGING_LEVEL_AT_MOCODE_MEMBERS=${MEMBERS_LOG_LEVEL:-DEBUG} + networks: + - meldestelle-network + volumes: + - members-logs:/app/logs + - members-temp:/app/tmp + healthcheck: + test: ["CMD", "curl", "-fsS", "--max-time", "3", "http://localhost:8084/actuator/health/readiness"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 45s + restart: unless-stopped + deploy: + resources: + limits: + memory: 1024M + cpus: '1.5' + labels: + - "traefik.enable=true" + - "traefik.http.routers.members-service.rule=Host(`members.meldestelle.local`)" + - "traefik.http.services.members-service.loadbalancer.server.port=8084" + - "prometheus.scrape=true" + - "prometheus.port=8084" + - "prometheus.path=/actuator/prometheus" + - "prometheus.service=members-service" + - "service.name=members-service" + - "service.version=1.0.0" + - "service.environment=${SPRING_PROFILES_ACTIVE:-docker}" + + # =================================================================== + # Horses Service + # =================================================================== + horses-service: + build: + context: . + dockerfile: dockerfiles/services/horses-service/Dockerfile + args: + GRADLE_VERSION: ${GRADLE_VERSION:-8.14} + JAVA_VERSION: ${JAVA_VERSION:-21} + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-docker} + APP_USER: ${HORSES_APP_USER:-horsesuser} + APP_UID: ${HORSES_APP_UID:-1005} + APP_GID: ${HORSES_APP_GID:-1005} + image: meldestelle/horses-service:latest + container_name: meldestelle-horses-service + ports: + - "${HORSES_SERVICE_PORT:-8085}:8085" + - "${HORSES_DEBUG_PORT:-5005}:5005" # Debug port + depends_on: + postgres: + condition: service_healthy + consul: + condition: service_healthy + redis: + condition: service_healthy + auth-server: + condition: service_healthy + environment: + # Spring Boot Configuration + - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-docker} + - SERVER_PORT=8085 + - MANAGEMENT_SERVER_PORT=8085 + - DEBUG=${DEBUG:-false} + + # Service Discovery + - SPRING_CLOUD_CONSUL_HOST=consul + - SPRING_CLOUD_CONSUL_PORT=8500 + - SPRING_APPLICATION_NAME=horses-service + + # Database Configuration + - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/${POSTGRES_DB:-meldestelle} + - SPRING_DATASOURCE_USERNAME=${POSTGRES_USER:-meldestelle} + - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD:-meldestelle} + - SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=${HORSES_DB_POOL_SIZE:-20} + - SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE=${HORSES_DB_MIN_IDLE:-10} + + # Redis Configuration + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PORT=6379 + - SPRING_REDIS_PASSWORD=${REDIS_PASSWORD:-} + - SPRING_REDIS_TIMEOUT=${REDIS_TIMEOUT:-2000ms} + + # Security Configuration + - AUTH_SERVER_URL=http://auth-server:8081 + - JWT_SECRET=${JWT_SECRET:-meldestelle-auth-secret-key-change-in-production} + + # Monitoring & Observability + - MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics,prometheus,configprops + - MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=always + - MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED=true + - MANAGEMENT_TRACING_SAMPLING_PROBABILITY=${TRACING_SAMPLING:-0.1} + - MANAGEMENT_ZIPKIN_TRACING_ENDPOINT=http://zipkin:9411/api/v2/spans + + # Performance Tuning + - JAVA_OPTS=-XX:MaxRAMPercentage=80.0 -XX:+UseG1GC -XX:+UseStringDeduplication + - LOGGING_LEVEL_ROOT=${LOG_LEVEL:-INFO} + - LOGGING_LEVEL_AT_MOCODE_HORSES=${HORSES_LOG_LEVEL:-DEBUG} + networks: + - meldestelle-network + volumes: + - horses-logs:/app/logs + - horses-temp:/app/tmp + healthcheck: + test: ["CMD", "curl", "-fsS", "--max-time", "3", "http://localhost:8085/actuator/health/readiness"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 45s + restart: unless-stopped + deploy: + resources: + limits: + memory: 1024M + cpus: '1.5' + labels: + - "traefik.enable=true" + - "traefik.http.routers.horses-service.rule=Host(`horses.meldestelle.local`)" + - "traefik.http.services.horses-service.loadbalancer.server.port=8085" + - "prometheus.scrape=true" + - "prometheus.port=8085" + - "prometheus.path=/actuator/prometheus" + - "prometheus.service=horses-service" + - "service.name=horses-service" + - "service.version=1.0.0" + - "service.environment=${SPRING_PROFILES_ACTIVE:-docker}" + + # =================================================================== + # Events Service + # =================================================================== + events-service: + build: + context: . + dockerfile: dockerfiles/services/events-service/Dockerfile + args: + GRADLE_VERSION: ${GRADLE_VERSION:-8.14} + JAVA_VERSION: ${JAVA_VERSION:-21} + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-docker} + APP_USER: ${EVENTS_APP_USER:-eventsuser} + APP_UID: ${EVENTS_APP_UID:-1006} + APP_GID: ${EVENTS_APP_GID:-1006} + image: meldestelle/events-service:latest + container_name: meldestelle-events-service + ports: + - "${EVENTS_SERVICE_PORT:-8086}:8086" + - "${EVENTS_DEBUG_PORT:-5006}:5006" # Debug port + depends_on: + postgres: + condition: service_healthy + consul: + condition: service_healthy + redis: + condition: service_healthy + auth-server: + condition: service_healthy + environment: + # Spring Boot Configuration + - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-docker} + - SERVER_PORT=8086 + - MANAGEMENT_SERVER_PORT=8086 + - DEBUG=${DEBUG:-false} + + # Service Discovery + - SPRING_CLOUD_CONSUL_HOST=consul + - SPRING_CLOUD_CONSUL_PORT=8500 + - SPRING_APPLICATION_NAME=events-service + + # Database Configuration + - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/${POSTGRES_DB:-meldestelle} + - SPRING_DATASOURCE_USERNAME=${POSTGRES_USER:-meldestelle} + - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD:-meldestelle} + - SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=${EVENTS_DB_POOL_SIZE:-20} + - SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE=${EVENTS_DB_MIN_IDLE:-10} + + # Redis Configuration + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PORT=6379 + - SPRING_REDIS_PASSWORD=${REDIS_PASSWORD:-} + - SPRING_REDIS_TIMEOUT=${REDIS_TIMEOUT:-2000ms} + + # Security Configuration + - AUTH_SERVER_URL=http://auth-server:8081 + - JWT_SECRET=${JWT_SECRET:-meldestelle-auth-secret-key-change-in-production} + + # Monitoring & Observability + - MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics,prometheus,configprops + - MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=always + - MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED=true + - MANAGEMENT_TRACING_SAMPLING_PROBABILITY=${TRACING_SAMPLING:-0.1} + - MANAGEMENT_ZIPKIN_TRACING_ENDPOINT=http://zipkin:9411/api/v2/spans + + # Performance Tuning + - JAVA_OPTS=-XX:MaxRAMPercentage=80.0 -XX:+UseG1GC -XX:+UseStringDeduplication + - LOGGING_LEVEL_ROOT=${LOG_LEVEL:-INFO} + - LOGGING_LEVEL_AT_MOCODE_EVENTS=${EVENTS_LOG_LEVEL:-DEBUG} + networks: + - meldestelle-network + volumes: + - events-logs:/app/logs + - events-temp:/app/tmp + healthcheck: + test: ["CMD", "curl", "-fsS", "--max-time", "3", "http://localhost:8086/actuator/health/readiness"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 45s + restart: unless-stopped + deploy: + resources: + limits: + memory: 1024M + cpus: '1.5' + labels: + - "traefik.enable=true" + - "traefik.http.routers.events-service.rule=Host(`events.meldestelle.local`)" + - "traefik.http.services.events-service.loadbalancer.server.port=8086" + - "prometheus.scrape=true" + - "prometheus.port=8086" + - "prometheus.path=/actuator/prometheus" + - "prometheus.service=events-service" + - "service.name=events-service" + - "service.version=1.0.0" + - "service.environment=${SPRING_PROFILES_ACTIVE:-docker}" + + # =================================================================== + # Masterdata Service + # =================================================================== + masterdata-service: + build: + context: . + dockerfile: dockerfiles/services/masterdata-service/Dockerfile + args: + GRADLE_VERSION: ${GRADLE_VERSION:-8.14} + JAVA_VERSION: ${JAVA_VERSION:-21} + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-docker} + APP_USER: ${MASTERDATA_APP_USER:-masterdatauser} + APP_UID: ${MASTERDATA_APP_UID:-1007} + APP_GID: ${MASTERDATA_APP_GID:-1007} + image: meldestelle/masterdata-service:latest + container_name: meldestelle-masterdata-service + ports: + - "${MASTERDATA_SERVICE_PORT:-8087}:8087" + - "${MASTERDATA_DEBUG_PORT:-5007}:5007" # Debug port + depends_on: + postgres: + condition: service_healthy + consul: + condition: service_healthy + redis: + condition: service_healthy + auth-server: + condition: service_healthy + environment: + # Spring Boot Configuration + - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-docker} + - SERVER_PORT=8087 + - MANAGEMENT_SERVER_PORT=8087 + - DEBUG=${DEBUG:-false} + + # Service Discovery + - SPRING_CLOUD_CONSUL_HOST=consul + - SPRING_CLOUD_CONSUL_PORT=8500 + - SPRING_APPLICATION_NAME=masterdata-service + + # Database Configuration + - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/${POSTGRES_DB:-meldestelle} + - SPRING_DATASOURCE_USERNAME=${POSTGRES_USER:-meldestelle} + - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD:-meldestelle} + - SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=${MASTERDATA_DB_POOL_SIZE:-15} + - SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE=${MASTERDATA_DB_MIN_IDLE:-8} + + # Redis Configuration + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PORT=6379 + - SPRING_REDIS_PASSWORD=${REDIS_PASSWORD:-} + - SPRING_REDIS_TIMEOUT=${REDIS_TIMEOUT:-2000ms} + + # Security Configuration + - AUTH_SERVER_URL=http://auth-server:8081 + - JWT_SECRET=${JWT_SECRET:-meldestelle-auth-secret-key-change-in-production} + + # Monitoring & Observability + - MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics,prometheus,configprops + - MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=always + - MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED=true + - MANAGEMENT_TRACING_SAMPLING_PROBABILITY=${TRACING_SAMPLING:-0.1} + - MANAGEMENT_ZIPKIN_TRACING_ENDPOINT=http://zipkin:9411/api/v2/spans + + # Performance Tuning + - JAVA_OPTS=-XX:MaxRAMPercentage=80.0 -XX:+UseG1GC -XX:+UseStringDeduplication + - LOGGING_LEVEL_ROOT=${LOG_LEVEL:-INFO} + - LOGGING_LEVEL_AT_MOCODE_MASTERDATA=${MASTERDATA_LOG_LEVEL:-DEBUG} + networks: + - meldestelle-network + volumes: + - masterdata-logs:/app/logs + - masterdata-temp:/app/tmp + healthcheck: + test: ["CMD", "curl", "-fsS", "--max-time", "3", "http://localhost:8087/actuator/health/readiness"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 45s + restart: unless-stopped + deploy: + resources: + limits: + memory: 768M + cpus: '1.0' + labels: + - "traefik.enable=true" + - "traefik.http.routers.masterdata-service.rule=Host(`masterdata.meldestelle.local`)" + - "traefik.http.services.masterdata-service.loadbalancer.server.port=8087" + - "prometheus.scrape=true" + - "prometheus.port=8087" + - "prometheus.path=/actuator/prometheus" + - "prometheus.service=masterdata-service" + - "service.name=masterdata-service" + - "service.version=1.0.0" + - "service.environment=${SPRING_PROFILES_ACTIVE:-docker}" + # =================================================================== # Volumes for Service Data, Logs, and Temporary Files # =================================================================== @@ -430,6 +818,46 @@ volumes: ping-temp: driver: local + # Members Service + members-logs: + driver: local + driver_opts: + type: none + o: bind + device: ./logs/members-service + members-temp: + driver: local + + # Horses Service + horses-logs: + driver: local + driver_opts: + type: none + o: bind + device: ./logs/horses-service + horses-temp: + driver: local + + # Events Service + events-logs: + driver: local + driver_opts: + type: none + o: bind + device: ./logs/events-service + events-temp: + driver: local + + # Masterdata Service + masterdata-logs: + driver: local + driver_opts: + type: none + o: bind + device: ./logs/masterdata-service + masterdata-temp: + driver: local + # =================================================================== # Networks (inherits from main docker-compose.yml) # =================================================================== diff --git a/dockerfiles/services/events-service/Dockerfile b/dockerfiles/services/events-service/Dockerfile new file mode 100644 index 00000000..ad5681f4 --- /dev/null +++ b/dockerfiles/services/events-service/Dockerfile @@ -0,0 +1,163 @@ +# syntax=docker/dockerfile:1.7 + +# =================================================================== +# Dockerfile for Events Service +# Based on Spring Boot Service Template with Events-specific configuration +# =================================================================== + +# Build arguments +ARG GRADLE_VERSION=8.14 +ARG JAVA_VERSION=21 +ARG ALPINE_VERSION=3.19 +ARG SPRING_PROFILES_ACTIVE=docker +ARG SERVICE_PATH=events/events-service +ARG SERVICE_NAME=events-service +ARG SERVICE_PORT=8086 + +# =================================================================== +# Build Stage +# =================================================================== +FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder + +# Re-declare build arguments for this stage +ARG SERVICE_PATH=events/events-service +ARG SERVICE_NAME=events-service +ARG SERVICE_PORT=8086 +ARG SPRING_PROFILES_ACTIVE=docker + +LABEL stage=builder +LABEL maintainer="Meldestelle Development Team" + +WORKDIR /workspace + +# Gradle optimizations +ENV GRADLE_OPTS="-Dorg.gradle.caching=true \ + -Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.configureondemand=true \ + -Xmx2g" + +# Copy build files in optimal order for caching +COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./ +COPY gradle/ gradle/ +COPY platform/ platform/ +COPY core/ core/ +COPY build.gradle.kts ./ + +# Copy events service modules in dependency order +COPY events/events-domain/ events/events-domain/ +COPY events/events-api/ events/events-api/ +COPY events/events-application/ events/events-application/ +COPY events/events-infrastructure/ events/events-infrastructure/ +COPY events/events-service/ events/events-service/ + +# Build events service +RUN echo "Building Events Service..." && \ + ./gradlew :events:events-service:dependencies --no-daemon --info && \ + ./gradlew :events:events-service:bootJar --no-daemon --info -Pspring.profiles.active=${SPRING_PROFILES_ACTIVE} + +# Extract JAR layers for optimized Docker layer caching +WORKDIR /builder +RUN cp /workspace/events/events-service/build/libs/*.jar app.jar && \ + java -Djarmode=layertools -jar app.jar extract + +# =================================================================== +# Runtime Stage +# =================================================================== +FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime + +# Metadata +LABEL service="events-service" \ + version="1.0.0" \ + description="Events Management Service for Austrian Equestrian Federation" \ + maintainer="Meldestelle Development Team" \ + java.version="${JAVA_VERSION}" + +# Build arguments +ARG APP_USER=eventsuser +ARG APP_GROUP=eventsgroup +ARG APP_UID=1006 +ARG APP_GID=1006 + +WORKDIR /app + +# System setup +RUN apk update && \ + apk upgrade && \ + apk add --no-cache curl jq tzdata && \ + rm -rf /var/cache/apk/* + +# Non-root user creation +RUN addgroup -g ${APP_GID} -S ${APP_GROUP} && \ + adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh + +# Directory setup +RUN mkdir -p /app/logs /app/tmp && \ + chown -R ${APP_USER}:${APP_GROUP} /app + +# Re-declare build arguments for runtime stage +ARG SERVICE_PATH=events/events-service +ARG SERVICE_NAME=events-service +ARG SERVICE_PORT=8086 + +# Copy Spring Boot layers in optimal order for Docker layer caching +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/spring-boot-loader/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/snapshot-dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/application/ ./ + +USER ${APP_USER} + +# Expose application port and debug port +EXPOSE ${SERVICE_PORT} 5006 + +# Health check +HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -fsS --max-time 2 http://localhost:${SERVICE_PORT}/actuator/health/readiness || exit 1 + +# JVM configuration optimized for events service +ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 \ + -XX:+UseG1GC \ + -XX:+UseStringDeduplication \ + -XX:+UseContainerSupport \ + -XX:G1HeapRegionSize=16m \ + -XX:+OptimizeStringConcat \ + -XX:+UseCompressedOops \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Duser.timezone=Europe/Vienna \ + -Dmanagement.endpoints.web.exposure.include=health,info,metrics,prometheus" + +# Spring Boot configuration +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ + SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE} \ + SERVER_PORT=${SERVICE_PORT} \ + LOGGING_LEVEL_ROOT=INFO \ + LOGGING_LEVEL_AT_MOCODE_EVENTS=DEBUG + +# Startup command with debug support +ENTRYPOINT ["sh", "-c", "\ + echo 'Starting Events Service on port ${SERVICE_PORT}...'; \ + if [ \"${DEBUG:-false}\" = \"true\" ]; then \ + echo 'Debug mode enabled on port 5006'; \ + exec java $JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5006 org.springframework.boot.loader.launch.JarLauncher; \ + else \ + exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher; \ + fi"] + +# =================================================================== +# Documentation +# =================================================================== +# Build commands: +# docker build -t meldestelle/events-service:latest -f dockerfiles/services/events-service/Dockerfile . +# docker run -p 8086:8086 --name events-service meldestelle/events-service:latest +# +# Key features: +# - Multi-stage build with JAR layer extraction for optimal caching +# - Non-root user execution for security (UID/GID 1006) +# - Optimized JVM settings for containers +# - Comprehensive health checks with events-specific endpoint +# - Debug support on port 5006 +# - Vienna timezone configuration for Austrian operations +# =================================================================== diff --git a/dockerfiles/services/horses-service/Dockerfile b/dockerfiles/services/horses-service/Dockerfile new file mode 100644 index 00000000..a06c7d44 --- /dev/null +++ b/dockerfiles/services/horses-service/Dockerfile @@ -0,0 +1,163 @@ +# syntax=docker/dockerfile:1.7 + +# =================================================================== +# Dockerfile for Horses Service +# Based on Spring Boot Service Template with Horses-specific configuration +# =================================================================== + +# Build arguments +ARG GRADLE_VERSION=8.14 +ARG JAVA_VERSION=21 +ARG ALPINE_VERSION=3.19 +ARG SPRING_PROFILES_ACTIVE=docker +ARG SERVICE_PATH=horses/horses-service +ARG SERVICE_NAME=horses-service +ARG SERVICE_PORT=8085 + +# =================================================================== +# Build Stage +# =================================================================== +FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder + +# Re-declare build arguments for this stage +ARG SERVICE_PATH=horses/horses-service +ARG SERVICE_NAME=horses-service +ARG SERVICE_PORT=8085 +ARG SPRING_PROFILES_ACTIVE=docker + +LABEL stage=builder +LABEL maintainer="Meldestelle Development Team" + +WORKDIR /workspace + +# Gradle optimizations +ENV GRADLE_OPTS="-Dorg.gradle.caching=true \ + -Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.configureondemand=true \ + -Xmx2g" + +# Copy build files in optimal order for caching +COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./ +COPY gradle/ gradle/ +COPY platform/ platform/ +COPY core/ core/ +COPY build.gradle.kts ./ + +# Copy horses service modules in dependency order +COPY horses/horses-domain/ horses/horses-domain/ +COPY horses/horses-api/ horses/horses-api/ +COPY horses/horses-application/ horses/horses-application/ +COPY horses/horses-infrastructure/ horses/horses-infrastructure/ +COPY horses/horses-service/ horses/horses-service/ + +# Build horses service +RUN echo "Building Horses Service..." && \ + ./gradlew :horses:horses-service:dependencies --no-daemon --info && \ + ./gradlew :horses:horses-service:bootJar --no-daemon --info -Pspring.profiles.active=${SPRING_PROFILES_ACTIVE} + +# Extract JAR layers for optimized Docker layer caching +WORKDIR /builder +RUN cp /workspace/horses/horses-service/build/libs/*.jar app.jar && \ + java -Djarmode=layertools -jar app.jar extract + +# =================================================================== +# Runtime Stage +# =================================================================== +FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime + +# Metadata +LABEL service="horses-service" \ + version="1.0.0" \ + description="Horses Management Service for Austrian Equestrian Federation" \ + maintainer="Meldestelle Development Team" \ + java.version="${JAVA_VERSION}" + +# Build arguments +ARG APP_USER=horsesuser +ARG APP_GROUP=horsesgroup +ARG APP_UID=1005 +ARG APP_GID=1005 + +WORKDIR /app + +# System setup +RUN apk update && \ + apk upgrade && \ + apk add --no-cache curl jq tzdata && \ + rm -rf /var/cache/apk/* + +# Non-root user creation +RUN addgroup -g ${APP_GID} -S ${APP_GROUP} && \ + adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh + +# Directory setup +RUN mkdir -p /app/logs /app/tmp && \ + chown -R ${APP_USER}:${APP_GROUP} /app + +# Re-declare build arguments for runtime stage +ARG SERVICE_PATH=horses/horses-service +ARG SERVICE_NAME=horses-service +ARG SERVICE_PORT=8085 + +# Copy Spring Boot layers in optimal order for Docker layer caching +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/spring-boot-loader/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/snapshot-dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/application/ ./ + +USER ${APP_USER} + +# Expose application port and debug port +EXPOSE ${SERVICE_PORT} 5005 + +# Health check +HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -fsS --max-time 2 http://localhost:${SERVICE_PORT}/actuator/health/readiness || exit 1 + +# JVM configuration optimized for horses service +ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 \ + -XX:+UseG1GC \ + -XX:+UseStringDeduplication \ + -XX:+UseContainerSupport \ + -XX:G1HeapRegionSize=16m \ + -XX:+OptimizeStringConcat \ + -XX:+UseCompressedOops \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Duser.timezone=Europe/Vienna \ + -Dmanagement.endpoints.web.exposure.include=health,info,metrics,prometheus" + +# Spring Boot configuration +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ + SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE} \ + SERVER_PORT=${SERVICE_PORT} \ + LOGGING_LEVEL_ROOT=INFO \ + LOGGING_LEVEL_AT_MOCODE_HORSES=DEBUG + +# Startup command with debug support +ENTRYPOINT ["sh", "-c", "\ + echo 'Starting Horses Service on port ${SERVICE_PORT}...'; \ + if [ \"${DEBUG:-false}\" = \"true\" ]; then \ + echo 'Debug mode enabled on port 5005'; \ + exec java $JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 org.springframework.boot.loader.launch.JarLauncher; \ + else \ + exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher; \ + fi"] + +# =================================================================== +# Documentation +# =================================================================== +# Build commands: +# docker build -t meldestelle/horses-service:latest -f dockerfiles/services/horses-service/Dockerfile . +# docker run -p 8085:8085 --name horses-service meldestelle/horses-service:latest +# +# Key features: +# - Multi-stage build with JAR layer extraction for optimal caching +# - Non-root user execution for security (UID/GID 1005) +# - Optimized JVM settings for containers +# - Comprehensive health checks with horses-specific endpoint +# - Debug support on port 5005 +# - Vienna timezone configuration for Austrian operations +# =================================================================== diff --git a/dockerfiles/services/masterdata-service/Dockerfile b/dockerfiles/services/masterdata-service/Dockerfile new file mode 100644 index 00000000..224dccd0 --- /dev/null +++ b/dockerfiles/services/masterdata-service/Dockerfile @@ -0,0 +1,163 @@ +# syntax=docker/dockerfile:1.7 + +# =================================================================== +# Dockerfile for Masterdata Service +# Based on Spring Boot Service Template with Masterdata-specific configuration +# =================================================================== + +# Build arguments +ARG GRADLE_VERSION=8.14 +ARG JAVA_VERSION=21 +ARG ALPINE_VERSION=3.19 +ARG SPRING_PROFILES_ACTIVE=docker +ARG SERVICE_PATH=masterdata/masterdata-service +ARG SERVICE_NAME=masterdata-service +ARG SERVICE_PORT=8087 + +# =================================================================== +# Build Stage +# =================================================================== +FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder + +# Re-declare build arguments for this stage +ARG SERVICE_PATH=masterdata/masterdata-service +ARG SERVICE_NAME=masterdata-service +ARG SERVICE_PORT=8087 +ARG SPRING_PROFILES_ACTIVE=docker + +LABEL stage=builder +LABEL maintainer="Meldestelle Development Team" + +WORKDIR /workspace + +# Gradle optimizations +ENV GRADLE_OPTS="-Dorg.gradle.caching=true \ + -Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.configureondemand=true \ + -Xmx2g" + +# Copy build files in optimal order for caching +COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./ +COPY gradle/ gradle/ +COPY platform/ platform/ +COPY core/ core/ +COPY build.gradle.kts ./ + +# Copy masterdata service modules in dependency order +COPY masterdata/masterdata-domain/ masterdata/masterdata-domain/ +COPY masterdata/masterdata-api/ masterdata/masterdata-api/ +COPY masterdata/masterdata-application/ masterdata/masterdata-application/ +COPY masterdata/masterdata-infrastructure/ masterdata/masterdata-infrastructure/ +COPY masterdata/masterdata-service/ masterdata/masterdata-service/ + +# Build masterdata service +RUN echo "Building Masterdata Service..." && \ + ./gradlew :masterdata:masterdata-service:dependencies --no-daemon --info && \ + ./gradlew :masterdata:masterdata-service:bootJar --no-daemon --info -Pspring.profiles.active=${SPRING_PROFILES_ACTIVE} + +# Extract JAR layers for optimized Docker layer caching +WORKDIR /builder +RUN cp /workspace/masterdata/masterdata-service/build/libs/*.jar app.jar && \ + java -Djarmode=layertools -jar app.jar extract + +# =================================================================== +# Runtime Stage +# =================================================================== +FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime + +# Metadata +LABEL service="masterdata-service" \ + version="1.0.0" \ + description="Masterdata Management Service for Austrian Equestrian Federation" \ + maintainer="Meldestelle Development Team" \ + java.version="${JAVA_VERSION}" + +# Build arguments +ARG APP_USER=masterdatauser +ARG APP_GROUP=masterdatagroup +ARG APP_UID=1007 +ARG APP_GID=1007 + +WORKDIR /app + +# System setup +RUN apk update && \ + apk upgrade && \ + apk add --no-cache curl jq tzdata && \ + rm -rf /var/cache/apk/* + +# Non-root user creation +RUN addgroup -g ${APP_GID} -S ${APP_GROUP} && \ + adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh + +# Directory setup +RUN mkdir -p /app/logs /app/tmp && \ + chown -R ${APP_USER}:${APP_GROUP} /app + +# Re-declare build arguments for runtime stage +ARG SERVICE_PATH=masterdata/masterdata-service +ARG SERVICE_NAME=masterdata-service +ARG SERVICE_PORT=8087 + +# Copy Spring Boot layers in optimal order for Docker layer caching +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/spring-boot-loader/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/snapshot-dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/application/ ./ + +USER ${APP_USER} + +# Expose application port and debug port +EXPOSE ${SERVICE_PORT} 5007 + +# Health check +HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -fsS --max-time 2 http://localhost:${SERVICE_PORT}/actuator/health/readiness || exit 1 + +# JVM configuration optimized for masterdata service +ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 \ + -XX:+UseG1GC \ + -XX:+UseStringDeduplication \ + -XX:+UseContainerSupport \ + -XX:G1HeapRegionSize=16m \ + -XX:+OptimizeStringConcat \ + -XX:+UseCompressedOops \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Duser.timezone=Europe/Vienna \ + -Dmanagement.endpoints.web.exposure.include=health,info,metrics,prometheus" + +# Spring Boot configuration +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ + SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE} \ + SERVER_PORT=${SERVICE_PORT} \ + LOGGING_LEVEL_ROOT=INFO \ + LOGGING_LEVEL_AT_MOCODE_MASTERDATA=DEBUG + +# Startup command with debug support +ENTRYPOINT ["sh", "-c", "\ + echo 'Starting Masterdata Service on port ${SERVICE_PORT}...'; \ + if [ \"${DEBUG:-false}\" = \"true\" ]; then \ + echo 'Debug mode enabled on port 5007'; \ + exec java $JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5007 org.springframework.boot.loader.launch.JarLauncher; \ + else \ + exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher; \ + fi"] + +# =================================================================== +# Documentation +# =================================================================== +# Build commands: +# docker build -t meldestelle/masterdata-service:latest -f dockerfiles/services/masterdata-service/Dockerfile . +# docker run -p 8087:8087 --name masterdata-service meldestelle/masterdata-service:latest +# +# Key features: +# - Multi-stage build with JAR layer extraction for optimal caching +# - Non-root user execution for security (UID/GID 1007) +# - Optimized JVM settings for containers +# - Comprehensive health checks with masterdata-specific endpoint +# - Debug support on port 5007 +# - Vienna timezone configuration for Austrian operations +# =================================================================== diff --git a/dockerfiles/services/members-service/Dockerfile b/dockerfiles/services/members-service/Dockerfile new file mode 100644 index 00000000..ee4da29b --- /dev/null +++ b/dockerfiles/services/members-service/Dockerfile @@ -0,0 +1,163 @@ +# syntax=docker/dockerfile:1.7 + +# =================================================================== +# Dockerfile for Members Service +# Based on Spring Boot Service Template with Members-specific configuration +# =================================================================== + +# Build arguments +ARG GRADLE_VERSION=8.14 +ARG JAVA_VERSION=21 +ARG ALPINE_VERSION=3.19 +ARG SPRING_PROFILES_ACTIVE=docker +ARG SERVICE_PATH=members/members-service +ARG SERVICE_NAME=members-service +ARG SERVICE_PORT=8084 + +# =================================================================== +# Build Stage +# =================================================================== +FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder + +# Re-declare build arguments for this stage +ARG SERVICE_PATH=members/members-service +ARG SERVICE_NAME=members-service +ARG SERVICE_PORT=8084 +ARG SPRING_PROFILES_ACTIVE=docker + +LABEL stage=builder +LABEL maintainer="Meldestelle Development Team" + +WORKDIR /workspace + +# Gradle optimizations +ENV GRADLE_OPTS="-Dorg.gradle.caching=true \ + -Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.configureondemand=true \ + -Xmx2g" + +# Copy build files in optimal order for caching +COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./ +COPY gradle/ gradle/ +COPY platform/ platform/ +COPY core/ core/ +COPY build.gradle.kts ./ + +# Copy members service modules in dependency order +COPY members/members-domain/ members/members-domain/ +COPY members/members-api/ members/members-api/ +COPY members/members-application/ members/members-application/ +COPY members/members-infrastructure/ members/members-infrastructure/ +COPY members/members-service/ members/members-service/ + +# Build members service +RUN echo "Building Members Service..." && \ + ./gradlew :members:members-service:dependencies --no-daemon --info && \ + ./gradlew :members:members-service:bootJar --no-daemon --info -Pspring.profiles.active=${SPRING_PROFILES_ACTIVE} + +# Extract JAR layers for optimized Docker layer caching +WORKDIR /builder +RUN cp /workspace/members/members-service/build/libs/*.jar app.jar && \ + java -Djarmode=layertools -jar app.jar extract + +# =================================================================== +# Runtime Stage +# =================================================================== +FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime + +# Metadata +LABEL service="members-service" \ + version="1.0.0" \ + description="Members Management Service for Austrian Equestrian Federation" \ + maintainer="Meldestelle Development Team" \ + java.version="${JAVA_VERSION}" + +# Build arguments +ARG APP_USER=membersuser +ARG APP_GROUP=membersgroup +ARG APP_UID=1004 +ARG APP_GID=1004 + +WORKDIR /app + +# System setup +RUN apk update && \ + apk upgrade && \ + apk add --no-cache curl jq tzdata && \ + rm -rf /var/cache/apk/* + +# Non-root user creation +RUN addgroup -g ${APP_GID} -S ${APP_GROUP} && \ + adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh + +# Directory setup +RUN mkdir -p /app/logs /app/tmp && \ + chown -R ${APP_USER}:${APP_GROUP} /app + +# Re-declare build arguments for runtime stage +ARG SERVICE_PATH=members/members-service +ARG SERVICE_NAME=members-service +ARG SERVICE_PORT=8084 + +# Copy Spring Boot layers in optimal order for Docker layer caching +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/spring-boot-loader/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/snapshot-dependencies/ ./ +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/application/ ./ + +USER ${APP_USER} + +# Expose application port and debug port +EXPOSE ${SERVICE_PORT} 5004 + +# Health check +HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -fsS --max-time 2 http://localhost:${SERVICE_PORT}/actuator/health/readiness || exit 1 + +# JVM configuration optimized for members service +ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 \ + -XX:+UseG1GC \ + -XX:+UseStringDeduplication \ + -XX:+UseContainerSupport \ + -XX:G1HeapRegionSize=16m \ + -XX:+OptimizeStringConcat \ + -XX:+UseCompressedOops \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Duser.timezone=Europe/Vienna \ + -Dmanagement.endpoints.web.exposure.include=health,info,metrics,prometheus" + +# Spring Boot configuration +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ + SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE} \ + SERVER_PORT=${SERVICE_PORT} \ + LOGGING_LEVEL_ROOT=INFO \ + LOGGING_LEVEL_AT_MOCODE_MEMBERS=DEBUG + +# Startup command with debug support +ENTRYPOINT ["sh", "-c", "\ + echo 'Starting Members Service on port ${SERVICE_PORT}...'; \ + if [ \"${DEBUG:-false}\" = \"true\" ]; then \ + echo 'Debug mode enabled on port 5004'; \ + exec java $JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5004 org.springframework.boot.loader.launch.JarLauncher; \ + else \ + exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher; \ + fi"] + +# =================================================================== +# Documentation +# =================================================================== +# Build commands: +# docker build -t meldestelle/members-service:latest -f dockerfiles/services/members-service/Dockerfile . +# docker run -p 8084:8084 --name members-service meldestelle/members-service:latest +# +# Key features: +# - Multi-stage build with JAR layer extraction for optimal caching +# - Non-root user execution for security (UID/GID 1004) +# - Optimized JVM settings for containers +# - Comprehensive health checks with members-specific endpoint +# - Debug support on port 5004 +# - Vienna timezone configuration for Austrian operations +# =================================================================== diff --git a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt b/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt index 8bd7ba98..00601298 100644 --- a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt +++ b/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt @@ -6,7 +6,6 @@ import java.time.Instant import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.LongAdder -import kotlin.time.toKotlinDuration /** * Comprehensive metrics tracking for Redis Event Store operations. diff --git a/infrastructure/gateway/build.gradle.kts b/infrastructure/gateway/build.gradle.kts index 2e5f0ea6..eeba4e08 100644 --- a/infrastructure/gateway/build.gradle.kts +++ b/infrastructure/gateway/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { // Obwohl bereits im monitoring-client Bundle, wird durch 'implementation' nicht transitiv verfügbar implementation(libs.spring.boot.starter.actuator) - // Logback-Abhängigkeiten für Tests - Versionen werden von Spring Boot BOM verwaltet + // Logback-Abhängigkeiten - Versionen werden von Spring Boot BOM verwaltet implementation("ch.qos.logback:logback-classic") implementation("ch.qos.logback:logback-core") implementation("org.slf4j:slf4j-api") @@ -60,7 +60,10 @@ dependencies { // Stellt alle Test-Abhängigkeiten gebündelt bereit. testImplementation(projects.platform.platformTesting) testImplementation(libs.bundles.testing.jvm) - testImplementation(libs.logback.classic) // SLF4J provider for tests + // Ensure Logback dependencies are available in test classpath + testImplementation("ch.qos.logback:logback-classic") + testImplementation("ch.qos.logback:logback-core") + testImplementation("org.slf4j:slf4j-api") // Redundante Security-Abhängigkeit im Testkontext entfernt (bereits durch platform-testing abgedeckt) } diff --git a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt index c6375c9f..b6608322 100644 --- a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt +++ b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt @@ -10,8 +10,6 @@ import org.springframework.http.server.reactive.ServerHttpResponse import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Mono -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -43,7 +41,7 @@ class CorrelationIdFilter : GlobalFilter, Ordered { .request(mutatedRequest) .build() - // Add a response header after processing + // Response-Header nach der Verarbeitung hinzufügen mutatedExchange.response.headers.add(CORRELATION_ID_HEADER, correlationId) return chain.filter(mutatedExchange) @@ -177,7 +175,7 @@ class RateLimitingFilter : GlobalFilter, Ordered { val limit = determineRateLimit(request, path) val counter = requestCounts.computeIfAbsent(clientIp) { RequestCounter() } - // Reset counter if more than a minute has passed + // Zähler zurücksetzen, wenn mehr als eine Minute vergangen ist val now = System.currentTimeMillis() if (now - counter.lastReset > 60_000) { counter.count = 0 @@ -186,7 +184,7 @@ class RateLimitingFilter : GlobalFilter, Ordered { counter.count++ - // Add rate limit headers + // Rate-Limit-Header hinzufügen response.headers.add(RATE_LIMIT_ENABLED_HEADER, "true") response.headers.add(RATE_LIMIT_LIMIT_HEADER, limit.toString()) response.headers.add(RATE_LIMIT_REMAINING_HEADER, maxOf(0, limit - counter.count).toString()) diff --git a/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt index 424cccfc..e40443fd 100644 --- a/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt +++ b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt @@ -114,6 +114,7 @@ class GatewayFiltersTests { .uri("/test/ratelimit") .header("Authorization", "Bearer test-token") .header("X-User-Role", "ADMIN") + .header("X-User-ID", "admin-test-user") // Required for admin detection security .exchange() .expectStatus().isOk .expectHeader().valueEquals("X-RateLimit-Limit", "500") // ADMIN_LIMIT diff --git a/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/JwtAuthenticationTests.kt b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/JwtAuthenticationTests.kt index 9d82e1b4..44b8cc32 100644 --- a/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/JwtAuthenticationTests.kt +++ b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/JwtAuthenticationTests.kt @@ -93,12 +93,13 @@ class JwtAuthenticationTests { .expectStatus().isUnauthorized .expectBody() .jsonPath("$.error").isEqualTo("UNAUTHORIZED") - .jsonPath("$.message").isEqualTo("Invalid JWT token") + .jsonPath("$.message").isEqualTo("Invalid JWT token format") } @Test fun `should allow access with valid JWT token and inject user headers`() { - val validToken = "valid-jwt-token-with-user-data" + // Create a mock JWT token with proper format (header.payload.signature) and length >50 for USER role + val validToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsInJvbGUiOiJVU0VSIiwiaWF0IjoxNjAwMDAwMDAwfQ.mockSignatureForUserTokenThatIsLongEnoughForValidation" webTestClient.get() .uri("/api/members/protected") @@ -116,7 +117,8 @@ class JwtAuthenticationTests { @Test fun `should extract admin role from JWT token`() { - val adminToken = "valid-jwt-token-with-admin-data" + // Create a mock JWT token with proper format, length >100, and "admin" in the token for ADMIN role + val adminToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbi11c2VyLTEyMyIsInJvbGUiOiJBRE1JTiIsImFkbWluIjp0cnVlLCJpYXQiOjE2MDAwMDAwMDAsImV4cCI6MTYwMDAwMDAwMH0.mockSignatureForAdminTokenThatIsVeryLongEnoughToMeetTheRequiredLengthForAdminValidation" webTestClient.get() .uri("/api/members/protected") @@ -132,7 +134,8 @@ class JwtAuthenticationTests { @Test fun `should extract user role from JWT token`() { - val userToken = "valid-jwt-token-with-user-data" + // Create a mock JWT token with proper format and length >50 for USER role + val userToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQ1NiIsInJvbGUiOiJVU0VSIiwiaWF0IjoxNjAwMDAwMDAwfQ.mockSignatureForUserRoleTokenThatIsLongEnoughForValidation" webTestClient.get() .uri("/api/members/protected") @@ -148,7 +151,8 @@ class JwtAuthenticationTests { @Test fun `should handle POST requests to protected endpoints`() { - val validToken = "valid-jwt-token-for-post" + // Create a mock JWT token with proper format and length >50 for USER role + val validToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTc4OSIsInJvbGUiOiJVU0VSIiwiaWF0IjoxNjAwMDAwMDAwfQ.mockSignatureForPostRequestTokenThatIsLongEnoughForValidation" webTestClient.post() .uri("/api/members/protected") diff --git a/members/members-api/src/main/kotlin/at/mocode/members/api/rest/MemberController.kt b/members/members-api/src/main/kotlin/at/mocode/members/api/rest/MemberController.kt index 1023ffd6..695a5a95 100644 --- a/members/members-api/src/main/kotlin/at/mocode/members/api/rest/MemberController.kt +++ b/members/members-api/src/main/kotlin/at/mocode/members/api/rest/MemberController.kt @@ -18,32 +18,32 @@ import org.springframework.web.bind.annotation.* import io.swagger.v3.oas.annotations.responses.ApiResponse as SwaggerApiResponse /** - * Simple no-op EventPublisher implementation for the controller. + * Einfache No-op EventPublisher Implementierung für den Controller. */ class NoOpEventPublisher : EventPublisher { override suspend fun publishEvent(topic: String, key: String?, event: Any) { - // No-op implementation - events are not published in this simple version + // No-op Implementierung - Events werden in dieser einfachen Version nicht veröffentlicht } override suspend fun publishEvents(topic: String, events: List>) { - // No-op implementation - events are not published in this simple version + // No-op Implementierung - Events werden in dieser einfachen Version nicht veröffentlicht } } /** - * REST API controller for member management operations. + * REST API Controller für Mitgliederverwaltungs-Operationen. * - * This controller provides HTTP endpoints for all member-related operations - * including CRUD operations and member search functionality. + * Dieser Controller stellt HTTP-Endpunkte für alle mitgliederbezogenen Operationen + * zur Verfügung, einschließlich CRUD-Operationen und Mitgliedersuche. */ @RestController @RequestMapping("/api/members") -@Tag(name = "Members", description = "Member management operations") +@Tag(name = "Members", description = "Mitgliederverwaltungs-Operationen") class MemberController( @Qualifier("memberRepositoryImpl") private val memberRepository: MemberRepository ) { - // Simple no-op EventPublisher implementation for now + // Einfache No-op EventPublisher Implementierung vorerst private val eventPublisher = NoOpEventPublisher() private val createMemberUseCase = CreateMemberUseCase(memberRepository, eventPublisher) @@ -55,7 +55,7 @@ class MemberController( private val validateMemberDataUseCase = ValidateMemberDataUseCase(memberRepository) /** - * Helper method to handle common response patterns for use case execution + * Hilfsmethode zur Behandlung gemeinsamer Antwortmuster für Use-Case-Ausführung */ private inline fun handleUseCaseExecution( crossinline operation: suspend () -> ApiResponse, @@ -87,7 +87,7 @@ class MemberController( } /** - * Helper method to handle repository operations with common error handling + * Hilfsmethode zur Behandlung von Repository-Operationen mit gemeinsamer Fehlerbehandlung */ private inline fun handleRepositoryOperation( crossinline operation: () -> T, @@ -103,27 +103,27 @@ class MemberController( } /** - * Get all members with optional filtering + * Alle Mitglieder mit optionaler Filterung abrufen */ @Operation( - summary = "Get all members", - description = "Retrieve all members with optional filtering by active status and search term" + summary = "Alle Mitglieder abrufen", + description = "Abrufen aller Mitglieder mit optionaler Filterung nach Aktivitätsstatus und Suchbegriff" ) @ApiResponses( value = [ - SwaggerApiResponse(responseCode = "200", description = "Successfully retrieved members"), - SwaggerApiResponse(responseCode = "500", description = "Internal server error") + SwaggerApiResponse(responseCode = "200", description = "Mitglieder erfolgreich abgerufen"), + SwaggerApiResponse(responseCode = "500", description = "Interner Serverfehler") ] ) @GetMapping fun getAllMembers( - @Parameter(description = "Filter by active members only", example = "true") + @Parameter(description = "Nur nach aktiven Mitgliedern filtern", example = "true") @RequestParam(defaultValue = "true") activeOnly: Boolean, - @Parameter(description = "Maximum number of results to return", example = "100") + @Parameter(description = "Maximale Anzahl der zurückzugebenden Ergebnisse", example = "100") @RequestParam(defaultValue = "100") limit: Int, - @Parameter(description = "Number of results to skip", example = "0") + @Parameter(description = "Anzahl der zu überspringenden Ergebnisse", example = "0") @RequestParam(defaultValue = "0") offset: Int, - @Parameter(description = "Search term for member names") + @Parameter(description = "Suchbegriff für Mitgliedernamen") @RequestParam(required = false) search: String? ): ResponseEntity>> { return try {