diff --git a/.dockerignore b/.dockerignore index f973e99e..c78e54e3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -70,7 +70,7 @@ Thumbs.db # =================================================================== # Environment and Configuration files # =================================================================== -.env +config/.env .env.local .env.*.local **/.env diff --git a/.env.template b/.env.template deleted file mode 100644 index fde1d32b..00000000 --- a/.env.template +++ /dev/null @@ -1,60 +0,0 @@ -# ==================================================== -# Meldestelle - Environment - Template Configuration -# ==================================================== -# Profil: DEVELOPMENT (Development) - -# --- PROJEKT EINSTELLUNGEN --- -# .env.template - Vorlage für Server/Kollegen -COMPOSE_PROJECT_NAME=meldestelle -# Restart Policy: 'no' für Dev (Fehler sehen), 'always' für Prod -RESTART_POLICY=always - -# --- POSTGRESQL (Datenbank) --- -POSTGRES_USER=pg-user -POSTGRES_PASSWORD= -POSTGRES_DB=meldestelle -# Port Mapping: Host:Container. -# Prod: 127.0.0.1:5432 (nur localhost) oder leer lassen -POSTGRES_PORT=5432:5432 - -# --- REDIS (Cache) --- -# Prod: 127.0.0.1:6379 oder leer lassen -REDIS_PORT=6379:6379 - -# --- KEYCLOAK (Identity Provider) --- -# Admin Login für die Konsole -KC_ADMIN_USER=kc-admin -KC_ADMIN_PASSWORD= -# Hostname (Wichtig für Redirects) -KC_HOSTNAME=localhost -# Port Mapping -KC_PORT=8180:8080 - -# --- PGADMIN (DB GUI) --- -PGADMIN_EMAIL=user@domain.com -PGADMIN_PASSWORD= -PGADMIN_PORT=8888:80 - -# --- GRAFANA (Monitoring GUI) --- -GF_ADMIN_USER=gf-admin -GF_ADMIN_PASSWORD= -GF_PORT=3000:3000 - -# --- PROMETHEUS (Metriken) --- -PROMETHEUS_PORT=9090:9090 - -# --- SERVICE DISCOVERY (Consul) --- -CONSUL_PORT=8500:8500 - -# --- API GATEWAY --- -# Der Port, der nach außen für Clients (Web App) offen ist -GATEWAY_PORT=8081 -# Debug Port für IntelliJ (Remote JVM Debug) -GATEWAY_DEBUG_PORT=5005 - -# --- CLIENT APPLICATIONS --- -# Web-App (Kotlin/JS, kein WASM) -WEB_APP_PORT=4000:4000 -# Desktop-App (VNC/noVNC) -DESKTOP_APP_VNC_PORT=5901:5901 -DESKTOP_APP_NOVNC_PORT=6080:6080 diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml index 7068fce3..034e7f44 100644 --- a/.github/workflows/ci-main.yml +++ b/.github/workflows/ci-main.yml @@ -21,19 +21,25 @@ jobs: name: Docker SSoT Validation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Validate Docker SSoT (compat mode) + if: ${{ hashFiles('scripts/docker-versions-update.sh') != '' && hashFiles('scripts/generate-compose-files.sh') != '' && hashFiles('scripts/validate-docker-consistency.sh') != '' }} run: | bash scripts/docker-versions-update.sh sync bash scripts/generate-compose-files.sh all development bash scripts/validate-docker-consistency.sh all - name: Validate Docker SSoT (envless mode) + if: ${{ hashFiles('scripts/generate-compose-files.sh') != '' && hashFiles('scripts/validate-docker-consistency.sh') != '' }} run: | DOCKER_SSOT_MODE=envless bash scripts/generate-compose-files.sh all development DOCKER_SSOT_MODE=envless bash scripts/validate-docker-consistency.sh all + - name: Lint docker-compose (fallback) + if: ${{ hashFiles('scripts/validate-docker-consistency.sh') == '' }} + run: docker compose -f docker/docker-compose.yaml config + # ======================================== # 2. OpenAPI Validation (nur Lint) # ======================================== @@ -41,10 +47,10 @@ jobs: name: Validate OpenAPI Specs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '20' @@ -53,7 +59,7 @@ jobs: - name: Validate OpenAPI run: | - spectral lint infrastructure/gateway/src/main/resources/openapi/documentation.yaml \ + spectral lint backend/gateway/src/main/resources/openapi/documentation.yaml \ --ruleset .spectral.yaml \ --fail-severity error @@ -64,22 +70,22 @@ jobs: name: Validate Essential Docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Lint Critical Markdown - uses: DavidAnson/markdownlint-cli2-action@v20 + uses: DavidAnson/markdownlint-cli2-action@v21 with: globs: | README.md docs/README.md - docs/architecture/adr/**/*.md + docs/adr/**/*.md docs/how-to/start-local.md - name: Check Links in ADRs uses: gaurav-nelson/github-action-markdown-link-check@v1 with: config-file: '.github/markdown-link-check.json' - folder-path: 'docs/architecture/adr/' + folder-path: 'docs/adr/' use-quiet-mode: 'yes' # ======================================== @@ -91,7 +97,7 @@ jobs: needs: [ docker-ssot, validate-openapi, validate-docs ] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup JDK 21 uses: actions/setup-java@v5 @@ -101,7 +107,7 @@ jobs: cache: gradle - name: Build - run: ./gradlew build -x test + run: ./gradlew staticAnalysis build -x test - name: Test run: ./gradlew test diff --git a/.github/workflows/deploy-proxmox.yml b/.github/workflows/deploy-proxmox.yml index da7888d6..590e4d0a 100644 --- a/.github/workflows/deploy-proxmox.yml +++ b/.github/workflows/deploy-proxmox.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 @@ -48,11 +48,14 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build Client (Test Compilation) - run: ./gradlew :client:compileCommonMainKotlinMetadata --no-daemon + - name: Static Analysis + run: ./gradlew staticAnalysis --no-daemon || true - - name: Run Client Tests - run: ./gradlew :client:test --no-daemon || true # Allow failure for now + - name: Build (all) + run: ./gradlew build -x test --no-daemon + + - name: Test (all) + run: ./gradlew test --no-daemon || true # Allow failure for now # =================================================================== # Deploy to Proxmox (nur bei main branch) @@ -64,7 +67,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup SSH Key uses: webfactory/ssh-agent@v0.8.0 @@ -98,7 +101,7 @@ jobs: # Stop existing services echo "🛑 Stopping existing services..." - docker compose -f docker-compose.yml -f docker-compose.services.yml -f docker-compose.clients.yml down || true + docker compose --env-file docker/.env -f docker/docker-compose.yaml down || true # Clean up old images (optional) echo "🧹 Cleaning up old images..." @@ -106,13 +109,11 @@ jobs: # Build new images echo "🏗️ Building new images..." - docker compose -f docker-compose.yml build - docker compose -f docker-compose.services.yml build - docker compose -f docker-compose.clients.yml build + docker compose --env-file docker/.env -f docker/docker-compose.yaml build # Start infrastructure first echo "🚀 Starting infrastructure..." - docker compose -f docker-compose.yml up -d + docker compose --env-file docker/.env -f docker/docker-compose.yaml up -d # Wait for infrastructure to be ready echo "⏳ Waiting for infrastructure..." @@ -120,7 +121,8 @@ jobs: # Start services echo "🚀 Starting services..." - docker compose -f docker-compose.yml -f docker-compose.services.yml up -d + # Start services (already included in main compose file) + docker compose --env-file docker/.env -f docker/docker-compose.yaml up -d # Wait for services to be ready echo "⏳ Waiting for services..." @@ -128,7 +130,8 @@ jobs: # Start clients echo "🚀 Starting clients..." - docker compose -f docker-compose.yml -f docker-compose.services.yml -f docker-compose.clients.yml up -d + # Start clients (already included in main compose file) + docker compose --env-file docker/.env -f docker/docker-compose.yaml up -d # Health check echo "🏥 Running health checks..." @@ -136,11 +139,11 @@ jobs: # Check service status echo "📊 Service Status:" - docker compose -f docker-compose.yml -f docker-compose.services.yml -f docker-compose.clients.yml ps + docker compose --env-file docker/.env -f docker/docker-compose.yaml ps # Check logs for errors echo "📋 Recent logs:" - docker compose -f docker-compose.yml -f docker-compose.services.yml -f docker-compose.clients.yml logs --tail=50 + docker compose --env-file docker/.env -f docker/docker-compose.yaml logs --tail=50 echo "✅ Deployment completed successfully!" ENDSSH diff --git a/.github/workflows/docs-kdoc-sync.yml b/.github/workflows/docs-kdoc-sync.yml index 76275cd9..7772f1d4 100644 --- a/.github/workflows/docs-kdoc-sync.yml +++ b/.github/workflows/docs-kdoc-sync.yml @@ -26,7 +26,7 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup JDK 21 uses: actions/setup-java@v5 @@ -38,7 +38,8 @@ jobs: uses: gradle/actions/setup-gradle@v5 - name: Build Dokka (GFM) - run: ./gradlew --no-daemon dokkaGfmAll + run: | + ./gradlew --no-daemon dokkaGfmAll || ./gradlew --no-daemon dokkaGfm - name: Python deps for YouTrack sync run: | @@ -46,6 +47,7 @@ jobs: pip install requests pyyaml - name: Sync KDoc Markdown to YouTrack KB + if: ${{ hashFiles('.junie/scripts/youtrack-sync-kb.py') != '' }} env: YT_URL: ${{ secrets.YT_URL }} YT_TOKEN: ${{ secrets.YT_TOKEN }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d66fc62b..e078f3b7 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -93,7 +93,7 @@ jobs: --health-start-period 10s steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 @@ -197,6 +197,9 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Static Analysis + run: ./gradlew staticAnalysis --no-daemon + - name: Run integration tests run: ./gradlew integrationTest --no-daemon --parallel env: diff --git a/.github/workflows/ssot-guard.yml b/.github/workflows/ssot-guard.yml index 2dfcf972..09ffc8e4 100644 --- a/.github/workflows/ssot-guard.yml +++ b/.github/workflows/ssot-guard.yml @@ -1,225 +1,25 @@ -name: Docker SSoT Guard - -permissions: - contents: read - -concurrency: - group: ssot-guard-${{ github.ref }} - cancel-in-progress: true +name: Docker SSoT Guard (Minimal) on: push: - branches: [ main ] paths: - 'docker/**' - - 'dockerfiles/**' - - 'docker-compose*.yml*' - - 'scripts/**' - '.github/workflows/ssot-guard.yml' pull_request: paths: - 'docker/**' - - 'dockerfiles/**' - - 'docker-compose*.yml*' - - 'scripts/**' - '.github/workflows/ssot-guard.yml' jobs: - ssot-guard: + check-compose-config: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 0 + - uses: actions/checkout@v4 + + # FIX: .env aus Example erstellen, damit Variablen da sind + - name: Create .env context + run: cp docker/.env.example docker/.env - - name: Show environment - run: | - bash --version - docker --version || true - compose_ver=$(docker compose version 2>/dev/null || true); echo "docker compose: $compose_ver" - - - name: Sync versions to env files - run: bash scripts/docker-versions-update.sh sync - - - name: Generate docker-compose files (all) - run: bash scripts/generate-compose-files.sh all development - - - name: Validate Docker SSoT consistency - run: bash scripts/validate-docker-consistency.sh all - - - name: Build vs Runtime variable guards - run: | - set -euo pipefail - echo "[Guard] Prüfe, dass keine Laufzeit-Variablen in Build-Args-Dateien vorkommen..." - RUNTIME_KEYS_REGEX='^(GATEWAY_HOST|GATEWAY_PORT|WEB_APP_PORT|NODE_ENV|CONSUL_(HOST|PORT|ENABLED)|DB_(HOST|PORT|NAME|USER|USERNAME|PASSWORD)|POSTGRES_DB|REDIS_PORT|KEYCLOAK_PORT|PING_SERVICE_PORT|MEMBERS_SERVICE_PORT|HORSES_SERVICE_PORT|EVENTS_SERVICE_PORT|MASTERDATA_SERVICE_PORT|AUTH_SERVICE_PORT|MONITORING_SERVER_PORT|PROMETHEUS_PORT|GRAFANA_PORT|JWT_ISSUER|JWT_AUDIENCE)$' - FAIL=0 - shopt -s nullglob - for f in docker/build-args/*.env config/build/*.env; do - [ -f "$f" ] || continue - BAD=$(grep -E '^[A-Z0-9_]+=' "$f" | cut -d= -f1 | grep -E "$RUNTIME_KEYS_REGEX" || true) - if [ -n "$BAD" ]; then - echo "Fehler: Laufzeit-Variablen in Build-Args Datei $f gefunden:"; echo "$BAD"; FAIL=1 - fi - done - shopt -u nullglob - if [ $FAIL -ne 0 ]; then - echo "Build vs Runtime Trennung verletzt."; exit 1; fi - - echo "[Guard] Prüfe, dass keine Build-/Versions-Variablen in Runtime-Env vorkommen..." - BUILD_KEYS_REGEX='^(GRADLE_VERSION|JAVA_VERSION|VERSION|APP_VERSION|[A-Z]+_IMAGE_TAG)$' - shopt -s nullglob - for f in config/env/.env .env.template; do - [ -f "$f" ] || continue - BAD=$(grep -E '^[A-Z0-9_]+=' "$f" | cut -d= -f1 | grep -E "$BUILD_KEYS_REGEX" || true) - if [ -n "$BAD" ]; then - echo "Fehler: Build-/Versions-Variablen in Runtime-Env $f gefunden:"; echo "$BAD"; FAIL=1 - fi - done - shopt -u nullglob - if [ $FAIL -ne 0 ]; then - echo "Build-/Runtime-Mischung in Runtime-Env."; exit 1; fi - - - name: Check versions.toml vs global.env consistency - run: | - set -euo pipefail - TOML=docker/versions.toml - GLOBAL=docker/build-args/global.env - [ -f "$TOML" ] || { echo "Missing $TOML"; exit 1; } - [ -f "$GLOBAL" ] || { echo "Missing $GLOBAL"; exit 1; } - - get_toml_ver(){ awk -F'=' -v key="$1" '/^\[versions\]/{in_vers=1; next} /^\[/{in_vers=0} in_vers && gsub(/^[ \t]+|[ \t]+$/,"",$1) && $1==key {gsub(/[ "\t]/,"",$2); print $2; exit}' "$TOML"; } - mapfile -t checks < <(printf "%s\n" \ - "GRADLE_VERSION:versions.gradle" \ - "JAVA_VERSION:versions.java" \ - "VERSION:versions.app-version" \ - "PROMETHEUS_IMAGE_TAG:versions.prometheus" \ - "GRAFANA_IMAGE_TAG:versions.grafana" \ - "KEYCLOAK_IMAGE_TAG:versions.keycloak" \ - "POSTGRES_IMAGE_TAG:versions.postgres" \ - "REDIS_IMAGE_TAG:versions.redis" \ - "CONSUL_IMAGE_TAG:versions.consul" \ - "ZOOKEEPER_IMAGE_TAG:versions.zookeeper" \ - "KAFKA_IMAGE_TAG:versions.kafka") - - FAIL=0 - for entry in "${checks[@]}"; do - var=${entry%%:*}; path=${entry##*:} - key=${path#*.} - case "$var" in - GRADLE_VERSION) expected=$(get_toml_ver gradle) ;; - JAVA_VERSION) expected=$(get_toml_ver java) ;; - VERSION) expected=$(get_toml_ver app-version) ;; - PROMETHEUS_IMAGE_TAG) expected=$(get_toml_ver prometheus) ;; - GRAFANA_IMAGE_TAG) expected=$(get_toml_ver grafana) ;; - KEYCLOAK_IMAGE_TAG) expected=$(get_toml_ver keycloak) ;; - POSTGRES_IMAGE_TAG) expected=$(get_toml_ver postgres) ;; - REDIS_IMAGE_TAG) expected=$(get_toml_ver redis) ;; - CONSUL_IMAGE_TAG) expected=$(get_toml_ver consul) ;; - ZOOKEEPER_IMAGE_TAG) expected=$(get_toml_ver zookeeper) ;; - KAFKA_IMAGE_TAG) expected=$(get_toml_ver kafka) ;; - esac - actual=$(grep -E "^${var}=" "$GLOBAL" | head -n1 | cut -d= -f2-) - if [ -z "$actual" ] || [ "$actual" != "$expected" ]; then - echo "Versions-Drift: $var global.env='$actual' != versions.toml('$expected')"; FAIL=1 - fi - done - if [ $FAIL -ne 0 ]; then - echo "Versions SSoT-Drift erkannt."; exit 1; fi - - - name: Check drift of generated artifacts (ignore timestamps) - run: | - set -euo pipefail - # Gather modified files after sync+generate - CHANGED=$(git diff --name-only) - if [ -z "$CHANGED" ]; then - echo "No drift detected." - exit 0 - fi - echo "Changed files:" $CHANGED - fail=0 - for f in $CHANGED; do - # Inspect actual content changes but ignore volatile timestamp/comment lines - # Ignore lines starting with + or - that are exactly the timestamp markers we generate - DIFF_FILTERED=$(git diff --unified=0 -- "$f" \ - | awk 'BEGIN{show=0} { \ - if ($0 ~ /^\+\+\+|^---|^@@/) { next } \ - if ($0 ~ /^[+-]# (Generated:|Last updated:)/) { next } \ - if ($0 ~ /^[+-]#\s*Generated from docker\/versions.toml/) { next } \ - if ($0 ~ /^[+-]#\s*Environment:/) { next } \ - if ($0 ~ /^[+-]#\s*Source:/) { next } \ - if ($0 ~ /^[+-]$/) { next } \ - if ($0 ~ /^[+-]/) { print $0 } \ - }') - if [ -n "$DIFF_FILTERED" ]; then - echo "SSoT drift detected in $f:"; - echo "$DIFF_FILTERED"; - fail=1; - fi - done - if [ $fail -ne 0 ]; then - echo "\nERROR: Generated artifacts differ from repository (beyond timestamps)." - echo "Run:" - echo " bash scripts/docker-versions-update.sh sync" - echo " bash scripts/generate-compose-files.sh all" - echo "and commit the changes." - exit 1 - fi - echo "No SSoT drift (ignoring timestamps)." - - - ssot-guard-envless: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Show environment - run: | - bash --version - docker --version || true - compose_ver=$(docker compose version 2>/dev/null || true); echo "docker compose: $compose_ver" - - - name: Generate docker-compose files (all) - run: bash scripts/generate-compose-files.sh all development - - - name: Validate Docker SSoT consistency (envless) - run: DOCKER_SSOT_MODE=envless bash scripts/validate-docker-consistency.sh all - - - name: Check drift of generated artifacts (ignore timestamps) - run: | - set -euo pipefail - CHANGED=$(git diff --name-only) - if [ -z "$CHANGED" ]; then - echo "No drift detected." - exit 0 - fi - echo "Changed files:" $CHANGED - fail=0 - for f in $CHANGED; do - DIFF_FILTERED=$(git diff --unified=0 -- "$f" \ - | awk 'BEGIN{show=0} { \ - if ($0 ~ /^\+\+\+|^---|^@@/) { next } \ - if ($0 ~ /^[+-]# (Generated:|Last updated:)/) { next } \ - if ($0 ~ /^[+-]#\s*Generated from docker\/versions.toml/) { next } \ - if ($0 ~ /^[+-]#\s*Environment:/) { next } \ - if ($0 ~ /^[+-]#\s*Source:/) { next } \ - if ($0 ~ /^[+-]$/) { next } \ - if ($0 ~ /^[+-]/) { print $0 } \ - }') - if [ -n "$DIFF_FILTERED" ]; then - echo "SSoT drift detected in $f:"; - echo "$DIFF_FILTERED"; - fail=1; - fi - done - if [ $fail -ne 0 ]; then - echo "\nERROR: Generated artifacts differ from repository (beyond timestamps)." - echo "Run:" - echo " DOCKER_SSOT_MODE=envless bash scripts/generate-compose-files.sh all" - echo "and commit the changes." - exit 1 - fi - echo "No SSoT drift (ignoring timestamps)." + # FIX: .yaml Extension nutzen (SSoT) + - name: Validate Docker Compose Config + run: docker compose --env-file docker/.env -f docker/docker-compose.yaml config diff --git a/.github/workflows/youtrack-sync.yml b/.github/workflows/youtrack-sync.yml index 5f88806a..e4507b4f 100644 --- a/.github/workflows/youtrack-sync.yml +++ b/.github/workflows/youtrack-sync.yml @@ -23,7 +23,7 @@ jobs: steps: # WICHTIG: Checkout ist notwendig, damit "git log" funktioniert - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 # Notwendig, um die Commit-Historie für "git log" zu laden diff --git a/.gitignore b/.gitignore index 5eaa1d53..e4d2cb84 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ logs/ # Kotlin/Java *.class -.env +config/.env # Generated diagrams build/diagrams/ diff --git a/.junie/guidelines/_archived/docker-guideline-v3.0.1-archived-2025-09-15.md b/.junie/guidelines/_archived/docker-guideline-v3.0.1-archived-2025-09-15.md index 00444547..63ca23cc 100644 --- a/.junie/guidelines/_archived/docker-guideline-v3.0.1-archived-2025-09-15.md +++ b/.junie/guidelines/_archived/docker-guideline-v3.0.1-archived-2025-09-15.md @@ -108,7 +108,7 @@ graph TB ```bash # Massive Redundanz über 100+ Dateien verteilt: gradle.properties: services.port.ping=8082 -docker-compose.services.yml: SERVER_PORT: ${PING_SERVICE_PORT:-8082} +docker-compose.services.yaml: SERVER_PORT: ${PING_SERVICE_PORT:-8082} dockerfiles/services/ping: EXPOSE 8082 scripts/test/integration: ping-service:8082 config/monitoring/prometheus: - targets: ['ping-service:8082'] @@ -258,7 +258,7 @@ ping-service = 8092 # Geändert von 8082 # Ergebnis: 38+ Dateien automatisch aktualisiert: # ✓ gradle.properties: services.port.ping=8092 -# ✓ docker-compose.services.yml: SERVER_PORT: ${PING_SERVICE_PORT:-8092} +# ✓ docker-compose.services.yaml: SERVER_PORT: ${PING_SERVICE_PORT:-8092} # ✓ dockerfiles/services/ping-service/Dockerfile: EXPOSE 8092 # ✓ scripts/test/integration-test.sh: ping-service:8092 # ✓ config/monitoring/prometheus.dev.yml: - targets: ['ping-service:8092'] @@ -355,7 +355,7 @@ future-service = 8099 # Nach Synchronisation automatisch in: # - gradle.properties -# - docker-compose.yml +# - docker-compose.yaml # - Monitoring-Konfiguration # - Test-Scripts # - Environment-Files @@ -387,7 +387,7 @@ future-service = 8099 ```bash # ❌ FALSCH - Nie mehr manuelle Port-Änderungen -vim docker-compose.yml # Änderungen gehen verloren! +vim docker-compose.yaml # Änderungen gehen verloren! # ✅ RICHTIG - Zentrale Änderung + Synchronisation vim config/central.toml @@ -914,7 +914,7 @@ test-containers = true ```bash # Development mit Hot-Reload und Debug export DOCKER_ENVIRONMENT=development -docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d +docker-compose -f docker-compose.yaml -f docker-compose.dev.yml up -d ``` #### Production Environment @@ -1162,9 +1162,9 @@ jobs: }, "docker.composeCommand": "docker-compose", "docker.composeFiles": [ - "docker-compose.yml", - "docker-compose.services.yml", - "docker-compose.clients.yml" + "docker-compose.yaml", + "docker-compose.services.yaml", + "docker-compose.clients.yaml" ] } ``` @@ -1713,20 +1713,20 @@ Unsere Compose-Dateien sind modular organisiert für verschiedene Einsatzszenari ```bash # Alle Services einschließlich Clients docker-compose \ - -f docker-compose.yml \ - -f docker-compose.services.yml \ - -f docker-compose.clients.yml \ + -f docker-compose.yaml \ + -f docker-compose.services.yaml \ + -f docker-compose.clients.yaml \ up -d # Nur Infrastructure für Backend-Entwicklung -docker-compose -f docker-compose.yml up -d postgres redis kafka consul zipkin +docker-compose -f docker-compose.yaml up -d postgres redis kafka consul zipkin # Mit Debug-Unterstützung für Service-Entwicklung DEBUG=true SPRING_PROFILES_ACTIVE=docker \ -docker-compose -f docker-compose.yml -f docker-compose.services.yml up -d +docker-compose -f docker-compose.yaml -f docker-compose.services.yaml up -d # Mit Live-Reload für Frontend-Entwicklung -docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d +docker-compose -f docker-compose.yaml -f docker-compose.override.yml up -d ``` #### 🔧 Erweiterte Umgebungskonfiguration @@ -1734,7 +1734,7 @@ docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d **Beispiel für Auth-Server Konfiguration:** ```yaml -# Erweiterte Environment-Variablen aus docker-compose.services.yml +# Erweiterte Environment-Variablen aus docker-compose.services.yaml auth-server: environment: # Spring Boot Configuration @@ -1788,7 +1788,7 @@ auth-server: # Production - Optimiert und sicher docker-compose \ -f docker-compose.prod.yml \ - -f docker-compose.services.yml \ + -f docker-compose.services.yaml \ up -d # Mit spezifischen Environment-Variablen @@ -1801,11 +1801,11 @@ docker-compose -f docker-compose.prod.yml up -d ```bash # Nur notwendige Services für Tests -docker-compose -f docker-compose.yml up -d postgres redis +docker-compose -f docker-compose.yaml up -d postgres redis ./gradlew test # End-to-End Tests -docker-compose -f docker-compose.yml -f docker-compose.services.yml up -d +docker-compose -f docker-compose.yaml -f docker-compose.services.yaml up -d ./gradlew :client:web-app:jsTest ``` @@ -1909,7 +1909,7 @@ services: ```bash # Service im Debug-Modus starten -docker-compose -f docker-compose.yml up -d ping-service +docker-compose -f docker-compose.yaml up -d ping-service docker-compose exec ping-service sh # Logs in Echtzeit verfolgen @@ -2080,7 +2080,7 @@ labels: ```bash # Centralized logging mit ELK Stack (optional) -docker-compose -f docker-compose.yml -f docker-compose.logging.yml up -d +docker-compose -f docker-compose.yaml -f docker-compose.logging.yml up -d # Log-Parsing für strukturierte Logs docker-compose logs --follow --tail=100 api-gateway | jq -r '.message' diff --git a/.junie/guidelines/technology-guides/docker/docker-development.md b/.junie/guidelines/technology-guides/docker/docker-development.md index d8bcf254..33715817 100644 --- a/.junie/guidelines/technology-guides/docker/docker-development.md +++ b/.junie/guidelines/technology-guides/docker/docker-development.md @@ -53,7 +53,7 @@ make full-logs # Alle Logs in Echtzeit Befehle für die lokale Entwicklungsumgebung: ```bash -make dev-up # Startet Entwicklungsumgebung (docker-compose.yml) +make dev-up # Startet Entwicklungsumgebung (docker-compose.yaml) make dev-down # Stoppt Entwicklungsumgebung make dev-restart # Neustart Entwicklungsumgebung make dev-logs # Zeigt alle Development-Logs @@ -634,16 +634,16 @@ Das Projekt verwendet mehrere Compose-Files: ```bash # Nur Infrastruktur -docker compose -f docker-compose.yml up -d +docker compose -f docker-compose.yaml up -d # Infrastruktur + Services -docker compose -f docker-compose.yml -f docker-compose.services.yml up -d +docker compose -f docker-compose.yaml -f docker-compose.services.yaml up -d # Infrastruktur + Clients -docker compose -f docker-compose.yml -f docker-compose.clients.yml up -d +docker compose -f docker-compose.yaml -f docker-compose.clients.yaml up -d # Alles -docker compose -f docker-compose.yml -f docker-compose.services.yml -f docker-compose.clients.yml up -d +docker compose -f docker-compose.yaml -f docker-compose.services.yaml -f docker-compose.clients.yaml up -d # ⚠️ Tipp: Verwende stattdessen die Makefile-Befehle! ``` diff --git a/.junie/guidelines/technology-guides/docker/docker-monitoring.md b/.junie/guidelines/technology-guides/docker/docker-monitoring.md index ad09609e..71fc9e68 100644 --- a/.junie/guidelines/technology-guides/docker/docker-monitoring.md +++ b/.junie/guidelines/technology-guides/docker/docker-monitoring.md @@ -58,7 +58,7 @@ labels: ```bash # Centralized logging mit ELK Stack (optional) -docker-compose -f docker-compose.yml -f docker-compose.logging.yml up -d +docker-compose -f docker-compose.yaml -f docker-compose.logging.yml up -d # Log-Parsing für strukturierte Logs docker-compose logs --follow --tail=100 api-gateway | jq -r '.message' diff --git a/.junie/guidelines/technology-guides/docker/docker-production.md b/.junie/guidelines/technology-guides/docker/docker-production.md index 16ffc145..1ddc2bf0 100644 --- a/.junie/guidelines/technology-guides/docker/docker-production.md +++ b/.junie/guidelines/technology-guides/docker/docker-production.md @@ -186,7 +186,7 @@ source .env.production certbot certificates # 3. Services mit Production-Konfiguration starten -docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d +docker-compose -f docker-compose.yaml -f docker-compose.prod.yml up -d # 4. Health-Checks durchführen curl -f https://api.meldestelle.at/actuator/health diff --git a/.junie/guidelines/technology-guides/web-app-guideline.md b/.junie/guidelines/technology-guides/web-app-guideline.md index 0c6271c4..1fa7f83c 100644 --- a/.junie/guidelines/technology-guides/web-app-guideline.md +++ b/.junie/guidelines/technology-guides/web-app-guideline.md @@ -146,8 +146,8 @@ Das Docker-Setup ist spezifisch für die Web-Entwicklung konfiguriert (wie in `R ```shell script # Startet die Web-App mit Hot-Reload -docker-compose -f docker-compose.yml \ --f docker-compose.clients.yml up -d web-app +docker-compose -f docker-compose.yaml \ +-f docker-compose.clients.yaml up -d web-app ``` Der Dienst ist dann unter dem in der `docker-compose.clients.yml` konfigurierten Port (z.B. Port `3000`) erreichbar. diff --git a/README.md b/README.md index 9f619549..0a32728d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Meldestelle + > Modulares System für Pferdesportveranstaltungen mit Domain-Driven Design [![CI Pipeline](https://github.com/StefanMoCoAt/meldestelle/workflows/CI%20-%20Main%20Pipeline/badge.svg)](https://github.com/StefanMoCoAt/meldestelle/actions) @@ -26,7 +27,7 @@ cp -n .env.template config/env/.env 2>/dev/null || true # DOCKER_SSOT_MODE=envless bash scripts/generate-compose-files.sh all development # 4) Infrastruktur starten -docker compose -f docker-compose.yml up -d +docker compose -f docker-compose.yaml up -d # 5) Services starten (Beispiel) ./gradlew :members:members-service:bootRun @@ -259,10 +260,10 @@ bash scripts/generate-compose-files.sh all development && \ ```bash # Nur Infrastruktur -# Wenn eine handgeschriebene docker-compose.yml existiert: -docker compose -f docker-compose.yml up -d +# Wenn eine handgeschriebene docker-compose.yaml existiert: +docker compose -f docker-compose.yaml up -d # Falls Compose-Files generiert werden: -docker compose -f docker-compose.services.yml up -d +docker compose -f docker-compose.services.yaml up -d # Services via Gradle a) Einzeldienst @@ -304,7 +305,7 @@ b) Falls unterstützt: alle (oder Aggregator) #### Nur Infrastruktur (Postgres, Redis, Kafka, Keycloak) ```bash - docker compose -f docker-compose.yml up -d + docker compose -f docker-compose.yaml up -d ``` #### Services über Gradle diff --git a/_backup_chaos/.env.template b/_backup_chaos/.env.template deleted file mode 100644 index a5ccc91a..00000000 --- a/_backup_chaos/.env.template +++ /dev/null @@ -1,195 +0,0 @@ -# =================================================================== -# Environment Configuration Template - Meldestelle Project -# =================================================================== -# Copy this file to config/env/.env and customize the values for your environment -# Security Note: Never commit .env files containing production secrets! -# =================================================================== - -# =================================================================== -# Runtime Configuration (Single Source for runtime values) -# Hinweis: Build-/Image-Versionen werden ausschließlich in docker/versions.toml -# und docker/build-args/global.env gepflegt. Keine Build-/Versionseinträge hier. -# =================================================================== - -# Anwendung -APP_NAME=Meldestelle - -# Profile -SPRING_PROFILES_ACTIVE=docker,keycloak - -# =================================================================== -# Infrastructure Services - Port Configuration -# =================================================================== -# Database -POSTGRES_DB=meldestelle -# Note: Username and password are now managed via Docker secrets - -# Redis Cache -REDIS_PORT=6379 - -# Keycloak Authentication -KEYCLOAK_PORT=8180 -KEYCLOAK_LOG_LEVEL=INFO - -# Service Discovery -CONSUL_HOST=consul -CONSUL_PORT=8500 -CONSUL_ENABLED=true - -# Messaging -ZOOKEEPER_CLIENT_PORT=2181 -KAFKA_PORT=9092 -KAFKA_BROKER_ID=1 -KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 - -# Monitoring -PROMETHEUS_PORT=9090 -GRAFANA_PORT=3000 - -# =================================================================== -# Application Services - Port Configuration -# =================================================================== -# API Gateway -GATEWAY_HOST=api-gateway -GATEWAY_PORT=8081 - -# Microservices -PING_SERVICE_PORT=8082 -MEMBERS_SERVICE_PORT=8083 -HORSES_SERVICE_PORT=8084 -EVENTS_SERVICE_PORT=8085 -MASTERDATA_SERVICE_PORT=8086 -AUTH_SERVICE_PORT=8087 -MONITORING_SERVER_PORT=8088 - -# =================================================================== -# Client Applications - Port Configuration -# =================================================================== -# Web Application -WEB_APP_PORT=4000 -WEB_APP_DOMAIN=localhost -NODE_ENV=production - -# Nginx Configuration -NGINX_WORKER_PROCESSES=auto -NGINX_WORKER_CONNECTIONS=1024 - -# Desktop Application -DESKTOP_VNC_WEB_PORT=6080 -DESKTOP_VNC_PORT=5901 -DESKTOP_APP_DOMAIN=localhost - -# =================================================================== -# Security Configuration -# =================================================================== -# JWT Configuration -JWT_ISSUER=meldestelle-auth-server -JWT_AUDIENCE=meldestelle-services - -# Note: JWT_SECRET is now managed via Docker secrets -# Generate with: openssl rand -hex 32 - -# Keycloak Configuration -KEYCLOAK_REALM=meldestelle -KEYCLOAK_CLIENT_ID=api-gateway - -# Note: All passwords and secrets are now managed via Docker secrets -# Run: ./docker/secrets/setup-secrets.sh to generate secure secrets - -# =================================================================== -# Data Storage Configuration -# =================================================================== -# Data directory for persistent volumes -# Default: ./data (relative to project root) -# Production: /var/lib/meldestelle or dedicated mount point -DATA_PATH=./data - -# Volume configuration -# These directories will be created under DATA_PATH: -# - postgres/ (PostgreSQL data) -# - redis/ (Redis data) -# - prometheus/ (Prometheus metrics) -# - grafana/ (Grafana dashboards) -# - keycloak/ (Keycloak data) -# - consul/ (Consul data) -# - monitoring/ (Custom monitoring data) -# - desktop-app/ (Desktop application data) - -# =================================================================== -# Development and Testing -# =================================================================== -# Enable debug mode for Java applications -DEBUG=false - -# Enable Wasm compilation for client applications -ENABLE_WASM=false - -# =================================================================== -# Production Deployment Settings -# =================================================================== -# Container resource limits (configured in docker-compose files) -# These are documented here for reference: - -# Infrastructure Services Resource Limits: -# - postgres: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - redis: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - keycloak: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 1GB RAM) -# - consul: 1 CPU, 512MB RAM (reserved: 0.25 CPU, 128MB RAM) -# - kafka: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - zookeeper: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - prometheus: 1 CPU, 2GB RAM (reserved: 0.25 CPU, 512MB RAM) -# - grafana: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - api-gateway: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 1GB RAM) - -# Microservices Resource Limits: -# - ping-service: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - members-service: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - horses-service: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - events-service: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - masterdata-service: 1.5 CPU, 1.5GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - auth-server: 1.5 CPU, 1.5GB RAM (reserved: 0.5 CPU, 512MB RAM) - -# Client Applications Resource Limits: -# - web-app: 1 CPU, 512MB RAM (reserved: 0.25 CPU, 128MB RAM) -# - desktop-app: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - monitoring-server: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) - -# =================================================================== -# Security Notes -# =================================================================== -# 1. All passwords and secrets are managed via Docker secrets -# 2. Run ./docker/secrets/setup-secrets.sh to generate secure credentials -# 3. Containers run as non-root users where possible -# 4. Security options: no-new-privileges enabled for all services -# 5. Networks are isolated with custom subnet (172.20.0.0/16) -# 6. Volumes have proper permissions and are mounted read-only where appropriate -# 7. Health checks are configured for all services -# 8. Resource limits prevent resource exhaustion attacks - -# =================================================================== -# Usage Instructions -# =================================================================== -# 1. Copy this file: mkdir -p config/env && cp .env.template config/env/.env -# 2. Customize values in config/env/.env for your environment -# 3. Generate secrets: ./docker/secrets/setup-secrets.sh --all -# 4. Create data directories: mkdir -p ./data/{postgres,redis,prometheus,grafana,keycloak,consul} -# 5. Deploy infrastructure: docker compose -f docker-compose.yml up -d -# 6. Deploy services: docker compose -f docker-compose.services.yml up -d -# 7. Deploy clients: docker compose -f docker-compose.clients.yml up -d - -# =================================================================== -# Monitoring and Logging -# =================================================================== -# Access URLs (when running with default ports): -# - Grafana Dashboard: http://localhost:3000 (admin credentials in secrets) -# - Prometheus Metrics: http://localhost:9090 -# - Consul UI: http://localhost:8500 -# - Keycloak Admin: http://localhost:8180/admin (admin credentials in secrets) -# - API Gateway: http://localhost:8081 -# - Web Application: http://localhost:4000 -# - Desktop VNC: http://localhost:6080 - -# Log locations (inside containers): -# - Application logs: /app/logs/ -# - Nginx logs: /var/log/nginx/ -# - System logs: journalctl -u docker diff --git a/_backup_chaos/.env~origin_main b/_backup_chaos/.env~origin_main deleted file mode 100644 index 9ea6101f..00000000 --- a/_backup_chaos/.env~origin_main +++ /dev/null @@ -1,207 +0,0 @@ -# =================================================================== -# Environment Configuration Template - Meldestelle Project -# =================================================================== -# Copy this file to .env and customize the values for your environment -# Security Note: Never commit .env files containing production secrets! -# =================================================================== - -# =================================================================== -# Build Configuration -# =================================================================== -# Docker image versions -DOCKER_GRADLE_VERSION=9.1.0 -DOCKER_JAVA_VERSION=21 -DOCKER_KEYCLOAK_VERSION=26.4.0 -DOCKER_PROMETHEUS_VERSION=v2.54.1 -DOCKER_GRAFANA_VERSION=11.3.0 - -# Application version -DOCKER_APP_VERSION=1.0.0 -APP_VERSION=1.0.0 -APP_NAME=Meldestelle - -# Build metadata -BUILD_DATE=2025-11-11 -# BUILD_DATE will be auto-generated if not set - -# Spring profiles for services -SPRING_PROFILES_ACTIVE=docker,keycloak -DOCKER_SPRING_PROFILES_DEFAULT=default -DOCKER_SPRING_PROFILES_DOCKER=docker - -# =================================================================== -# Infrastructure Services - Port Configuration -# =================================================================== -# Database -POSTGRES_DB=meldestelle -# Note: Username and password are now managed via Docker secrets - -# Redis Cache -REDIS_PORT=6379 - -# Keycloak Authentication -KEYCLOAK_PORT=8180 -KEYCLOAK_LOG_LEVEL=INFO - -# Service Discovery -CONSUL_HOST=consul -CONSUL_PORT=8500 -CONSUL_ENABLED=true - -# Messaging -ZOOKEEPER_CLIENT_PORT=2181 -KAFKA_PORT=9092 -KAFKA_BROKER_ID=1 -KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 - -# Monitoring -PROMETHEUS_PORT=9090 -GRAFANA_PORT=3000 - -# =================================================================== -# Application Services - Port Configuration -# =================================================================== -# API Gateway -GATEWAY_HOST=api-gateway -GATEWAY_PORT=8081 - -# Microservices -PING_SERVICE_PORT=8082 -MEMBERS_SERVICE_PORT=8083 -HORSES_SERVICE_PORT=8084 -EVENTS_SERVICE_PORT=8085 -MASTERDATA_SERVICE_PORT=8086 -AUTH_SERVICE_PORT=8087 -MONITORING_SERVER_PORT=8088 - -# =================================================================== -# Client Applications - Port Configuration -# =================================================================== -# Web Application -WEB_APP_PORT=4000 -WEB_APP_DOMAIN=localhost -NODE_ENV=production - -# Nginx Configuration -NGINX_WORKER_PROCESSES=auto -NGINX_WORKER_CONNECTIONS=1024 - -# Desktop Application -DESKTOP_VNC_WEB_PORT=6080 -DESKTOP_VNC_PORT=5901 -DESKTOP_APP_DOMAIN=localhost - -# =================================================================== -# Security Configuration -# =================================================================== -# JWT Configuration -JWT_ISSUER=meldestelle-auth-server -JWT_AUDIENCE=meldestelle-services - -# Note: JWT_SECRET is now managed via Docker secrets -# Generate with: openssl rand -hex 32 - -# Keycloak Configuration -KEYCLOAK_REALM=meldestelle -KEYCLOAK_CLIENT_ID=api-gateway - -# Note: All passwords and secrets are now managed via Docker secrets -# Run: ./docker/secrets/setup-secrets.sh to generate secure secrets - -# =================================================================== -# Data Storage Configuration -# =================================================================== -# Data directory for persistent volumes -# Default: ./data (relative to project root) -# Production: /var/lib/meldestelle or dedicated mount point -DATA_PATH=./data - -# Volume configuration -# These directories will be created under DATA_PATH: -# - postgres/ (PostgreSQL data) -# - redis/ (Redis data) -# - prometheus/ (Prometheus metrics) -# - grafana/ (Grafana dashboards) -# - keycloak/ (Keycloak data) -# - consul/ (Consul data) -# - monitoring/ (Custom monitoring data) -# - desktop-app/ (Desktop application data) - -# =================================================================== -# Development and Testing -# =================================================================== -# Enable debug mode for Java applications -DEBUG=false - -# Enable Wasm compilation for client applications -ENABLE_WASM=false - -# =================================================================== -# Production Deployment Settings -# =================================================================== -# Container resource limits (configured in docker-compose files) -# These are documented here for reference: - -# Infrastructure Services Resource Limits: -# - postgres: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - redis: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - keycloak: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 1GB RAM) -# - consul: 1 CPU, 512MB RAM (reserved: 0.25 CPU, 128MB RAM) -# - kafka: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - zookeeper: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - prometheus: 1 CPU, 2GB RAM (reserved: 0.25 CPU, 512MB RAM) -# - grafana: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - api-gateway: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 1GB RAM) - -# Microservices Resource Limits: -# - ping-service: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) -# - members-service: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - horses-service: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - events-service: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - masterdata-service: 1.5 CPU, 1.5GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - auth-server: 1.5 CPU, 1.5GB RAM (reserved: 0.5 CPU, 512MB RAM) - -# Client Applications Resource Limits: -# - web-app: 1 CPU, 512MB RAM (reserved: 0.25 CPU, 128MB RAM) -# - desktop-app: 2 CPU, 2GB RAM (reserved: 0.5 CPU, 512MB RAM) -# - monitoring-server: 1 CPU, 1GB RAM (reserved: 0.25 CPU, 256MB RAM) - -# =================================================================== -# Security Notes -# =================================================================== -# 1. All passwords and secrets are managed via Docker secrets -# 2. Run ./docker/secrets/setup-secrets.sh to generate secure credentials -# 3. Containers run as non-root users where possible -# 4. Security options: no-new-privileges enabled for all services -# 5. Networks are isolated with custom subnet (172.20.0.0/16) -# 6. Volumes have proper permissions and are mounted read-only where appropriate -# 7. Health checks are configured for all services -# 8. Resource limits prevent resource exhaustion attacks - -# =================================================================== -# Usage Instructions -# =================================================================== -# 1. Copy this file: cp .env.template .env -# 2. Customize values in .env for your environment -# 3. Generate secrets: ./docker/secrets/setup-secrets.sh --all -# 4. Create data directories: mkdir -p ./data/{postgres,redis,prometheus,grafana,keycloak,consul} -# 5. Deploy infrastructure: docker-compose -f docker-compose.yml.optimized up -d -# 6. Deploy services: docker-compose -f docker-compose.yml.optimized -f docker-compose.services.yml.optimized up -d -# 7. Deploy clients: docker-compose -f docker-compose.yml.optimized -f docker-compose.services.yml.optimized -f docker-compose.clients.yml.optimized up -d - -# =================================================================== -# Monitoring and Logging -# =================================================================== -# Access URLs (when running with default ports): -# - Grafana Dashboard: http://localhost:3000 (admin credentials in secrets) -# - Prometheus Metrics: http://localhost:9090 -# - Consul UI: http://localhost:8500 -# - Keycloak Admin: http://localhost:8180/admin (admin credentials in secrets) -# - API Gateway: http://localhost:8081 -# - Web Application: http://localhost:4000 -# - Desktop VNC: http://localhost:6080 - -# Log locations (inside containers): -# - Application logs: /app/logs/ -# - Nginx logs: /var/log/nginx/ -# - System logs: journalctl -u docker diff --git a/_backup_chaos/README.md b/_backup_chaos/README.md deleted file mode 100644 index b2588a09..00000000 --- a/_backup_chaos/README.md +++ /dev/null @@ -1 +0,0 @@ -# Horses Module\n\nThis is a minimal placeholder README to satisfy documentation validation. See docs/index.md for project docs diff --git a/_backup_chaos/Schlachtplan.md b/_backup_chaos/Schlachtplan.md deleted file mode 100644 index fe0fc29f..00000000 --- a/_backup_chaos/Schlachtplan.md +++ /dev/null @@ -1,443 +0,0 @@ -### Schlachtplan für das 'infrastructure'-Modul - -Basierend auf der Analyse des aktuellen Zustands (Stand: 11. Oktober 2025) habe ich einen strukturierten Aktionsplan -erstellt. Die letzte größere Aktualisierung war im Juli 2025, seitdem gab es signifikante Änderungen am Gateway-Modul. - ---- - -### 🔴 Phase 1: SOFORT (Diese Woche) - -#### 1.1 Gateway-Tests reparieren (Höchste Priorität) - -**Problem:** Tests sind komplett defekt - nur ~47% funktionieren noch (25/53 Tests). - -**Aktionen:** - -- ❌ **Löschen:** `JwtAuthenticationTests.kt` - testet nicht-existierende Custom-Filter -- ✅ **Behalten:** `FallbackControllerTests.kt`, `GatewayApplicationTests.kt` -- ✏️ **Überarbeiten:** `GatewayRoutingTests.kt`, `GatewaySecurityTests.kt`, `GatewayFiltersTests.kt` - - Option A: Tests mit MockJWT-Tokens ausstatten (siehe `TestSecurityConfig.kt`) - - Option B: Tests auf Public Paths verlegen (`/actuator/**`, `/fallback/**`) - - Option C: Security in Tests deaktivieren - -**Warum jetzt:** Tests geben keine Sicherheit mehr – blockiert Entwicklung. - -**Zeitaufwand:** 4–6 Stunden - ---- - -#### 1.2 Gateway-Build-Datei bereinigen - -**Problem:** Duplizierte Dependency in `gateway/build.gradle.kts` (Zeile 33-34). - -**Aktion:** - -```kotlin -// ENTFERNEN: Zeile 34 -implementation(project(":infrastructure:event-store:redis-event-store")) // ← Duplikat! -``` - -**Zeitaufwand:** 5 Minuten - ---- - -### 🟡 Phase 2: KURZFRISTIG (Nächste 2 Wochen) - -#### 2.1 Dependency-Versionen aktualisieren - -**Problem:** Versionen von Juli 2025 – teilweise veraltet. - -**Zu prüfen und aktualisieren:** - -| Dependency | Aktuell | Latest (Okt 2025) | Priorität | -|-------------------|----------|-------------------|-----------| -| Spring Boot | 3.5.5 | 3.5.x | Mittel | -| Spring Cloud | 2025.0.0 | 2025.0.x | Mittel | -| Kotlin | 2.2.20 | 2.2.x | Niedrig | -| Keycloak | 26.0.7 | 26.x.x | Hoch | -| Testcontainers | 1.21.3 | 1.21.x | Niedrig | -| PostgresQL Driver | 42.7.7 | 42.7.x | Niedrig | - -**Aktion:** - -1. `gradle/libs.versions.toml` aktualisieren -2. Tests nach jedem Update ausführen -3. Breaking Changes dokumentieren - -**Zeitaufwand:** 1–2 Tage (mit Testing) - ---- - -#### 2.2 Docker-Images aktualisieren - -**Problem:** Einige Docker-Images sind möglicherweise veraltet. - -**Zu prüfen:** - -```yaml -# docker-compose.yml -postgres: 16-alpine # ✅ Aktuell (neueste: 16.x) -redis: 7-alpine # ✅ Aktuell -keycloak: 26.4.0 # ⚠️ Prüfen auf 26.x updates -consul: 1.15 # ⚠️ Prüfen (neueste: 1.20+) -kafka: 7.4.0 # ⚠️ Prüfen (neueste: 7.8+) -prometheus: v2.54.1 # ⚠️ Prüfen -grafana: 11.3.0 # ✅ Wahrscheinlich aktuell -``` - -**Aktion:** - -1. Versions-Check durchführen -2. Schrittweise aktualisieren (einzeln testen!) -3. `.env`-Datei mit Versions-Variablen anlegen - -**Zeitaufwand:** 3–4 Stunden - ---- - -#### 2.3 Monitoring-Modul vervollständigen - -**Problem:** Nur 3 Kotlin-Files – deutlich unter implementiert im Vergleich zur Dokumentation. - -**Dokumentiert, aber fehlt:** - -- Distributed Tracing (Zipkin) - Docker-Container fehlt! -- Custom Metrics Implementation -- Health Check Aggregation -- Alerting Rules Implementation - -**Aktion:** - -1. Zipkin zu `docker-compose.yml` hinzufügen -2. Tracing-Integration in Gateway testen -3. Custom Metrics-Library erstellen -4. Prometheus Alerting Rules konfigurieren - -**Zeitaufwand:** 2–3 Tage - ---- - -### 🟢 Phase 3: MITTELFRISTIG (Nächste 4–6 Wochen) - -#### 3.1 Dokumentation aktualisieren - -**Problem:** README von Juli 2025 – nicht mehr aktuell. - -**Zu aktualisieren:** - -**`README-INFRASTRUCTURE.md`:** - -- Zeile 552: "Letzte Aktualisierung: 25. Juli 2025" → Oktober 2025 -- Security-Sektion: OAuth2 Resource Server statt Custom JWT Filter -- Keycloak Version: 23.0 → 26.4.0 -- Kafka Version: 7.5.0 → 7.4.0 (Downgrade dokumentieren!) -- Monitoring: Zipkin-Konfiguration ergänzen - -**Neue Sections hinzufügen:** - -- #### Bekannte Limitierungen - -- #### Migration Notes (Juli → Oktober 2025) - -- #### Troubleshooting erweitern - -**Zeitaufwand:** 1 Tag - ---- - -#### 3.2 Auth-Module überarbeiten - -**Problem:** Vermutlich veraltet - Custom JWT vs. OAuth2 Resource Server Diskrepanz. - -**Zu klären:** - -- Werden `auth-client` und `auth-server` noch verwendet? -- Redundanz mit Gateway's OAuth2 Resource Server? -- Keycloak-Integration vereinheitlichen - -**Aktion:** - -1. Abhängigkeiten zu auth-Modulen analysieren -2. Entscheiden: Refactoring oder Deprecation -3. Wenn deprecated: Migration Path dokumentieren - -**Zeitaufwand:** 3–5 Tage - ---- - -#### 3.3 Cache-Module modernisieren - -**Problem:** Redis 7 ist aktuell, aber Implementation-Patterns könnten veraltet sein. - -**Zu prüfen:** - -- Multi-Level Caching tatsächlich implementiert? -- Cache Statistics vorhanden? -- TTL Management korrekt? -- Integration mit Spring Cache Abstraction? - -**Aktion:** - -1. Cache-Tests erweitern -2. Performance-Metriken hinzufügen -3. Cache-Warming Strategy implementieren - -**Zeitaufwand:** 2–3 Tage - ---- - -#### 3.4 Event-Store Performance-Optimierung - -**Problem:** Redis-basiert - für Production ggf. nicht optimal. - -**Zu evaluieren:** - -- Ist Redis der richtige Event Store für Production? -- Alternative: PostgresQL mit Event Store Pattern? -- Snapshot-Strategie tatsächlich implementiert? - -**Aktion:** - -1. Performance-Tests durchführen -2. Event Store Benchmark (Redis vs. PostgresQL) -3. Dokumentation aktualisieren mit Pros/Cons - -**Zeitaufwand:** 1 Woche - ---- - -### 🔵 Phase 4: LANGFRISTIG (Nächste 2–3 Monate) - -#### 4.1 Service Mesh evaluieren - -**Dokumentiert in "Zukünftige Erweiterungen"** – noch nicht implementiert. - -**Optionen:** - -- Istio (komplex, feature-reich) -- Linkerd (leichtgewichtig) -- Consul Connect (bereits Consul vorhanden!) - -**Empfehlung:** Start mit Consul Connect - minimaler Overhead. - -**Zeitaufwand:** 2–3 Wochen - ---- - -#### 4.2 OpenTelemetry statt Zipkin - -**Problem:** Zipkin ist veraltet – OpenTelemetry ist der moderne Standard. - -**Migration Path:** - -1. OpenTelemetry Collector aufsetzen -2. Spring Boot Auto-Instrumentation aktivieren -3. Zipkin als Backend behalten (kompatibel!) -4. Schrittweise migrieren - -**Zeitaufwand:** 1–2 Wochen - ---- - -#### 4.3 Security Hardening - -**Aktuelle Gaps:** - -- JWT Token Rotation nicht implementiert -- Rate Limiting nur dokumentiert, nicht konfiguriert -- Audit Logging fehlt -- HTTPS/TLS noch nicht erzwungen - -**Aktion:** - -1. Rate Limiting im Gateway aktivieren -2. Audit Log Framework implementieren -3. TLS für Service-zu-Service-Kommunikation -4. Security Scan mit OWASP Dependency Check - -**Zeitaufwand:** 2–3 Wochen - ---- - -#### 4.4 Infrastructure as Code (IaC) - -**Problem:** Nur Docker Compose – für Production nicht ausreichend. - -**Zu erstellen:** - -- Kubernetes Manifests (aktualisieren - Zeile 393+) -- Helm Charts (aktualisieren – Zeile 420+) -- Terraform für Cloud-Ressourcen -- CI/CD Pipelines - -**Zeitaufwand:** 4–6 Wochen - ---- - -### 📊 Priorisierung-Matrix - -| Phase | Aufgabe | Dringlichkeit | Aufwand | Impact | -|-------|---------------|---------------|---------|---------| -| 1 | Gateway-Tests | 🔴 Sehr hoch | 4-6h | Hoch | -| 1 | Build-Datei | 🔴 Sehr hoch | 5min | Niedrig | -| 2 | Dependencies | 🟡 Hoch | 1-2d | Mittel | -| 2 | Docker-Images | 🟡 Hoch | 3-4h | Mittel | -| 2 | Monitoring | 🟡 Mittel | 2-3d | Hoch | -| 3 | Dokumentation | 🟢 Mittel | 1d | Mittel | -| 3 | Auth-Module | 🟢 Mittel | 3-5d | Hoch | -| 3 | Cache | 🟢 Niedrig | 2-3d | Mittel | -| 3 | Event-Store | 🟢 Niedrig | 1w | Mittel | -| 4 | Service Mesh | 🔵 Niedrig | 2-3w | Hoch | -| 4 | OpenTelemetry | 🔵 Niedrig | 1-2w | Mittel | -| 4 | Security | 🔵 Mittel | 2-3w | Hoch | -| 4 | IaC | 🔵 Niedrig | 4-6w | Hoch | - ---- - -### 🎯 Empfohlene Reihenfolge - -#### Woche 1-2 - -1. Gateway-Tests reparieren -2. Build-Datei bereinigen -3. Dependencies aktualisieren - -#### Woche 3-4 - -4. Docker-Images aktualisieren -5. Monitoring vervollständigen -6. Dokumentation aktualisieren - -#### Woche 5-8 - -7. Auth-Module evaluieren/refactored -8. Cache-Module modernisieren -9. Event-Store Performance-Tests - -#### Monat 3-4 - -10. Security Hardening -11. OpenTelemetry Migration -12. Service Mesh Evaluation - -#### Monat 5-6 - -13. Infrastructure as Code -14. Production Readiness Assessment - ---- - -### 🛠️ Tooling-Empfehlungen - -**Für Dependency-Management:** - -- Renovate Bot oder Dependabot für automatische Updates -- `./gradlew dependencyUpdates` Plugin verwenden - -**Für Security:** - -- OWASP Dependency Check -- Trivy für Container-Scanning -- SonarQube für Code-Qualität - -**Für Monitoring:** - -- Grafana Dashboards aus Community importieren -- Prometheus Alertmanager konfigurieren - ---- - -### 📝 Nächste Schritte - -1. **Jetzt sofort:** Gateway-Tests fixen (blockiert alles andere) -2. **Diese Woche:** Dependencies updaten und testen -3. **Nächste Woche:** Sprint Planning für Phase 2 -4. **Monatlich:** Review des Fortschritts und Repriorisierung - ---- - -### ⚠️ Risiken & Abhängigkeiten - -**Kritische Pfade:** - -- Gateway-Tests müssen ZUERST behoben werden -- Dependency-Updates können Breaking Changes haben -- Auth-Refactoring könnte alle Services betreffen - -**Externe Abhängigkeiten:** - -- Keycloak Breaking Changes bei Major Updates -- Spring Boot/Cloud Release Schedule beachten -- Kubernetes Cluster für IaC-Phase benötigt - ---- - -**Geschätzter Gesamtaufwand:** 6–8 Wochen (bei 1 Vollzeit-Entwickler) - -**Empfohlener Start:** Sofort mit Phase 1, dann iterativ durch die Phasen - ---- - -### Documentations-Sprachbereinigung (2025-10-22) - -Im Zuge der Vereinheitlichung auf ausschließlich deutschsprachige Dokumentation wurden folgende Dateien entfernt: - -Gelöschte ADRs (englische Varianten): - -- docs/architecture/adr/0000-adr-template.md -- docs/architecture/adr/0001-modular-architecture.md -- docs/architecture/adr/0002-domain-driven-design.md -- docs/architecture/adr/0003-microservices-architecture.md -- docs/architecture/adr/0004-event-driven-communication.md -- docs/architecture/adr/0005-polyglot-persistence.md -- docs/architecture/adr/0006-authentication-authorization-keycloak.md -- docs/architecture/adr/0007-api-gateway-pattern.md -- docs/architecture/adr/0008-multiplatform-client-applications.md - -Gelöschte C4-Diagramme (englische Varianten): - -- docs/architecture/c4/01-context.puml -- docs/architecture/c4/02-container.puml -- docs/architecture/c4/03-component-events-service.puml - -Hinweis: - -- Alle verbleibenden ADRs und C4-Diagramme sind in deutscher Sprache vorhanden (Suffix-de) und verlinkt. -- Weitere Doku-Dateien in docs/ sind deutsch (Front-Matter/Sprachindizien geprüft). - ---- - -## CI‑Stabilisierung Keycloak (2025‑10‑25) - -Hintergrund: In GitHub Actions startete Keycloak zeitweise nicht zuverlässig. Ziel: Integrationstests stabilisieren, -ohne produktive Architektur zu ändern. - -Änderungen: - -- Integration‑Workflow (`.github/workflows/integration-tests.yml`) auf Matrixbetrieb umgestellt: - - `keycloak_db=postgres` (produktnäher, mit externer Postgres‑DB) - - `keycloak_db=dev-file` (Dateibackend, ohne Postgres; stabiler im CI) -- Robuste Startlogik: - - Aktives Warten auf Postgres (nur in `postgres`‑Variante) - - Keycloak‑Start per `docker run … start-dev` (26.4.2) mit `KC_HEALTH_ENABLED=true` - - Health‑Checks gegen `/`, `/health`, `/q/health`, `/health/ready`, Admin‑Konsole - - Ausführliche Log‑Ausgabe bei Fehlern (Keycloak & Postgres) -- Fail‑fast deaktiviert; beide Matrix‑Jobs laufen unabhängig. - -Nutzung/Operative Hinweise: - -- In PRs beide Matrix‑Runs beachten; bei Flakes in `postgres` sichert `dev-file` die Tests ab. -- Logs bei Fehlschlag: Step „Dump service logs (Keycloak, Postgres)“ am Jobende öffnen. -- Produktiv bleibt Postgres maßgeblich (siehe `docker-compose.yml`). - -ADR‑Konsistenz: - -- ADR‑0006 (Keycloak) bleibt gültig und unverändert; die `dev-file`‑Variante betrifft ausschließlich CI‑Tests. - -Next Steps (optional): - -- Falls `postgres` im CI dauerhaft flakey: Required Checks vorübergehend auf `dev-file` begrenzen. -- Langfristig: Ursachenanalyse für Postgres‑Variante (Runner‑Leistung/Timeouts/Schema‑Setup) und Re‑Enable als Required - Check nach Stabilisierung. - ---- diff --git a/_backup_chaos/build-args/clients.env b/_backup_chaos/build-args/clients.env deleted file mode 100644 index 20217c8a..00000000 --- a/_backup_chaos/build-args/clients.env +++ /dev/null @@ -1,20 +0,0 @@ -# =================================================================== -# Clients Docker Build Arguments - dockerfiles/clients/* -# Source: docker/versions.toml [categories.clients] -# Last updated: 2025-11-18 14:30:11 UTC -# =================================================================== - -# --- Include Global Arguments --- -# Source global.env for GRADLE_VERSION, JAVA_VERSION, VERSION - -# --- Client-Specific Build Tools --- -NODE_VERSION=22.21.0 -NGINX_VERSION=1.28.0-alpine - -# --- Client Build Configuration --- -CLIENT_PATH=client -CLIENT_MODULE=client -CLIENT_NAME=meldestelle-client - -# Note: Runtime/Dev values moved to config/env/.env -# Keep this file strictly for build-time values only. diff --git a/_backup_chaos/build-args/global.env b/_backup_chaos/build-args/global.env deleted file mode 100644 index bc1f9e65..00000000 --- a/_backup_chaos/build-args/global.env +++ /dev/null @@ -1,26 +0,0 @@ -# =================================================================== -# Global Docker Build Arguments - Used by all categories -# Source: docker/versions.toml -# Last updated: 2025-11-18 15:44:00 UTC -# =================================================================== - -# --- Build Tools --- -GRADLE_VERSION=9.1.0 -JAVA_VERSION=21 - -# --- Build Metadata --- -VERSION=1.0.0 - -# --- Monitoring & Infrastructure Services (image tags) --- -PROMETHEUS_IMAGE_TAG=v2.54.1 -GRAFANA_IMAGE_TAG=11.3.0 -KEYCLOAK_IMAGE_TAG=26.4.2 - -# --- Datastore Images (image tags) --- -POSTGRES_IMAGE_TAG=16-alpine -REDIS_IMAGE_TAG=7-alpine - -# --- Additional Infrastructure Images (image tags) --- -CONSUL_IMAGE_TAG=1.15 -ZOOKEEPER_IMAGE_TAG=7.4.0 -KAFKA_IMAGE_TAG=7.4.0 diff --git a/_backup_chaos/build-args/infrastructure.env b/_backup_chaos/build-args/infrastructure.env deleted file mode 100644 index e4815383..00000000 --- a/_backup_chaos/build-args/infrastructure.env +++ /dev/null @@ -1,22 +0,0 @@ -# =================================================================== -# Infrastructure Docker Build Arguments - dockerfiles/infrastructure/* -# Source: docker/versions.toml [categories.infrastructure] -# Last updated: 2025-11-18 14:30:11 UTC -# =================================================================== - -# --- Include Global Arguments --- -# Source global.env for GRADLE_VERSION, JAVA_VERSION, VERSION - -# --- API Gateway Specific --- -GATEWAY_SERVICE_PATH=infrastructure/gateway -GATEWAY_SERVICE_NAME=api-gateway - -# --- Auth Server Specific --- -AUTH_SERVER_PATH=infrastructure/auth/auth-server -AUTH_SERVER_SERVICE_NAME=auth-server - -# --- Monitoring Server Specific --- -MONITORING_SERVER_PATH=infrastructure/monitoring/monitoring-server -MONITORING_SERVER_SERVICE_NAME=monitoring-server - -# Note: Runtime profiles/ports/dependencies moved to config/env/.env diff --git a/_backup_chaos/build-args/services.env b/_backup_chaos/build-args/services.env deleted file mode 100644 index bb61096a..00000000 --- a/_backup_chaos/build-args/services.env +++ /dev/null @@ -1,14 +0,0 @@ -# =================================================================== -# Services Docker Build Arguments - dockerfiles/services/* -# Source: docker/versions.toml [categories.services] -# Last updated: 2025-11-18 14:30:11 UTC -# =================================================================== - -# --- Include Global Arguments --- -# Source global.env for GRADLE_VERSION, JAVA_VERSION, VERSION - -# --- Service-Specific Arguments --- -SERVICE_PATH=. -SERVICE_NAME=spring-boot-service - -# Note: Runtime profiles/ports moved to config/env/.env diff --git a/_backup_chaos/compose.yaml b/_backup_chaos/compose.yaml deleted file mode 100644 index 770d83c0..00000000 --- a/_backup_chaos/compose.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Zentrale Einstiegsdatei für Docker Compose -name: meldestelle-system - -include: - - path: docker-compose.yml # Infra (Postgres, Redis, Keycloak...) - - path: docker-compose.services.yml # Gateway & Microservices - - path: docker-compose.clients.yml # Web App - -# Hier definieren wir das Netzwerk zentral, damit alle includes es nutzen können -networks: - meldestelle-network: - driver: bridge diff --git a/_backup_chaos/docker-compose.clients.yml b/_backup_chaos/docker-compose.clients.yml deleted file mode 100644 index cff30daa..00000000 --- a/_backup_chaos/docker-compose.clients.yml +++ /dev/null @@ -1,54 +0,0 @@ -# =================================================================== -# Docker Compose - Client Applications -# Generated from docker/versions.toml -# Environment: development -# Generated: 2025-11-18 19:43:46 UTC -# =================================================================== - -services: - # =================================================================== - # Web Application (Compose for Web) - # =================================================================== - web-app: - profiles: ["ui", "frontend"] - build: - context: .. - dockerfile: ../dockerfiles/clients/web-app/Dockerfile - args: - # Global build arguments (centralized DOCKER_* variables) - GRADLE_VERSION: ${DOCKER_GRADLE_VERSION} - JAVA_VERSION: ${DOCKER_JAVA_VERSION} - BUILD_DATE: ${BUILD_DATE} - VERSION: ${DOCKER_APP_VERSION} - # Client-specific arguments (centralized DOCKER_* variables) - NODE_VERSION: ${DOCKER_NODE_VERSION} - NGINX_VERSION: ${DOCKER_NGINX_VERSION} - # Application-specific arguments - CLIENT_PATH: client - CLIENT_MODULE: client - CLIENT_NAME: meldestelle-web-app - container_name: meldestelle-web-app - environment: - NODE_ENV: ${NODE_ENV:-dev} - API_BASE_URL: http://api-gateway:${GATEWAY_PORT:-8081} - WS_URL: ws://api-gateway:${GATEWAY_PORT:-8081}/ws - APP_TITLE: ${APP_NAME:-Meldestelle} - APP_VERSION: ${APP_VERSION:-1.0.0} - ports: - - "4000:4000" - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:4000/health"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - -# =================================================================== -# Networks (shared network from main compose file) -# =================================================================== -networks: - meldestelle-network: - driver: bridge diff --git a/_backup_chaos/docker-compose.services.yml b/_backup_chaos/docker-compose.services.yml deleted file mode 100644 index 241c30e2..00000000 --- a/_backup_chaos/docker-compose.services.yml +++ /dev/null @@ -1,80 +0,0 @@ -# =================================================================== -# Docker Compose - Application Services -# Generated from docker/versions.toml -# Environment: development -# Generated: 2025-11-18 19:43:46 UTC -# =================================================================== - -services: - profiles: ["app", "backend"] - ping-service: - build: - context: .. - dockerfile: ../dockerfiles/services/ping-service/Dockerfile - args: - # Global build arguments (centralized DOCKER_* variables) - GRADLE_VERSION: ${DOCKER_GRADLE_VERSION} - JAVA_VERSION: ${DOCKER_JAVA_VERSION} - BUILD_DATE: ${BUILD_DATE} - VERSION: ${DOCKER_APP_VERSION} - # Service-specific arguments (centralized DOCKER_* variables) - SPRING_PROFILES_ACTIVE: ${DOCKER_SPRING_PROFILES_DOCKER} - container_name: meldestelle-ping-service - environment: - SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-dev} - SERVER_PORT: ${PING_SERVICE_PORT:-8082} - DEBUG: ${DEBUG:-true} - LOGGING_LEVEL_ROOT: ${LOGGING_LEVEL_ROOT:-DEBUG} - JVM_DEBUG_PORT: 5005 - ports: - - "${PING_SERVICE_PORT:-8082}:8082" - - "5005:5005" # Debug-Port - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:8082/actuator/health/readiness"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - - api-gateway: - profiles: ["infra", "gateway"] - build: - context: .. - dockerfile: ../dockerfiles/infrastructure/gateway/Dockerfile - args: - # Global build arguments (centralized DOCKER_* variables) - GRADLE_VERSION: ${DOCKER_GRADLE_VERSION} - JAVA_VERSION: ${DOCKER_JAVA_VERSION} - BUILD_DATE: ${BUILD_DATE} - VERSION: ${DOCKER_APP_VERSION} - # Infrastructure-specific arguments (centralized DOCKER_* variables) - SPRING_PROFILES_ACTIVE: ${DOCKER_SPRING_PROFILES_DEFAULT} - container_name: meldestelle-api-gateway - environment: - SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-dev} - SERVER_PORT: ${API_GATEWAY_PORT:-8081} - DEBUG: ${DEBUG:-true} - LOGGING_LEVEL_ROOT: ${LOGGING_LEVEL_ROOT:-DEBUG} - JVM_DEBUG_PORT: 5005 - ports: - - "${API_GATEWAY_PORT:-8081}:8081" - - "5005:5005" # Debug-Port - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:8081/actuator/health/readiness"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - -# =================================================================== -# Networks (shared network from main compose file) -# =================================================================== -networks: - meldestelle-network: - driver: bridge diff --git a/_backup_chaos/docker-compose.yml b/_backup_chaos/docker-compose.yml deleted file mode 100644 index b2fafb94..00000000 --- a/_backup_chaos/docker-compose.yml +++ /dev/null @@ -1,162 +0,0 @@ -# =================================================================== -# Docker Compose - Infrastructure Services -# Generated from docker/versions.toml -# Environment: development -# Generated: 2025-11-18 19:43:46 UTC -# =================================================================== - -services: - # =================================================================== - # Database - # =================================================================== - postgres: - profiles: ["core", "backend"] - image: postgres:${DOCKER_POSTGRES_VERSION:-16-alpine} - container_name: meldestelle-postgres - environment: - POSTGRES_USER: ${POSTGRES_USER:-meldestelle} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-meldestelle} - POSTGRES_DB: ${POSTGRES_DB:-meldestelle} - ports: - - "5432:5432" - volumes: - - postgres-data:/var/lib/postgresql/data - - ./docker/services/postgres:/docker-entrypoint-initdb.d - networks: - - meldestelle-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U meldestelle -d meldestelle"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - - # =================================================================== - # Cache - # =================================================================== - redis: - profiles: ["core", "backend"] - image: redis:${DOCKER_REDIS_VERSION:-7-alpine} - container_name: meldestelle-redis - ports: - - "${REDIS_PORT:-6379}:6379" - volumes: - - redis-data:/data - command: redis-server --appendonly yes - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - - # =================================================================== - # Authentication - # =================================================================== - keycloak: - profiles: ["auth", "security"] - image: quay.io/keycloak/keycloak:${DOCKER_KEYCLOAK_VERSION:-26.4.2} - container_name: meldestelle-keycloak - environment: - KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-admin} - KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin} - KC_DB: postgres - KC_DB_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-meldestelle} - KC_DB_USERNAME: ${POSTGRES_USER:-meldestelle} - KC_DB_PASSWORD: ${POSTGRES_PASSWORD:-meldestelle} - ports: - - "8180:8080" - depends_on: - postgres: - condition: service_healthy - volumes: - - ./docker/services/keycloak:/opt/keycloak/data/import - command: start-dev --import-realm - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - - # =================================================================== - # Monitoring - # =================================================================== - prometheus: - profiles: ["monitoring"] - image: prom/prometheus:${DOCKER_PROMETHEUS_VERSION:-v2.54.1} - container_name: meldestelle-prometheus - ports: - - "${PROMETHEUS_PORT:-9090}:9090" - volumes: - - prometheus-data:/prometheus - - ./docker/monitoring/prometheus:/etc/prometheus:ro - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--web.console.libraries=/etc/prometheus/console_libraries' - - '--web.console.templates=/etc/prometheus/consoles' - - '--storage.tsdb.retention.time=200h' - - '--web.enable-lifecycle' - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9090/-/healthy"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - - grafana: - profiles: ["monitoring"] - image: grafana/grafana:${DOCKER_GRAFANA_VERSION:-11.3.0} - container_name: meldestelle-grafana - environment: - GF_SECURITY_ADMIN_USER: ${GF_SECURITY_ADMIN_USER:-admin} - GF_SECURITY_ADMIN_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD:-admin} - GF_USERS_ALLOW_SIGN_UP: ${GF_USERS_ALLOW_SIGN_UP:-false} - GF_INSTALL_PLUGINS: grafana-piechart-panel - ports: - - "${GRAFANA_PORT:-3000}:3000" - volumes: - - grafana-data:/var/lib/grafana - - ./docker/monitoring/grafana:/etc/grafana/provisioning:ro - depends_on: - - prometheus - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:3000/api/health"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - restart: unless-stopped - -# =================================================================== -# Volumes -# =================================================================== -volumes: - postgres-data: - driver: local - redis-data: - driver: local - prometheus-data: - driver: local - grafana-data: - driver: local - -# =================================================================== -# Networks -# =================================================================== -networks: - meldestelle-network: - driver: bridge diff --git a/_backup_chaos/events/README.md b/_backup_chaos/events/README.md deleted file mode 100644 index fe671c3d..00000000 --- a/_backup_chaos/events/README.md +++ /dev/null @@ -1 +0,0 @@ -# Events Module\n\nThis is a minimal placeholder README to satisfy documentation validation. See docs/index.md for project docs diff --git a/_backup_chaos/masterdata/README.md b/_backup_chaos/masterdata/README.md deleted file mode 100644 index 2cac59a4..00000000 --- a/_backup_chaos/masterdata/README.md +++ /dev/null @@ -1 +0,0 @@ -# Masterdata Module\n\nThis is a minimal placeholder README to satisfy documentation validation. See docs/index.md for project docs diff --git a/_backup_chaos/members/README.md b/_backup_chaos/members/README.md deleted file mode 100644 index 0e336b79..00000000 --- a/_backup_chaos/members/README.md +++ /dev/null @@ -1 +0,0 @@ -# Members Module\n\nThis is a minimal placeholder README to satisfy documentation validation. See docs/index.md for project docs diff --git a/_backup_chaos/secrets/README.md b/_backup_chaos/secrets/README.md deleted file mode 100644 index d50a04ac..00000000 --- a/_backup_chaos/secrets/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Docker Secrets (Development vs. Production) - -In der lokalen Entwicklung werden keine Docker-Secrets erzwungen. - -- Verwende für sensible Werte stattdessen die Datei `config/env/.env.local` (ist gitignored). -- Die Dateien in diesem Ordner sind lediglich Platzhalter und enthalten KEINE echten Geheimnisse. -- Für ein Deployment in Produktion kannst du diese Dateien mit echten Werten befüllen oder einen sicheren Secret-Store (Docker/K8s) verwenden. - -Hinweise: -- Postgres-User/Passwort haben in der lokalen Entwicklung Standard/Fallback-Werte via `docker-compose.yml` (Environment mit Defaults). -- Die optimierten Compose-Dateien (`*.optimized`) können weiterhin Docker-Secrets verwenden – diese sind für Prod gedacht. - -Schnellstart lokal (ohne Secrets): -- Passe `config/env/.env` und optional `config/env/.env.local` an -- Starte mit: `docker compose -f docker-compose.yml -f docker-compose.services.yml up` diff --git a/_backup_chaos/secrets/grafana_admin_password.txt b/_backup_chaos/secrets/grafana_admin_password.txt deleted file mode 100644 index e37f4acd..00000000 --- a/_backup_chaos/secrets/grafana_admin_password.txt +++ /dev/null @@ -1 +0,0 @@ -TiB6FRRYW4gjM7xie17mKtTYFOp \ No newline at end of file diff --git a/_backup_chaos/secrets/grafana_admin_user.txt b/_backup_chaos/secrets/grafana_admin_user.txt deleted file mode 100644 index f77b0040..00000000 --- a/_backup_chaos/secrets/grafana_admin_user.txt +++ /dev/null @@ -1 +0,0 @@ -admin \ No newline at end of file diff --git a/_backup_chaos/secrets/jwt_secret.txt b/_backup_chaos/secrets/jwt_secret.txt deleted file mode 100644 index 6c466b67..00000000 --- a/_backup_chaos/secrets/jwt_secret.txt +++ /dev/null @@ -1 +0,0 @@ -ba960b899f72d5ed192b5597d7f4b5b8853d9d641a2dc23c6b1a4b692b20211c \ No newline at end of file diff --git a/_backup_chaos/secrets/keycloak_admin_password.txt b/_backup_chaos/secrets/keycloak_admin_password.txt deleted file mode 100644 index d85823d0..00000000 --- a/_backup_chaos/secrets/keycloak_admin_password.txt +++ /dev/null @@ -1 +0,0 @@ -XASb7AzVy7G5fEKulE1mNPTy2Sw6pHi \ No newline at end of file diff --git a/_backup_chaos/secrets/keycloak_auth_client_secret.txt b/_backup_chaos/secrets/keycloak_auth_client_secret.txt deleted file mode 100644 index 24b090bc..00000000 --- a/_backup_chaos/secrets/keycloak_auth_client_secret.txt +++ /dev/null @@ -1 +0,0 @@ -s8N3r59JwS0lFsJobKWFJXh9qvdbHgcC6S3fYXYdXFM6eMKkRMtQbxHo0NJKFJC \ No newline at end of file diff --git a/_backup_chaos/secrets/keycloak_client_secret.txt b/_backup_chaos/secrets/keycloak_client_secret.txt deleted file mode 100644 index 9f2f28b1..00000000 --- a/_backup_chaos/secrets/keycloak_client_secret.txt +++ /dev/null @@ -1 +0,0 @@ -lRo7W15UNy60EFRlvk1XP99MmgrgK2Z97QK9btl9ZPVIVzWcY81Bebp9hpB \ No newline at end of file diff --git a/_backup_chaos/secrets/metrics_auth_password.txt b/_backup_chaos/secrets/metrics_auth_password.txt deleted file mode 100644 index 73e896b2..00000000 --- a/_backup_chaos/secrets/metrics_auth_password.txt +++ /dev/null @@ -1 +0,0 @@ -pON4NxxsKPWseVg1gw5PyLNN4YYrj8h \ No newline at end of file diff --git a/_backup_chaos/secrets/metrics_auth_username.txt b/_backup_chaos/secrets/metrics_auth_username.txt deleted file mode 100644 index 6f8aca1a..00000000 --- a/_backup_chaos/secrets/metrics_auth_username.txt +++ /dev/null @@ -1 +0,0 @@ -metrics \ No newline at end of file diff --git a/_backup_chaos/secrets/postgres_password.txt b/_backup_chaos/secrets/postgres_password.txt deleted file mode 100644 index a9a6cd11..00000000 --- a/_backup_chaos/secrets/postgres_password.txt +++ /dev/null @@ -1 +0,0 @@ -CHANGE_ME_LOCAL_DEV diff --git a/_backup_chaos/secrets/postgres_user.txt b/_backup_chaos/secrets/postgres_user.txt deleted file mode 100644 index 81559440..00000000 --- a/_backup_chaos/secrets/postgres_user.txt +++ /dev/null @@ -1 +0,0 @@ -meldestelle \ No newline at end of file diff --git a/_backup_chaos/secrets/redis_password.txt b/_backup_chaos/secrets/redis_password.txt deleted file mode 100644 index eaab894a..00000000 --- a/_backup_chaos/secrets/redis_password.txt +++ /dev/null @@ -1 +0,0 @@ -p701HhKOnZJ4zbY9dGRvyH9kQTKcsUm \ No newline at end of file diff --git a/_backup_chaos/secrets/setup-secrets.sh b/_backup_chaos/secrets/setup-secrets.sh deleted file mode 100755 index 919f0a3e..00000000 --- a/_backup_chaos/secrets/setup-secrets.sh +++ /dev/null @@ -1,345 +0,0 @@ -#!/bin/bash - -# =================================================================== -# Docker Secrets Setup Script - Meldestelle Project -# =================================================================== -# This script generates secure secrets for all Docker services -# Security Features: -# - Generates cryptographically secure random passwords -# - Creates JWT secrets with proper length for HMAC512 -# - Sets appropriate file permissions (600) for security -# - Provides backup functionality -# - Validates secret file creation -# =================================================================== - -set -euo pipefail - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Script directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SECRETS_DIR="${SCRIPT_DIR}" - -# Logging function -log() { - echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" -} - -warn() { - echo -e "${YELLOW}[WARNING] $1${NC}" -} - -error() { - echo -e "${RED}[ERROR] $1${NC}" - exit 1 -} - -# Function to generate secure random password -generate_password() { - local length=${1:-32} - openssl rand -base64 $((length * 3 / 4)) | tr -d "=+/" | cut -c1-${length} -} - -# Function to generate JWT secret (64 characters for HMAC512) -generate_jwt_secret() { - openssl rand -hex 32 -} - -# Function to create secret file with proper permissions -create_secret_file() { - local filename="$1" - local content="$2" - local filepath="${SECRETS_DIR}/${filename}" - - # Check if file already exists - if [[ -f "$filepath" ]]; then - warn "Secret file $filename already exists. Use --force to overwrite." - return 1 - fi - - # Create the secret file - echo -n "$content" > "$filepath" - chmod 600 "$filepath" - - log "Created secret file: $filename" - return 0 -} - -# Function to backup existing secrets -backup_secrets() { - local backup_dir="${SECRETS_DIR}/backup_$(date +%Y%m%d_%H%M%S)" - - if find "$SECRETS_DIR" -name "*.txt" -type f | grep -q .; then - log "Creating backup of existing secrets..." - mkdir -p "$backup_dir" - find "$SECRETS_DIR" -name "*.txt" -type f -exec cp {} "$backup_dir/" \; - log "Backup created in: $backup_dir" - fi -} - -# Function to validate secret file -validate_secret_file() { - local filepath="$1" - local min_length="$2" - - if [[ ! -f "$filepath" ]]; then - error "Secret file does not exist: $filepath" - fi - - local content_length=$(wc -c < "$filepath") - if [[ $content_length -lt $min_length ]]; then - error "Secret file $filepath is too short (${content_length} < ${min_length})" - fi - - local permissions=$(stat -c %a "$filepath") - if [[ "$permissions" != "600" ]]; then - warn "Secret file $filepath has incorrect permissions: $permissions (should be 600)" - chmod 600 "$filepath" - fi -} - -# Function to generate all secrets -generate_all_secrets() { - local force_overwrite=${1:-false} - - log "Starting secret generation for Meldestelle Docker infrastructure..." - - # Create backup if not forcing overwrite - if [[ "$force_overwrite" != "true" ]]; then - backup_secrets - fi - - # Database secrets - log "Generating database secrets..." - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/postgres_user.txt" ]]; then - create_secret_file "postgres_user.txt" "meldestelle" - fi - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/postgres_password.txt" ]]; then - create_secret_file "postgres_password.txt" "$(generate_password 32)" - fi - - # Redis secrets - log "Generating Redis secrets..." - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/redis_password.txt" ]]; then - create_secret_file "redis_password.txt" "$(generate_password 32)" - fi - - # Keycloak secrets - log "Generating Keycloak secrets..." - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/keycloak_admin_password.txt" ]]; then - create_secret_file "keycloak_admin_password.txt" "$(generate_password 32)" - fi - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/keycloak_client_secret.txt" ]]; then - create_secret_file "keycloak_client_secret.txt" "$(generate_password 64)" - fi - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/keycloak_auth_client_secret.txt" ]]; then - create_secret_file "keycloak_auth_client_secret.txt" "$(generate_password 64)" - fi - - # Grafana secrets - log "Generating Grafana secrets..." - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/grafana_admin_user.txt" ]]; then - create_secret_file "grafana_admin_user.txt" "admin" - fi - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/grafana_admin_password.txt" ]]; then - create_secret_file "grafana_admin_password.txt" "$(generate_password 32)" - fi - - # JWT secrets - log "Generating JWT secrets..." - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/jwt_secret.txt" ]]; then - create_secret_file "jwt_secret.txt" "$(generate_jwt_secret)" - fi - - # VNC secrets (for desktop app) - log "Generating VNC secrets..." - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/vnc_password.txt" ]]; then - create_secret_file "vnc_password.txt" "$(generate_password 16)" - fi - - # Monitoring secrets - log "Generating monitoring secrets..." - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/metrics_auth_username.txt" ]]; then - create_secret_file "metrics_auth_username.txt" "metrics" - fi - if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/metrics_auth_password.txt" ]]; then - create_secret_file "metrics_auth_password.txt" "$(generate_password 32)" - fi - - log "Secret generation completed successfully!" -} - -# Function to validate all secrets -validate_all_secrets() { - log "Validating all secret files..." - - # Define expected secrets with minimum lengths - declare -A secrets=( - ["postgres_user.txt"]=8 - ["postgres_password.txt"]=16 - ["redis_password.txt"]=16 - ["keycloak_admin_password.txt"]=16 - ["keycloak_client_secret.txt"]=32 - ["keycloak_auth_client_secret.txt"]=32 - ["grafana_admin_user.txt"]=4 - ["grafana_admin_password.txt"]=16 - ["jwt_secret.txt"]=64 - ["vnc_password.txt"]=8 - ["metrics_auth_username.txt"]=4 - ["metrics_auth_password.txt"]=16 - ) - - local all_valid=true - for secret_file in "${!secrets[@]}"; do - local filepath="${SECRETS_DIR}/${secret_file}" - local min_length=${secrets[$secret_file]} - - if validate_secret_file "$filepath" "$min_length" 2>/dev/null; then - log "✓ $secret_file is valid" - else - error "✗ $secret_file is invalid or missing" - all_valid=false - fi - done - - if [[ "$all_valid" == "true" ]]; then - log "All secret files are valid and properly secured!" - else - error "Some secret files are invalid. Please regenerate secrets." - fi -} - -# Function to create Docker secrets -create_docker_secrets() { - log "Creating Docker secrets..." - - # Get the project name (directory name) - local project_name=$(basename "$(dirname "$(dirname "$SCRIPT_DIR")")") - - # Define secrets to create - declare -A docker_secrets=( - ["postgres_user"]="postgres_user.txt" - ["postgres_password"]="postgres_password.txt" - ["redis_password"]="redis_password.txt" - ["keycloak_admin_password"]="keycloak_admin_password.txt" - ["keycloak_client_secret"]="keycloak_client_secret.txt" - ["grafana_admin_user"]="grafana_admin_user.txt" - ["grafana_admin_password"]="grafana_admin_password.txt" - ["jwt_secret"]="jwt_secret.txt" - ) - - for secret_name in "${!docker_secrets[@]}"; do - local secret_file="${docker_secrets[$secret_name]}" - local filepath="${SECRETS_DIR}/${secret_file}" - local docker_secret_name="${project_name}_${secret_name}" - - # Check if Docker secret already exists - if docker secret ls --format "{{.Name}}" | grep -q "^${docker_secret_name}$"; then - warn "Docker secret $docker_secret_name already exists" - else - # Create Docker secret - if docker secret create "$docker_secret_name" "$filepath"; then - log "Created Docker secret: $docker_secret_name" - else - error "Failed to create Docker secret: $docker_secret_name" - fi - fi - done -} - -# Function to show usage -show_usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " --help Show this help message" - echo " --generate Generate all secret files (default)" - echo " --force Force overwrite existing secret files" - echo " --validate Validate existing secret files" - echo " --docker-secrets Create Docker secrets from files" - echo " --all Generate files, validate, and create Docker secrets" - echo "" - echo "Examples:" - echo " $0 # Generate secrets (skip existing files)" - echo " $0 --force # Generate secrets (overwrite existing files)" - echo " $0 --validate # Validate existing secret files" - echo " $0 --all # Complete setup (generate, validate, docker secrets)" -} - -# Main execution -main() { - local action="generate" - local force_overwrite=false - - # Check dependencies - if ! command -v openssl &> /dev/null; then - error "openssl is required but not installed" - fi - - # Parse command line arguments - while [[ $# -gt 0 ]]; do - case $1 in - --help) - show_usage - exit 0 - ;; - --generate) - action="generate" - shift - ;; - --force) - force_overwrite=true - shift - ;; - --validate) - action="validate" - shift - ;; - --docker-secrets) - action="docker-secrets" - shift - ;; - --all) - action="all" - shift - ;; - *) - error "Unknown option: $1" - ;; - esac - done - - # Ensure secrets directory exists - mkdir -p "$SECRETS_DIR" - - # Execute requested action - case $action in - "generate") - generate_all_secrets "$force_overwrite" - ;; - "validate") - validate_all_secrets - ;; - "docker-secrets") - create_docker_secrets - ;; - "all") - generate_all_secrets "$force_overwrite" - validate_all_secrets - create_docker_secrets - ;; - *) - error "Invalid action: $action" - ;; - esac - - log "Operation completed successfully!" -} - -# Run main function with all arguments -main "$@" diff --git a/_backup_chaos/secrets/vnc_password.txt b/_backup_chaos/secrets/vnc_password.txt deleted file mode 100644 index 3fbc6e46..00000000 --- a/_backup_chaos/secrets/vnc_password.txt +++ /dev/null @@ -1 +0,0 @@ -nrscAXfIoOKTAEt \ No newline at end of file diff --git a/_backup_chaos/versions.toml.bak b/_backup_chaos/versions.toml.bak deleted file mode 100644 index 30d81e3d..00000000 --- a/_backup_chaos/versions.toml.bak +++ /dev/null @@ -1,192 +0,0 @@ -# =================================================================== -# Docker Versions Catalog - Single Source of Truth -# Analogous to gradle/libs.versions.toml for centralized version management -# =================================================================== -# Last updated: 2025-09-13 -# Eliminates version redundancy across 12+ Dockerfiles - -[versions] -# --- Build Tools --- -gradle = "9.1.0" -java = "21" -node = "22.21.0" - -# --- Base Images --- -nginx = "1.25-alpine" -alpine = "3.19" -eclipse-temurin-jdk = "21-jdk-alpine" -eclipse-temurin-jre = "21-jre-alpine" - -# --- Monitoring & Infrastructure Services --- -prometheus = "v2.54.1" -grafana = "11.3.0" -keycloak = "26.0.7" - -# --- Spring Configuration --- -spring-profiles-default = "default" -spring-profiles-docker = "docker" -spring-profiles-prod = "prod" - -# --- Application Versions --- -app-version = "1.0.0" - -# --- Zentrale Port-Verwaltung --- -# Single Source of Truth für alle Service-Ports - -[service-ports] -# --- Infrastructure Services --- -api-gateway = 8081 -auth-server = 8087 -monitoring-server = 8088 - -# --- Application Services --- -ping-service = 8082 -members-service = 8083 -horses-service = 8084 -events-service = 8085 -masterdata-service = 8086 - -# --- External Services --- -postgres = 5432 -redis = 6379 -keycloak = 8180 -consul = 8500 -zookeeper = 2181 -kafka = 9092 - -# --- Monitoring Stack --- -prometheus = 9090 -grafana = 3000 - -# --- Client Applications --- -web-app = 4000 -desktop-app-vnc = 5901 -desktop-app-novnc = 6080 - -[port-ranges] -# --- Port-Range-Definitionen für automatische Port-Zuweisung --- -infrastructure = "8081-8088" -services = "8082-8099" -monitoring = "9090-9099" -clients = "4000-4099" -vnc = "5901-5999" -debug = "5005-5009" - -# --- Reserved Port Ranges --- -system-reserved = "0-1023" -ephemeral = "32768-65535" - -[build-args] -# --- Global Build Arguments (used across all categories) --- -global = [ - "GRADLE_VERSION", - "JAVA_VERSION", - "BUILD_DATE", - "VERSION" -] - -# --- Spring Boot Services (dockerfiles/services/* and infrastructure/*) --- -spring-services = [ - "SPRING_PROFILES_ACTIVE", - "SERVICE_PATH", - "SERVICE_NAME", - "SERVICE_PORT" -] - -# --- Kotlin/JS Web Clients (dockerfiles/clients/*) --- -web-clients = [ - "NODE_VERSION", - "NGINX_VERSION", - "CLIENT_PATH", - "CLIENT_MODULE", - "CLIENT_NAME" -] - -[categories] -# --- Services Configuration --- -[categories.services] -default-spring-profile = "docker" -default-port-start = 8082 -services = [ - "ping-service", - "members-service", - "horses-service", - "events-service", - "masterdata-service" -] - -# --- Infrastructure Configuration --- -[categories.infrastructure] -default-spring-profile = "default" -services = [ - "gateway", - "auth-server", - "monitoring-server" -] - -# --- Client Applications Configuration --- -[categories.clients] -default-node-version = "20.11.0" -default-nginx-version = "1.25-alpine" -clients = [ - "web-app", - "desktop-app" -] - -[environment-mapping] -# --- Environment Variable Names for Docker Compose --- -# Maps internal version names to environment variable names -gradle-version = "DOCKER_GRADLE_VERSION" -java-version = "DOCKER_JAVA_VERSION" -node-version = "DOCKER_NODE_VERSION" -nginx-version = "DOCKER_NGINX_VERSION" -prometheus-version = "DOCKER_PROMETHEUS_VERSION" -grafana-version = "DOCKER_GRAFANA_VERSION" -keycloak-version = "DOCKER_KEYCLOAK_VERSION" -spring-profiles-default = "DOCKER_SPRING_PROFILES_DEFAULT" -spring-profiles-docker = "DOCKER_SPRING_PROFILES_DOCKER" -app-version = "DOCKER_APP_VERSION" - -[environments] -# --- Environment-spezifische Konfigurationen --- -# Zentrale Verwaltung für dev/test/prod Umgebungen - -[environments.development] -spring-profiles = "dev" -debug-enabled = true -log-level = "DEBUG" -health-check-interval = "30s" -health-check-timeout = "5s" -health-check-retries = 3 -health-check-start-period = "40s" -resource-limits = false -jvm-debug-port = 5005 -hot-reload = true - -[environments.production] -spring-profiles = "prod" -debug-enabled = false -log-level = "INFO" -health-check-interval = "15s" -health-check-timeout = "3s" -health-check-retries = 3 -health-check-start-period = "30s" -resource-limits = true -jvm-debug-port = false -hot-reload = false -security-headers = true -tls-enabled = true - -[environments.testing] -spring-profiles = "test" -debug-enabled = true -log-level = "DEBUG" -health-check-interval = "10s" -health-check-timeout = "5s" -health-check-retries = 2 -health-check-start-period = "20s" -resource-limits = false -jvm-debug-port = 5005 -hot-reload = false -ephemeral-storage = true -test-containers = true diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 1e834ba3..00000000 --- a/backend/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Backend - -Domänenspezifische Services und Gateway. - -- gateway: API Gateway/Auth/Routing -- discovery: Service Registry/Discovery (optional) -- services: Microservices, pro Domäne ein Service diff --git a/backend/gateway/gateway/CONFIGURATION.md b/backend/gateway/gateway/CONFIGURATION.md deleted file mode 100644 index 62373426..00000000 --- a/backend/gateway/gateway/CONFIGURATION.md +++ /dev/null @@ -1,733 +0,0 @@ -# Gateway Configuration Documentation - -## Überblick - -Dieses Dokument beschreibt alle zentralen Konfigurationseigenschaften für das API Gateway. Die Konfiguration erfolgt über die `application.yml` Datei und kann durch Umgebungsvariablen überschrieben werden. - -## Table of Contents - -- [Server Configuration](#server-configuration) -- [Spring Application](#spring-application) -- [Consul Service Discovery](#consul-service-discovery) -- [Spring Cloud Gateway](#spring-cloud-gateway) -- [Circuit Breaker (Resilience4j)](#circuit-breaker-resilience4j) -- [Management & Monitoring](#management--monitoring) -- [Security](#security) -- [Logging](#logging) - ---- - -## Server Configuration - -### server.port - -- **Typ**: Integer -- **Default**: 8081 -- **Environment Variable**: `GATEWAY_PORT` -- **Beschreibung**: Port, auf dem das Gateway läuft - -### server.netty.connection-timeout - -- **Typ**: Duration -- **Default**: 5s -- **Beschreibung**: Timeout für initiale TCP-Verbindungen - -### server.netty.idle-timeout - -- **Typ**: Duration -- **Default**: 15s -- **Beschreibung**: Timeout für inaktive Verbindungen - -**Beispiel:** - -```yaml -server: - port: 8081 - netty: - connection-timeout: 5s - idle-timeout: 15s -``` - ---- - -## Spring Application - -### spring.application.name - -- **Typ**: String -- **Default**: api-gateway -- **Beschreibung**: Name der Anwendung, wird in Consul und Logs verwendet - -### spring.profiles.active - -- **Typ**: String -- **Default**: dev -- **Environment Variable**: `SPRING_PROFILES_ACTIVE` -- **Beschreibung**: Aktives Spring-Profil (dev, test, prod) -- **Mögliche Werte**: dev, test, staging, prod - -### spring.security.user.name / password - -- **Typ**: String -- **Default**: admin / admin -- **Environment Variables**: `GATEWAY_ADMIN_USER`, `GATEWAY_ADMIN_PASSWORD` -- **Beschreibung**: Basic Auth für administrative Endpunkte -- **⚠️ Wichtig**: In Produktion durch sichere Werte ersetzen! - -**Beispiel:** - -```yaml -spring: - application: - name: api-gateway - profiles: - active: ${SPRING_PROFILES_ACTIVE:dev} - security: - user: - name: ${GATEWAY_ADMIN_USER:admin} - password: ${GATEWAY_ADMIN_PASSWORD:admin} -``` - ---- - -## Consul Service Discovery - -### spring.cloud.consul.host - -- **Typ**: String -- **Default**: localhost -- **Environment Variable**: `CONSUL_HOST` -- **Beschreibung**: Hostname des Consul-Servers - -### spring.cloud.consul.port - -- **Typ**: Integer -- **Default**: 8500 -- **Environment Variable**: `CONSUL_PORT` -- **Beschreibung**: Port des Consul-Servers - -### spring.cloud.consul.enabled - -- **Typ**: Boolean -- **Default**: true -- **Environment Variable**: `CONSUL_ENABLED` -- **Beschreibung**: Aktiviert/Deaktiviert Consul Integration - -### spring.cloud.consul.discovery.enabled - -- **Typ**: Boolean -- **Default**: true -- **Environment Variable**: `CONSUL_ENABLED` -- **Beschreibung**: Aktiviert Service Discovery - -### spring.cloud.consul.discovery.register - -- **Typ**: Boolean -- **Default**: true -- **Environment Variable**: `CONSUL_ENABLED` -- **Beschreibung**: Registriert das Gateway in Consul - -### spring.cloud.consul.discovery.health-check-path - -- **Typ**: String -- **Default**: /actuator/health -- **Beschreibung**: Pfad für Consul Health Checks - -### spring.cloud.consul.discovery.health-check-interval - -- **Typ**: Duration -- **Default**: 10s -- **Beschreibung**: Intervall für Health Checks - -### spring.cloud.consul.discovery.instance-id - -- **Typ**: String -- **Default**: ${spring.application.name}-${server.port}-${random.uuid} -- **Beschreibung**: Eindeutige Instanz-ID für Service Discovery - -**Beispiel:** - -```yaml -spring: - cloud: - consul: - host: ${CONSUL_HOST:localhost} - port: ${CONSUL_PORT:8500} - enabled: ${CONSUL_ENABLED:true} - discovery: - enabled: ${CONSUL_ENABLED:true} - register: ${CONSUL_ENABLED:true} - health-check-path: /actuator/health - health-check-interval: 10s - instance-id: ${spring.application.name}-${server.port}-${random.uuid} -``` - ---- - -## Spring Cloud Gateway - -### Verbindungskonfiguration - -#### spring.cloud.gateway.server.webflux.httpclient.connect-timeout - -- **Typ**: Integer (Millisekunden) -- **Default**: 5000 -- **Beschreibung**: Timeout für Backend-Verbindungen - -#### spring.cloud.gateway.server.webflux.httpclient.response-timeout - -- **Typ**: Duration -- **Default**: 30s -- **Beschreibung**: Timeout für Backend-Responses - -#### spring.cloud.gateway.server.webflux.httpclient.pool.max-idle-time - -- **Typ**: Duration -- **Default**: 15s -- **Beschreibung**: Max. Idle-Zeit für Verbindungen im Pool - -#### spring.cloud.gateway.server.webflux.httpclient.pool.max-life-time - -- **Typ**: Duration -- **Default**: 60s -- **Beschreibung**: Max. Lebensdauer einer Verbindung - -**Beispiel:** - -```yaml -spring: - cloud: - gateway: - server: - webflux: - httpclient: - connect-timeout: 5000 - response-timeout: 30s - pool: - max-idle-time: 15s - max-life-time: 60s -``` - -### Default Filters - -Diese Filter werden auf **alle** Routen angewendet: - -1. **DedupeResponseHeader**: Entfernt doppelte CORS-Header -2. **CircuitBreaker**: Default Circuit Breaker mit Fallback -3. **Retry**: Automatische Wiederholung bei Fehlern -4. **Security Headers**: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, etc. -5. **Cache-Control**: No-cache Header für alle Responses - -**Beispiel:** - -```yaml -spring: - cloud: - gateway: - default-filters: - - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin - - name: CircuitBreaker - args: - name: defaultCircuitBreaker - fallbackUri: forward:/fallback - - name: Retry - args: - retries: 3 - statuses: BAD_GATEWAY,GATEWAY_TIMEOUT - methods: GET,POST,PUT,DELETE - backoff: - firstBackoff: 50ms - maxBackoff: 500ms - factor: 2 -``` - -### Routes - -Das Gateway definiert folgende Service-Routen: - -#### 1. Members Service Route - -- **Path**: `/api/members/**` -- **Service**: members-service (via Consul) -- **Circuit Breaker**: membersCircuitBreaker -- **Fallback**: /fallback/members - -#### 2. Horses Service Route - -- **Path**: `/api/horses/**` -- **Service**: horses-service (via Consul) -- **Circuit Breaker**: horsesCircuitBreaker -- **Fallback**: /fallback/horses - -#### 3. Events Service Route - -- **Path**: `/api/events/**` -- **Service**: events-service (via Consul) -- **Circuit Breaker**: eventsCircuitBreaker -- **Fallback**: /fallback/events - -#### 4. Masterdata Service Route - -- **Path**: `/api/masterdata/**` -- **Service**: masterdata-service (via Consul) -- **Circuit Breaker**: masterdataCircuitBreaker -- **Fallback**: /fallback/masterdata - -#### 5. Auth Service Route - -- **Path**: `/api/auth/**` -- **Service**: auth-service (via Consul) -- **Circuit Breaker**: authCircuitBreaker -- **Fallback**: /fallback/auth - -#### 6. Ping Service Route - -- **Path**: `/api/ping/**` -- **Service**: ping-service (via Consul) -- **No Circuit Breaker**: Optional service - -**Beispiel einer Route:** - -```yaml -spring: - cloud: - gateway: - routes: - - id: members-service-route - uri: lb://members-service # lb = Load Balanced via Consul - predicates: - - Path=/api/members/** - filters: - - StripPrefix=1 # Entfernt /api vom Pfad - - name: CircuitBreaker - args: - name: membersCircuitBreaker - fallbackUri: forward:/fallback/members -``` - ---- - -## Circuit Breaker (Resilience4j) - -### Default Konfiguration - -#### resilience4j.circuitbreaker.configs.default.registerHealthIndicator - -- **Typ**: Boolean -- **Default**: true -- **Beschreibung**: Registriert Circuit Breaker im Health Endpoint - -#### resilience4j.circuitbreaker.configs.default.slidingWindowSize - -- **Typ**: Integer -- **Default**: 100 -- **Beschreibung**: Größe des Sliding Window für Fehlerrate-Berechnung - -#### resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls - -- **Typ**: Integer -- **Default**: 20 -- **Beschreibung**: Mindestanzahl an Calls bevor Circuit Breaker aktiviert wird - -#### resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState - -- **Typ**: Integer -- **Default**: 3 -- **Beschreibung**: Anzahl Test-Calls im Half-Open State - -#### resilience4j.circuitbreaker.configs.default.waitDurationInOpenState - -- **Typ**: Duration -- **Default**: 5s -- **Beschreibung**: Wartezeit bevor von Open zu Half-Open gewechselt wird - -#### resilience4j.circuitbreaker.configs.default.failureRateThreshold - -- **Typ**: Integer (Prozent) -- **Default**: 50 -- **Beschreibung**: Fehlerrate-Schwelle für Circuit Breaker Aktivierung - -### Service-spezifische Circuit Breaker - -Jeder Service hat einen eigenen Circuit Breaker mit angepasster Konfiguration: - -| Service | Sliding Window | Failure Threshold | Besonderheit | -|---------|---------------|-------------------|--------------| -| members-service | 50 | 50% | Standard | -| horses-service | 50 | 50% | Standard | -| events-service | 75 | 50% | Größeres Window | -| masterdata-service | 30 | 50% | Kleineres Window | -| auth-service | 20 | 30% | Sensitiverer Threshold | - -**Beispiel:** - -```yaml -resilience4j: - circuitbreaker: - instances: - authCircuitBreaker: - baseConfig: default - slidingWindowSize: 20 - failureRateThreshold: 30 # Auth ist kritisch -> niedrigerer Threshold -``` - ---- - -## Management & Monitoring - -### Exposed Endpoints - -#### management.endpoints.web.exposure.include - -- **Typ**: Comma-separated String -- **Default**: health,info,metrics,prometheus,gateway,circuitbreakers -- **Beschreibung**: Öffentlich verfügbare Actuator Endpoints - -**Verfügbare Endpoints:** - -- `/actuator/health` - Service Health Status -- `/actuator/info` - Service Informationen -- `/actuator/metrics` - Micrometer Metriken -- `/actuator/prometheus` - Prometheus Scrape Endpoint -- `/actuator/gateway` - Gateway Routes & Filters -- `/actuator/circuitbreakers` - Circuit Breaker Status - -### Health Endpoint - -#### management.endpoint.health.show-details - -- **Typ**: String -- **Default**: always -- **Mögliche Werte**: never, when-authorized, always -- **Beschreibung**: Zeigt detaillierte Health-Informationen - -#### management.endpoint.health.show-components - -- **Typ**: Boolean -- **Default**: always -- **Beschreibung**: Zeigt Health-Komponenten - -#### management.endpoint.health.probes.enabled - -- **Typ**: Boolean -- **Default**: true -- **Beschreibung**: Aktiviert Kubernetes Liveness/Readiness Probes - -### Metrics - -#### management.metrics.tags - -- **Beschreibung**: Globale Tags für alle Metriken -- **Standard Tags**: - - application: ${spring.application.name} - - environment: ${spring.profiles.active} - - instance: ${spring.cloud.consul.discovery.instance-id} - - service: gateway - - component: infrastructure - - gateway: api-gateway - -#### management.metrics.distribution.percentiles-histogram.http.server.requests - -- **Typ**: Boolean -- **Default**: true -- **Beschreibung**: Aktiviert Histogram für Request-Zeiten - -#### management.metrics.distribution.percentiles.http.server.requests - -- **Typ**: Array[Double] -- **Default**: [0.5, 0.90, 0.95, 0.99] -- **Beschreibung**: Percentile-Werte für Request-Zeiten - -### Tracing - -#### management.tracing.enabled - -- **Typ**: Boolean -- **Default**: false -- **Environment Variable**: `TRACING_ENABLED` -- **Beschreibung**: Aktiviert Distributed Tracing - -#### management.tracing.sampling.probability - -- **Typ**: Double (0.0 - 1.0) -- **Default**: 1.0 -- **Environment Variable**: `TRACING_SAMPLING_PROBABILITY` -- **Beschreibung**: Sampling-Rate für Traces (1.0 = 100%) - -#### management.zipkin.tracing.endpoint - -- **Typ**: URL -- **Default**: -- **Environment Variable**: `ZIPKIN_TRACING_ENDPOINT` -- **Beschreibung**: Zipkin Server URL - -**Beispiel:** - -```yaml -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus,gateway,circuitbreakers - endpoint: - health: - show-details: always - probes: - enabled: true - tracing: - enabled: ${TRACING_ENABLED:false} - sampling: - probability: ${TRACING_SAMPLING_PROBABILITY:1.0} - zipkin: - tracing: - endpoint: ${ZIPKIN_TRACING_ENDPOINT:http://localhost:9411/api/v2/spans} -``` - ---- - -## Security - -Die Security-Konfiguration erfolgt über Custom Properties unter `gateway.security`: - -### gateway.security.publicPaths - -- **Typ**: Array[String] -- **Default**: ["/", "/fallback/**", "/actuator/**", "/webjars/**", "/v3/api-docs/**", "/api/auth/**"] -- **Beschreibung**: Pfade, die ohne Authentifizierung zugänglich sind - -### gateway.security.cors.allowedOriginPatterns - -- **Typ**: Array[String] -- **Default**: ["http://localhost:[*]", "https://*.meldestelle.at"] -- **Beschreibung**: Erlaubte Origin-Patterns für CORS - -### gateway.security.cors.allowedMethods - -- **Typ**: Array[String] -- **Default**: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"] -- **Beschreibung**: Erlaubte HTTP-Methoden - -### gateway.security.cors.allowedHeaders - -- **Typ**: Array[String] -- **Default**: ["*"] -- **Beschreibung**: Erlaubte Request-Headers - -### gateway.security.cors.exposedHeaders - -- **Typ**: Array[String] -- **Default**: ["X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"] -- **Beschreibung**: Headers die an Client exponiert werden - -### gateway.security.cors.allowCredentials - -- **Typ**: Boolean -- **Default**: true -- **Beschreibung**: Erlaubt Credentials (Cookies, Auth-Header) - -### gateway.security.cors.maxAge - -- **Typ**: Duration -- **Default**: 1h -- **Beschreibung**: Cache-Zeit für CORS Preflight-Requests - -**Beispiel:** - -```yaml -gateway: - security: - publicPaths: - - "/" - - "/actuator/**" - - "/api/auth/**" - cors: - allowedOriginPatterns: - - "http://localhost:[*]" - - "https://*.meldestelle.at" - allowedMethods: - - GET - - POST - - PUT - - DELETE - allowCredentials: true - maxAge: 1h -``` - -### JWT Configuration - -#### spring.security.oauth2.resourceserver.jwt.jwk-set-uri - -- **Typ**: URL -- **Environment Variable**: `KEYCLOAK_JWK_SET_URI` -- **Beschreibung**: Keycloak JWK Set URI für JWT-Validierung -- **Beispiel**: - ---- - -## Logging - -### logging.level - -- **Beschreibung**: Log-Level für verschiedene Pakete - -**Standard Log-Levels:** - -- `org.springframework.cloud.gateway`: INFO -- `org.springframework.cloud.loadbalancer`: DEBUG -- `org.springframework.cloud.consul`: INFO -- `at.mocode.infrastructure.gateway`: DEBUG -- `io.github.resilience4j`: INFO -- `reactor.netty.http.client`: INFO -- `org.springframework.security`: WARN -- `org.springframework.web`: INFO - -### logging.pattern.console - -- **Beschreibung**: Console-Log-Pattern mit Farben und Correlation-ID - -### logging.pattern.file - -- **Beschreibung**: File-Log-Pattern ohne Farben - -### logging.file.name - -- **Typ**: String -- **Default**: infrastructure/gateway/logs/gateway.log -- **Beschreibung**: Log-Datei Pfad - -### logging.logback.rollingpolicy - -- **clean-history-on-start**: true -- **max-file-size**: 100MB -- **total-size-cap**: 1GB -- **max-history**: 30 (Tage) - -**Beispiel:** - -```yaml -logging: - level: - at.mocode.infrastructure.gateway: DEBUG - org.springframework.cloud.gateway: INFO - file: - name: infrastructure/gateway/logs/gateway.log - logback: - rollingpolicy: - max-file-size: 100MB - max-history: 30 -``` - ---- - -## Umgebungsvariablen Übersicht - -### Kritische Variablen für Produktion - -| Variable | Beschreibung | Default | -|----------|--------------|---------| -| `GATEWAY_PORT` | Gateway Port | 8081 | -| `CONSUL_HOST` | Consul Server | localhost | -| `CONSUL_PORT` | Consul Port | 8500 | -| `CONSUL_ENABLED` | Consul Aktivieren | true | -| `GATEWAY_ADMIN_USER` | Admin Username | admin | -| `GATEWAY_ADMIN_PASSWORD` | Admin Password | admin | -| `KEYCLOAK_JWK_SET_URI` | Keycloak JWK URI | ... | -| `TRACING_ENABLED` | Tracing aktivieren | false | -| `ZIPKIN_TRACING_ENDPOINT` | Zipkin Server | ... | -| `SPRING_PROFILES_ACTIVE` | Spring Profil | dev | - ---- - -## Profile-spezifische Konfiguration - -Das Gateway unterstützt verschiedene Spring Profile: - -### dev (Development) - -- Detailliertes Logging -- Alle Monitoring-Endpunkte verfügbar -- Tracing optional - -### test - -- Reduziertes Logging -- Test-spezifische Timeouts -- In-Memory Services optional - -### prod (Production) - -- Production-ready Logging -- Sichere Credentials erforderlich -- Tracing empfohlen -- Rate Limiting aktiviert - -**Beispiel für profile-spezifische Datei:** - -```yaml -# application-prod.yml -spring: - security: - user: - name: ${GATEWAY_ADMIN_USER} # Muss gesetzt sein! - password: ${GATEWAY_ADMIN_PASSWORD} # Muss gesetzt sein! - -management: - tracing: - enabled: true - sampling: - probability: 0.1 # 10% Sampling in Production - -logging: - level: - at.mocode.infrastructure.gateway: INFO # Weniger Logs -``` - ---- - -## Best Practices - -1. **Umgebungsvariablen verwenden**: Nie Credentials in application.yml hardcoden -2. **Profile nutzen**: Separate Konfigurationen für dev/test/prod -3. **Health Checks aktivieren**: Für Consul und Kubernetes -4. **Tracing in Production**: Mindestens 10% Sampling -5. **Monitoring exportieren**: Prometheus-Endpunkt für Grafana -6. **Circuit Breaker tunen**: An Service-Charakteristiken anpassen -7. **CORS restriktiv**: Nur benötigte Origins erlauben -8. **Log Rotation**: Verhindert volle Festplatten - ---- - -## Troubleshooting - -### Gateway startet nicht - -- ✅ Prüfen: Consul erreichbar? -- ✅ Prüfen: Port 8081 frei? -- ✅ Prüfen: Keycloak erreichbar? (Optional) - -### Service nicht erreichbar - -- ✅ Prüfen: Service in Consul registriert? -- ✅ Prüfen: Circuit Breaker offen? -- ✅ Prüfen: Health Check erfolgreich? - -### CORS-Fehler - -- ✅ Prüfen: Origin in allowedOriginPatterns? -- ✅ Prüfen: Methode in allowedMethods? -- ✅ Prüfen: allowCredentials korrekt? - -### Hohe Latenz - -- ✅ Prüfen: response-timeout zu hoch? -- ✅ Prüfen: Backend-Services langsam? -- ✅ Prüfen: Connection Pool ausgeschöpft? - ---- - -## Weitere Ressourcen - -- [Gateway README](README-INFRA-GATEWAY.md) -- [Spring Cloud Gateway Dokumentation](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/) -- [Resilience4j Dokumentation](https://resilience4j.readme.io/) -- [Consul Dokumentation](https://www.consul.io/docs) diff --git a/backend/gateway/gateway/README-INFRA-GATEWAY.md b/backend/gateway/gateway/README-INFRA-GATEWAY.md deleted file mode 100644 index b6515d39..00000000 --- a/backend/gateway/gateway/README-INFRA-GATEWAY.md +++ /dev/null @@ -1,442 +0,0 @@ -# Infrastructure/Gateway Module - Comprehensive Documentation - -## Überblick - -Das API-Gateway ist der zentrale und einzige öffentliche Einstiegspunkt für alle Anfragen von externen Clients (z.B. Web-Anwendung, Desktop-Anwendung, mobile Apps) an das Meldestelle-System. Es fungiert als "Pförtner" für die gesamte Microservice-Landschaft und wurde zu einem vollwertigen, produktionstauglichen API Gateway mit modernen Best Practices erweitert. - -**Wichtiger Grundsatz**: Kein externer Client sollte jemals direkt mit einem internen Microservice kommunizieren. Alle Anfragen laufen über das Gateway. - -## Architektur und Technologie - -Das Gateway ist als eigenständiger Spring Boot Service implementiert und nutzt Spring Cloud Gateway als technologische Grundlage. Spring Cloud Gateway ist ein reaktives, nicht-blockierendes Framework, das sich nahtlos in das Spring-Ökosystem integriert. - -### Technologie-Stack - -- **Spring Boot 3.x** - Moderne Spring Boot Anwendung -- **Spring Cloud Gateway** - Reaktives Gateway Framework -- **Spring WebFlux** - Reaktive Web-Programmierung mit Netty -- **Resilience4j** - Circuit Breaker Pattern Implementation -- **Consul** - Service Discovery und Health Checks -- **Micrometer + Prometheus** - Umfassende Metriken und Monitoring -- **JWT** - Token-basierte Authentifizierung -- **Reactive Streams** - Non-blocking I/O für optimale Performance - -## Hauptverantwortlichkeiten - -Das Gateway handhabt alle Cross-Cutting Concerns (übergreifende Belange), die für mehrere oder alle Microservices gelten und entlastet damit die Fach-Services von technischen Aufgaben. - -### 1. Dynamisches Routing - -- **Service Discovery Integration**: Vollständige Consul Integration für automatische Service-Erkennung -- **Load Balancing**: Intelligente Lastverteilung zwischen Service-Instanzen -- **Health-basiertes Routing**: Weiterleitung nur an gesunde Service-Instanzen - -**Verfügbare Routen**: - -- `/api/members/**` → members-service -- `/api/horses/**` → horses-service -- `/api/events/**` → events-service -- `/api/masterdata/**` → masterdata-service -- `/api/auth/**` → auth-service -- `/api/ping/**` → ping-service - -### 2. Sicherheit und Authentifizierung - -- **JWT Security Enforcement**: Validierung von Bearer Tokens für alle geschützten Endpunkte -- **Public Path Exemptions**: Konfigurierbare öffentliche Pfade (`/`, `/health`, `/actuator/**`, `/api/auth/login`) -- **User Context Injection**: Automatische Weiterleitung von User-ID und Rolle an Backend Services -- **Standardisierte Fehlerbehandlung**: Strukturierte 401 Unauthorized Responses - -### 3. Rate Limiting - -- **Intelligentes Rate Limiting** basierend auf User-Typ: - - **Anonymous Users**: 50 Anfragen pro Minute - - **Authenticated Users**: 200 Anfragen pro Minute - - **Admin Users**: 500 Anfragen pro Minute -- **IP-basierte Limits**: Schutz vor DDoS-Attacken -- **Custom Headers**: X-RateLimit-* Header für Client-Information - -### 4. Circuit Breaker und Resilienz - -- **Service-spezifische Circuit Breaker**: Resilience4j Integration für jeden Backend Service -- **Fallback Mechanismen**: Benutzerfreundliche Fehlermeldungen bei Service-Ausfällen -- **Retry Logic**: Automatische Wiederholungen bei transienten Fehlern -- **Graceful Degradation**: Systembetrieb auch bei partiellen Service-Ausfällen - -### 5. Monitoring und Observability - -Das Gateway implementiert umfassende Observability durch eine vollständig integrierte Micrometer-basierte Metriken-Architektur. - -#### Automatische Metriken-Erfassung (GatewayMetricsConfig) - -- **Request Duration Tracking**: Automatische Messung aller Request-Response Zyklen - - Metric: `gateway_request_duration` (Timer) - - Tags: method, path, status, status_series - - Percentile-basierte Auswertung (P50, P90, P95, P99) -- **Error Rate Monitoring**: Detailliertes Error-Tracking für 4xx/5xx Responses - - Metric: `gateway_errors_total` (Counter) - - Tags: method, path, status, status_series, error_type - - Unterscheidung zwischen client_error und server_error -- **Request Volume Tracking**: Vollständige Request-Volumen Überwachung - - Metric: `gateway_requests_total` (Counter) - - Tags: method, path für detaillierte Analyse -- **Circuit Breaker Events**: Monitoring von Resilience-Pattern Events - - Metric: `gateway_circuit_breaker_events_total` (Counter) - - Integration mit Resilience4j Circuit Breaker Status - -#### Intelligente Pfad-Normalisierung - -- **Kardinalitäts-Kontrolle**: Automatische Normalisierung von dynamischen Pfaden - - `/api/horses/123` → `/api/horses/{id}` - - UUID-Pattern → `/{uuid}` - - Sehr lange Pfade werden gekürzt (100+ Zeichen) - -#### Health Monitoring Integration - -- **Downstream Service Health**: Umfassende Überwachung aller Backend Services - - Kritische Services: Members, Horses, Events, Masterdata, Auth - - Optionale Services: Ping Service - - Circuit Breaker Status Integration -- **Distributed Tracing**: Korrelations-ID basiertes Request-Tracking -- **Strukturierte Logs**: JSON-Format für maschinelle Auswertung - -#### Prometheus Export - -- **Automatischer Export**: Alle Metriken werden automatisch an Prometheus exportiert -- **Common Tags**: Alle Metriken erhalten automatisch Service- und Component-Tags -- **Filter-Optimierung**: Rauschen-reduzierende Metrik-Filter für interne Spring/Netty Metriken - -### 6. CORS-Management - -- **Produktionstaugliche CORS-Konfiguration**: - - Erlaubte Origins: `https://*.meldestelle.at`, `http://localhost:*` - - Alle HTTP-Methoden (GET, POST, PUT, DELETE, PATCH, OPTIONS) - - Credential-Support für authentifizierte Anfragen - -## Implementierte Optimierungen - -### Gateway-Konfiguration (application.yml) - -✅ **Vollständige Service-Routen**: Routing für alle Business Services -✅ **Circuit Breaker Integration**: Service-spezifische Resilience4j Konfigurationen -✅ **Connection Pooling**: Optimierte HTTP-Client-Konfiguration -✅ **Security Headers**: Umfassende Sicherheits-Header (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection) -✅ **Enhanced Logging**: Strukturierte Logs mit Korrelations-IDs und Performance-Daten - -### Health Monitoring (GatewayHealthIndicator.kt) - -✅ **Downstream Service Monitoring**: Überwachung aller kritischen Services -✅ **Service Discovery Integration**: Consul-basierte Service-Erkennung -✅ **Test-Environment Handling**: Graceful Degradation in Test-Umgebungen -✅ **Detailliertes Error Reporting**: Umfassende Statusinformationen - -### Build-Optimierungen (build.gradle.kts) - -✅ **SINGLE SOURCE OF TRUTH**: Alle Dependencies über libs.versions.toml -✅ **Build Info Generation**: Automatische Build-Metadaten -✅ **Modern Kotlin Compiler**: Optimierte Compiler-Einstellungen -✅ **Dependency Optimization**: Bereinigung redundanter Dependencies - -### Docker-Optimierungen (Dockerfile) - -✅ **Multi-Stage Build**: Spring Boot Layer-Extraktion für 90%+ besseres Caching -✅ **Security Hardening**: Non-root User, Security Updates -✅ **OCI Compliance**: Vollständige Container-Metadaten -✅ **Production-Ready**: Optimierte JVM-Settings für Container-Umgebung - -### Metriken-Integration (GatewayMetricsConfig.kt) - -✅ **Comprehensive Micrometer Integration**: Vollständige Metriken-Erfassung mit automatischem Prometheus Export -✅ **Request/Response Time Tracking**: Detaillierte Performance-Metriken mit Percentile-Auswertung -✅ **Error Rate Monitoring**: Intelligente Fehler-Klassifikation und -Tracking -✅ **Path Normalization**: Kardinalitäts-kontrolle für dynamische API-Pfade -✅ **Circuit Breaker Metrics**: Integration mit Resilience4j Event-Tracking -✅ **Custom Business Metrics**: Erweiterbare Metrik-Architektur für fachliche KPIs - -### Dokumentation - -✅ **OpenAPI 3.0.3 Spezifikation**: Vollständige API-Dokumentation mit Members Service -✅ **Interactive Swagger UI**: Modern dokumentierte API-Endpunkte -✅ **Static HTML Documentation**: Responsive, moderne Dokumentations-Website -✅ **Health Monitoring Integration**: Real-time Status-Informationen - -## Performance und Reliability - -### Netty Server Optimierungen - -- **Connection Timeouts**: 5 Sekunden für optimale Responsiveness -- **Idle Timeout**: 15 Sekunden für effiziente Resource-Nutzung -- **Elastic Connection Pool**: Automatische Skalierung basierend auf Load - -### Circuit Breaker Konfiguration - -- **Sliding Window**: 100 Anfragen für Default, service-spezifische Anpassungen -- **Failure Rate Threshold**: 50% für Standard-Services, 30% für Auth-Service -- **Half-Open State**: 3 Test-Anfragen für Service-Recovery - -### JVM Optimierungen (Container) - -```bash -JAVA_OPTS="-server -Xmx512m -Xms256m -XX:+UseG1GC - -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" -``` - -## API Gateway Request Flow - -Ein typischer Anfrage-Flow: - -1. **Client Request**: `https://api.meldestelle.at/api/members/123` -2. **Gateway Empfang**: Anfrage wird vom Spring Cloud Gateway empfangen -3. **Filter-Pipeline**: - - **Security Filter**: JWT-Validierung - - **Rate Limiting Filter**: Anfrage-Limits prüfen - - **Correlation Filter**: Trace-ID generieren - - **Logging Filter**: Request-Details erfassen -4. **Service Discovery**: Consul-Abfrage für verfügbare `members-service` Instanzen -5. **Load Balancing**: Intelligente Auswahl einer gesunden Instanz -6. **Circuit Breaker**: Überwachung der Service-Verfügbarkeit -7. **Request Forwarding**: Weiterleitung an Backend Service -8. **Response Processing**: Antwort-Verarbeitung und Header-Enrichment -9. **Client Response**: Strukturierte Antwort an Client - -## Monitoring und Health Checks - -### Actuator Endpunkte - -- `/actuator/health` - Umfassender Health Status aller Services -- `/actuator/metrics` - Prometheus-kompatible Metriken -- `/actuator/info` - Anwendungs- und Build-Informationen -- `/actuator/gateway` - Gateway-spezifische Routing-Informationen -- `/actuator/circuitbreakers` - Circuit Breaker Status - -### Key Performance Indicators (KPIs) - -#### Automatisch erfasste Metriken - -- **Request Throughput**: `gateway_requests_total` - Anfragen pro Sekunde nach Method/Path -- **Response Times**: `gateway_request_duration` - P50, P90, P95, P99 Percentile nach Status -- **Error Rates**: `gateway_errors_total` - 4xx/5xx Response Codes mit Error-Type Klassifikation -- **Circuit Breaker Events**: `gateway_circuit_breaker_events_total` - Resilience Pattern Aktivierungen -- **Service Availability**: Upstream Service Health via Health Indicators - -#### Verfügbare Metric Tags für detaillierte Analyse - -- **method**: HTTP-Method (GET, POST, PUT, DELETE, etc.) -- **path**: Normalisierter API-Pfad (z.B. `/api/horses/{id}`) -- **status**: HTTP-Status-Code (200, 404, 500, etc.) -- **status_series**: Status-Gruppe (2xx, 3xx, 4xx, 5xx) -- **error_type**: Fehler-Klassifikation (client_error, server_error) -- **service**: Automatisches "gateway" Tag -- **component**: Automatisches "infrastructure" Tag - -#### Prometheus Query Beispiele - -```promql -# Request Rate pro Endpunkt -rate(gateway_requests_total[5m]) - -# 95. Percentile Response Time -histogram_quantile(0.95, rate(gateway_request_duration_bucket[5m])) - -# Error Rate nach Service -rate(gateway_errors_total[5m]) / rate(gateway_requests_total[5m]) - -# Circuit Breaker Aktivierungen -increase(gateway_circuit_breaker_events_total[1h]) -``` - -## Security Features - -### JWT Authentication - -- **Bearer Token Validation**: Automatische JWT-Verifikation -- **Role Extraction**: User-Rolle für Backend Services verfügbar -- **Token Refresh**: Unterstützung für Token-Erneuerung -- **Public Endpoints**: Konfigurierbare Ausnahmen für öffentliche APIs - -### Security Headers - -```yaml -X-Content-Type-Options: nosniff -X-Frame-Options: DENY -X-XSS-Protection: 1; mode=block -Referrer-Policy: strict-origin-when-cross-origin -Cache-Control: no-cache, no-store, must-revalidate -``` - -## Development und Testing - -### Local Development - -**WICHTIG:** Alle Befehle müssen aus dem Projekt-Root-Verzeichnis (`/home/stefan/WsMeldestelle/Meldestelle`) ausgeführt werden. - -```bash -# Sicherstellen, dass Sie im richtigen Verzeichnis sind -cd /home/stefan/WsMeldestelle/Meldestelle - -# Gateway starten -./gradlew :infrastructure:gateway:bootRun - -# Mit Docker -docker build -t meldestelle/gateway:latest -f infrastructure/gateway/Dockerfile . -docker run -p 8081:8081 meldestelle/gateway:latest -``` - -📖 **Detaillierte Startup-Anleitung:** Siehe `GATEWAY-STARTUP-GUIDE.md` im Projekt-Root für vollständige Befehle und Fehlerbehebung. - -### Testing - -```bash -# Unit Tests -./gradlew :infrastructure:gateway:test - -# Integration Tests (mit Testcontainers) -./gradlew :infrastructure:gateway:integrationTest -``` - -## Konfiguration - -### Environment Variables - -```bash -SPRING_PROFILES_ACTIVE=prod -CONSUL_HOST=consul.meldestelle.at -CONSUL_PORT=8500 -GATEWAY_ADMIN_USER=admin -GATEWAY_ADMIN_PASSWORD=secure-password -``` - -### Profile-spezifische Konfiguration - -- **dev**: Entwicklungsumgebung mit Debug-Logging -- **test**: Test-Umgebung mit Mock Services -- **prod**: Produktionsumgebung mit allen Security Features - -## Deployment - -### Docker Deployment - -```bash -# Multi-stage Build mit Layer Caching -docker build -t meldestelle/gateway:1.0.0 \ - -f infrastructure/gateway/Dockerfile . - -# Container starten -docker run -d \ - --name gateway \ - -p 8081:8081 \ - -e SPRING_PROFILES_ACTIVE=prod \ - -e CONSUL_HOST=consul \ - meldestelle/gateway:1.0.0 -``` - -### Kubernetes Deployment - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: api-gateway -spec: - replicas: 3 - selector: - matchLabels: - app: api-gateway - template: - spec: - containers: - - name: gateway - image: meldestelle/gateway:1.0.0 - ports: - - containerPort: 8081 - livenessProbe: - httpGet: - path: /actuator/health - port: 8081 - initialDelaySeconds: 90 - periodSeconds: 30 -``` - -## Troubleshooting - -### Häufige Probleme - -**Service Discovery Issues** - -- Consul Connectivity prüfen -- Service Registration Status überprüfen -- DNS Resolution testen - -**Circuit Breaker Activation** - -- Service Health Status prüfen -- Failure Rate Threshold analysieren -- Manual Circuit Breaker Reset über Actuator - -**Performance Issues** - -- Connection Pool Metrics analysieren -- JVM Heap Usage monitoring -- Request Rate Limiting überprüfen - -**Metriken und Monitoring Issues** - -- Prometheus Scraping Endpunkt prüfen: `/actuator/prometheus` -- Metrics Registry Status überprüfen: `/actuator/metrics` -- GatewayMetricsWebFilter Aktivierung validieren -- Metric Tags auf Kardinalitäts-Explosion prüfen -- Path Normalization bei unerwarteten Metric-Namen - -### Logging und Debugging - -```bash -# Logs mit Korrelations-IDs -docker logs gateway | grep "correlationId" - -# Circuit Breaker Status -curl http://localhost:8081/actuator/circuitbreakers - -# Health Details -curl http://localhost:8081/actuator/health -``` - -## Zukünftige Erweiterungen - -### Geplante Features - -- **OAuth2/OIDC Integration**: Erweiterte Authentifizierung -- **GraphQL Gateway**: Unified GraphQL Interface -- **Caching Layer**: Redis-basiertes Response Caching -- **Request/Response Transformation**: Dynamic Content Modification - -### Performance Optimierungen - -- **HTTP/2 Support**: Moderne Protocol-Unterstützung -- **Connection Pooling Tuning**: Erweiterte Pool-Konfiguration -- **Reactive Streams Optimization**: Backpressure Handling - -## Dokumentation und Ressourcen - -### API Dokumentation - -- **Swagger UI**: `/swagger` - Interactive API Documentation -- **OpenAPI Spec**: `/openapi` - Machine-readable API Specification -- **Static Documentation**: `/docs` - Comprehensive Documentation Hub - -### Monitoring Dashboards - -- **Health Status**: `/actuator/health` - Real-time Service Health -- **Metrics**: `/actuator/metrics` - Prometheus Metrics -- **Gateway Routes**: `/actuator/gateway/routes` - Active Route Information - ---- - -**Letzte Aktualisierung**: 14. August 2025 - -**Version**: 1.1.0 - -**Maintainer**: Meldestelle Development Team - ---- - -Diese Dokumentation wurde umfassend aktualisiert und um die neue Micrometer Metrics Integration (GatewayMetricsConfig.kt) erweitert. Sie dokumentiert alle implementierten Optimierungen einschließlich der vollständigen Observability-Architektur mit automatischer Request/Response Zeit Messung, Error Rate Tracking und Custom Business Metrics. diff --git a/infrastructure/cache/cache-api/build.gradle.kts b/backend/infrastructure/cache/cache-api/build.gradle.kts similarity index 100% rename from infrastructure/cache/cache-api/build.gradle.kts rename to backend/infrastructure/cache/cache-api/build.gradle.kts diff --git a/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheConfiguration.kt b/backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheConfiguration.kt similarity index 100% rename from infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheConfiguration.kt rename to backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheConfiguration.kt diff --git a/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheEntry.kt b/backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheEntry.kt similarity index 100% rename from infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheEntry.kt rename to backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheEntry.kt diff --git a/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheSerializer.kt b/backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheSerializer.kt similarity index 100% rename from infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheSerializer.kt rename to backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/CacheSerializer.kt diff --git a/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/ConnectionStatus.kt b/backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/ConnectionStatus.kt similarity index 100% rename from infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/ConnectionStatus.kt rename to backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/ConnectionStatus.kt diff --git a/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCache.kt b/backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCache.kt similarity index 100% rename from infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCache.kt rename to backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCache.kt diff --git a/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCacheExtensions.kt b/backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCacheExtensions.kt similarity index 100% rename from infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCacheExtensions.kt rename to backend/infrastructure/cache/cache-api/src/main/kotlin/at/mocode/infrastructure/cache/api/DistributedCacheExtensions.kt diff --git a/infrastructure/cache/redis-cache/build.gradle.kts b/backend/infrastructure/cache/redis-cache/build.gradle.kts similarity index 95% rename from infrastructure/cache/redis-cache/build.gradle.kts rename to backend/infrastructure/cache/redis-cache/build.gradle.kts index fd0b6a79..5e4ce942 100644 --- a/infrastructure/cache/redis-cache/build.gradle.kts +++ b/backend/infrastructure/cache/redis-cache/build.gradle.kts @@ -27,7 +27,7 @@ dependencies { // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. api(platform(projects.platform.platformBom)) // Implementiert die provider-agnostische Caching-API. - implementation(projects.infrastructure.cache.cacheApi) + implementation(projects.backend.infrastructure.cache.cacheApi) // OPTIMIERUNG: Verwendung des `redis-cache`-Bundles aus libs.versions.toml. // Dieses Bundle enthält Spring Data Redis, Lettuce und Jackson-Module. implementation(libs.bundles.redis.cache) diff --git a/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/JacksonCacheSerializer.kt b/backend/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/JacksonCacheSerializer.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/JacksonCacheSerializer.kt rename to backend/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/JacksonCacheSerializer.kt diff --git a/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisConfiguration.kt b/backend/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisConfiguration.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisConfiguration.kt rename to backend/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisConfiguration.kt diff --git a/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCache.kt b/backend/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCache.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCache.kt rename to backend/infrastructure/cache/redis-cache/src/main/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCache.kt diff --git a/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheConfigurationTest.kt b/backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheConfigurationTest.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheConfigurationTest.kt rename to backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheConfigurationTest.kt diff --git a/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheEdgeCasesTest.kt b/backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheEdgeCasesTest.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheEdgeCasesTest.kt rename to backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheEdgeCasesTest.kt diff --git a/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheIntegrationTest.kt b/backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheIntegrationTest.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheIntegrationTest.kt rename to backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheIntegrationTest.kt diff --git a/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCachePerformanceTest.kt b/backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCachePerformanceTest.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCachePerformanceTest.kt rename to backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCachePerformanceTest.kt diff --git a/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheResilienceTest.kt b/backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheResilienceTest.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheResilienceTest.kt rename to backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheResilienceTest.kt diff --git a/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheTest.kt b/backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheTest.kt similarity index 100% rename from infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheTest.kt rename to backend/infrastructure/cache/redis-cache/src/test/kotlin/at/mocode/infrastructure/cache/redis/RedisDistributedCacheTest.kt diff --git a/infrastructure/cache/redis-cache/src/test/resources/logback-test.xml b/backend/infrastructure/cache/redis-cache/src/test/resources/logback-test.xml similarity index 100% rename from infrastructure/cache/redis-cache/src/test/resources/logback-test.xml rename to backend/infrastructure/cache/redis-cache/src/test/resources/logback-test.xml diff --git a/infrastructure/event-store/event-store-api/build.gradle.kts b/backend/infrastructure/event-store/event-store-api/build.gradle.kts similarity index 100% rename from infrastructure/event-store/event-store-api/build.gradle.kts rename to backend/infrastructure/event-store/event-store-api/build.gradle.kts diff --git a/infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventSerializer.kt b/backend/infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventSerializer.kt similarity index 100% rename from infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventSerializer.kt rename to backend/infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventSerializer.kt diff --git a/infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventStore.kt b/backend/infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventStore.kt similarity index 100% rename from infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventStore.kt rename to backend/infrastructure/event-store/event-store-api/src/main/kotlin/at/mocode/infrastructure/eventstore/api/EventStore.kt diff --git a/infrastructure/event-store/redis-event-store/build.gradle.kts b/backend/infrastructure/event-store/redis-event-store/build.gradle.kts similarity index 92% rename from infrastructure/event-store/redis-event-store/build.gradle.kts rename to backend/infrastructure/event-store/redis-event-store/build.gradle.kts index ec039819..03204c29 100644 --- a/infrastructure/event-store/redis-event-store/build.gradle.kts +++ b/backend/infrastructure/event-store/redis-event-store/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen implementation(platform(projects.platform.platformBom)) // Implementiert die provider-agnostische Event-Store-API - api(projects.infrastructure.eventStore.eventStoreApi) + api(projects.backend.infrastructure.eventStore.eventStoreApi) // Benötigt Zugriff auf Core-Module für Domänen-Events und Utilities implementation(projects.core.coreDomain) implementation(projects.core.coreUtils) @@ -43,8 +43,8 @@ dependencies { testImplementation(libs.kotlinx.serialization.json) testImplementation(libs.reactor.test) // Für Integration Tests mit beiden Redis-Modulen - testImplementation(projects.infrastructure.cache.cacheApi) - testImplementation(projects.infrastructure.cache.redisCache) + testImplementation(projects.backend.infrastructure.cache.cacheApi) + testImplementation(projects.backend.infrastructure.cache.redisCache) } // === Task Configuration === diff --git a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt b/backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt rename to backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/EventStoreMetrics.kt diff --git a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializer.kt b/backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializer.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializer.kt rename to backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializer.kt diff --git a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt b/backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt rename to backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt diff --git a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStore.kt b/backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStore.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStore.kt rename to backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStore.kt diff --git a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfiguration.kt b/backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfiguration.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfiguration.kt rename to backend/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfiguration.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializerTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializerTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializerTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/JacksonEventSerializerTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisCacheAndEventStoreIntegrationTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisCacheAndEventStoreIntegrationTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisCacheAndEventStoreIntegrationTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisCacheAndEventStoreIntegrationTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumerResilienceTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumerResilienceTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumerResilienceTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumerResilienceTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfigurationTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfigurationTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfigurationTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreConfigurationTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreErrorHandlingTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreErrorHandlingTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreErrorHandlingTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreErrorHandlingTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreStreamTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreStreamTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreStreamTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreStreamTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt b/backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt similarity index 100% rename from infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt rename to backend/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt diff --git a/dockerfiles/infrastructure/gateway/Dockerfile b/backend/infrastructure/gateway/Dockerfile similarity index 97% rename from dockerfiles/infrastructure/gateway/Dockerfile rename to backend/infrastructure/gateway/Dockerfile index 736b558b..b78b0449 100644 --- a/dockerfiles/infrastructure/gateway/Dockerfile +++ b/backend/infrastructure/gateway/Dockerfile @@ -23,8 +23,8 @@ ARG VERSION FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder # Re-declare build arguments for this stage -ARG BUILD_DATE ARG VERSION +ARG BUILD_DATE LABEL stage=builder LABEL service="api-gateway" @@ -57,6 +57,7 @@ RUN chmod +x gradlew COPY platform/ platform/ COPY core/ core/ +# Copy infrastructure directories (required by settings.gradle.kts) # Copy infrastructure directories (required by settings.gradle.kts) COPY infrastructure/ infrastructure/ @@ -65,6 +66,7 @@ COPY domains/ domains/ # Copy services directories (required by settings.gradle.kts) COPY services/ services/ +COPY backend/ backend/ # Copy client directories (required by settings.gradle.kts) COPY clients/ clients/ @@ -78,16 +80,16 @@ COPY build.gradle.kts ./ # Download and cache dependencies with BuildKit cache mount (removed deprecated flag) RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ --mount=type=cache,target=/home/gradle/.gradle/wrapper \ - ./gradlew :infrastructure:gateway:dependencies --info + ./gradlew :backend:gateway:dependencies --info # Build the application with optimizations and build cache (removed deprecated flag) RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ --mount=type=cache,target=/home/gradle/.gradle/wrapper \ - ./gradlew :infrastructure:gateway:bootJar --info + ./gradlew :backend:gateway:bootJar --info # Extract JAR layers for better caching in runtime stage RUN mkdir -p build/dependency && \ - (cd build/dependency; java -Djarmode=layertools -jar /workspace/infrastructure/gateway/build/libs/*.jar extract) + (cd build/dependency; java -Djarmode=layertools -jar /workspace/backend/gateway/build/libs/*.jar extract) # =================================================================== # Runtime Stage diff --git a/backend/gateway/build.gradle.kts b/backend/infrastructure/gateway/build.gradle.kts similarity index 96% rename from backend/gateway/build.gradle.kts rename to backend/infrastructure/gateway/build.gradle.kts index 1ff18a52..74c656a9 100644 --- a/backend/gateway/build.gradle.kts +++ b/backend/infrastructure/gateway/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { // === Core Dependencies === implementation(projects.core.coreUtils) implementation(projects.platform.platformDependencies) - implementation(projects.infrastructure.monitoring.monitoringClient) + implementation(projects.backend.infrastructure.monitoring.monitoringClient) // === GATEWAY-SPEZIFISCHE ABHÄNGIGKEITEN === implementation(libs.bundles.spring.cloud.gateway) diff --git a/backend/gateway/docs/index.html b/backend/infrastructure/gateway/docs/index.html similarity index 100% rename from backend/gateway/docs/index.html rename to backend/infrastructure/gateway/docs/index.html diff --git a/backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt b/backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt similarity index 100% rename from backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt rename to backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/GatewayApplication.kt diff --git a/backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt b/backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt similarity index 100% rename from backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt rename to backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/config/GatewayConfig.kt diff --git a/backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/controller/FallbackController.kt b/backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/controller/FallbackController.kt similarity index 100% rename from backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/controller/FallbackController.kt rename to backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/controller/FallbackController.kt diff --git a/backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt b/backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt similarity index 100% rename from backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt rename to backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt diff --git a/backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/metrics/GatewayMetricsConfig.kt b/backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/metrics/GatewayMetricsConfig.kt similarity index 100% rename from backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/metrics/GatewayMetricsConfig.kt rename to backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/metrics/GatewayMetricsConfig.kt diff --git a/backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt b/backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt similarity index 100% rename from backend/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt rename to backend/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt diff --git a/backend/gateway/src/main/resources/application-keycloak.yml b/backend/infrastructure/gateway/src/main/resources/application-keycloak.yml similarity index 100% rename from backend/gateway/src/main/resources/application-keycloak.yml rename to backend/infrastructure/gateway/src/main/resources/application-keycloak.yml diff --git a/backend/gateway/src/main/resources/application.conf b/backend/infrastructure/gateway/src/main/resources/application.conf similarity index 100% rename from backend/gateway/src/main/resources/application.conf rename to backend/infrastructure/gateway/src/main/resources/application.conf diff --git a/backend/gateway/src/main/resources/application.yml b/backend/infrastructure/gateway/src/main/resources/application.yml similarity index 100% rename from backend/gateway/src/main/resources/application.yml rename to backend/infrastructure/gateway/src/main/resources/application.yml diff --git a/backend/gateway/src/main/resources/logback-spring.xml b/backend/infrastructure/gateway/src/main/resources/logback-spring.xml similarity index 100% rename from backend/gateway/src/main/resources/logback-spring.xml rename to backend/infrastructure/gateway/src/main/resources/logback-spring.xml diff --git a/backend/gateway/src/main/resources/logback.xml b/backend/infrastructure/gateway/src/main/resources/logback.xml similarity index 100% rename from backend/gateway/src/main/resources/logback.xml rename to backend/infrastructure/gateway/src/main/resources/logback.xml diff --git a/backend/gateway/src/main/resources/openapi/documentation.yaml b/backend/infrastructure/gateway/src/main/resources/openapi/documentation.yaml similarity index 100% rename from backend/gateway/src/main/resources/openapi/documentation.yaml rename to backend/infrastructure/gateway/src/main/resources/openapi/documentation.yaml diff --git a/backend/gateway/src/main/resources/static/docs/index.html b/backend/infrastructure/gateway/src/main/resources/static/docs/index.html similarity index 100% rename from backend/gateway/src/main/resources/static/docs/index.html rename to backend/infrastructure/gateway/src/main/resources/static/docs/index.html diff --git a/backend/gateway/src/main/resources/static/docs/postman/Meldestelle_API_Collection.json b/backend/infrastructure/gateway/src/main/resources/static/docs/postman/Meldestelle_API_Collection.json similarity index 100% rename from backend/gateway/src/main/resources/static/docs/postman/Meldestelle_API_Collection.json rename to backend/infrastructure/gateway/src/main/resources/static/docs/postman/Meldestelle_API_Collection.json diff --git a/backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/FallbackControllerTests.kt b/backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/FallbackControllerTests.kt similarity index 100% rename from backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/FallbackControllerTests.kt rename to backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/FallbackControllerTests.kt diff --git a/backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayApplicationTests.kt b/backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayApplicationTests.kt similarity index 100% rename from backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayApplicationTests.kt rename to backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayApplicationTests.kt diff --git a/backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt b/backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt similarity index 100% rename from backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt rename to backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayFiltersTests.kt diff --git a/backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayRoutingTests.kt b/backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayRoutingTests.kt similarity index 100% rename from backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayRoutingTests.kt rename to backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewayRoutingTests.kt diff --git a/backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewaySecurityTests.kt b/backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewaySecurityTests.kt similarity index 100% rename from backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewaySecurityTests.kt rename to backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/GatewaySecurityTests.kt diff --git a/backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt b/backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt similarity index 100% rename from backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt rename to backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt diff --git a/backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/config/TestSecurityConfig.kt b/backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/config/TestSecurityConfig.kt similarity index 100% rename from backend/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/config/TestSecurityConfig.kt rename to backend/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/config/TestSecurityConfig.kt diff --git a/backend/gateway/src/test/resources/application-dev.yml b/backend/infrastructure/gateway/src/test/resources/application-dev.yml similarity index 100% rename from backend/gateway/src/test/resources/application-dev.yml rename to backend/infrastructure/gateway/src/test/resources/application-dev.yml diff --git a/backend/gateway/src/test/resources/application-keycloak-integration-test.yml b/backend/infrastructure/gateway/src/test/resources/application-keycloak-integration-test.yml similarity index 100% rename from backend/gateway/src/test/resources/application-keycloak-integration-test.yml rename to backend/infrastructure/gateway/src/test/resources/application-keycloak-integration-test.yml diff --git a/backend/gateway/src/test/resources/application-test.yml b/backend/infrastructure/gateway/src/test/resources/application-test.yml similarity index 100% rename from backend/gateway/src/test/resources/application-test.yml rename to backend/infrastructure/gateway/src/test/resources/application-test.yml diff --git a/backend/gateway/src/test/resources/logback-test.xml b/backend/infrastructure/gateway/src/test/resources/logback-test.xml similarity index 100% rename from backend/gateway/src/test/resources/logback-test.xml rename to backend/infrastructure/gateway/src/test/resources/logback-test.xml diff --git a/backend/gateway/src/test/resources/test-init-keycloak-schema.sql b/backend/infrastructure/gateway/src/test/resources/test-init-keycloak-schema.sql similarity index 91% rename from backend/gateway/src/test/resources/test-init-keycloak-schema.sql rename to backend/infrastructure/gateway/src/test/resources/test-init-keycloak-schema.sql index 6287cefb..bd6767a0 100644 --- a/backend/gateway/src/test/resources/test-init-keycloak-schema.sql +++ b/backend/infrastructure/gateway/src/test/resources/test-init-keycloak-schema.sql @@ -1,4 +1,4 @@ --- Testcontainers init script for Keycloak schema +-- Testcontainers an init script for Keycloak schema -- Creates the schema and basic privileges for the test DB user CREATE SCHEMA IF NOT EXISTS keycloak; diff --git a/infrastructure/messaging/messaging-client/build.gradle.kts b/backend/infrastructure/messaging/messaging-client/build.gradle.kts similarity index 93% rename from infrastructure/messaging/messaging-client/build.gradle.kts rename to backend/infrastructure/messaging/messaging-client/build.gradle.kts index 5495f2a0..a9f53660 100644 --- a/infrastructure/messaging/messaging-client/build.gradle.kts +++ b/backend/infrastructure/messaging/messaging-client/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { // Stellt gemeinsame Abhängigkeiten bereit. implementation(projects.platform.platformDependencies) // Baut auf der zentralen Kafka-Konfiguration auf und erbt deren Abhängigkeiten. - implementation(projects.infrastructure.messaging.messagingConfig) + implementation(projects.backend.infrastructure.messaging.messagingConfig) // Fügt die reaktive Kafka-Implementierung hinzu (Project Reactor). implementation(libs.reactor.kafka) // Stellt alle Test-Abhängigkeiten gebündelt bereit. diff --git a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventConsumer.kt b/backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventConsumer.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventConsumer.kt rename to backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventConsumer.kt diff --git a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventPublisher.kt b/backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventPublisher.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventPublisher.kt rename to backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/EventPublisher.kt diff --git a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumer.kt b/backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumer.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumer.kt rename to backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumer.kt diff --git a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisher.kt b/backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisher.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisher.kt rename to backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisher.kt diff --git a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/MessagingError.kt b/backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/MessagingError.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/MessagingError.kt rename to backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/MessagingError.kt diff --git a/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt b/backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt rename to backend/infrastructure/messaging/messaging-client/src/main/kotlin/at/mocode/infrastructure/messaging/client/ReactiveKafkaConfig.kt diff --git a/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumerCacheTest.kt b/backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumerCacheTest.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumerCacheTest.kt rename to backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventConsumerCacheTest.kt diff --git a/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisherErrorTest.kt b/backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisherErrorTest.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisherErrorTest.kt rename to backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaEventPublisherErrorTest.kt diff --git a/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt b/backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt rename to backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaIntegrationTest.kt diff --git a/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaSecurityTest.kt b/backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaSecurityTest.kt similarity index 100% rename from infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaSecurityTest.kt rename to backend/infrastructure/messaging/messaging-client/src/test/kotlin/at/mocode/infrastructure/messaging/client/KafkaSecurityTest.kt diff --git a/infrastructure/messaging/messaging-config/build.gradle.kts b/backend/infrastructure/messaging/messaging-config/build.gradle.kts similarity index 100% rename from infrastructure/messaging/messaging-config/build.gradle.kts rename to backend/infrastructure/messaging/messaging-config/build.gradle.kts diff --git a/infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt b/backend/infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt similarity index 100% rename from infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt rename to backend/infrastructure/messaging/messaging-config/src/main/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfig.kt diff --git a/infrastructure/messaging/messaging-config/src/test/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfigTest.kt b/backend/infrastructure/messaging/messaging-config/src/test/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfigTest.kt similarity index 100% rename from infrastructure/messaging/messaging-config/src/test/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfigTest.kt rename to backend/infrastructure/messaging/messaging-config/src/test/kotlin/at/mocode/infrastructure/messaging/config/KafkaConfigTest.kt diff --git a/infrastructure/monitoring/monitoring-client/build.gradle.kts b/backend/infrastructure/monitoring/monitoring-client/build.gradle.kts similarity index 100% rename from infrastructure/monitoring/monitoring-client/build.gradle.kts rename to backend/infrastructure/monitoring/monitoring-client/build.gradle.kts diff --git a/infrastructure/monitoring/monitoring-client/src/main/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfiguration.kt b/backend/infrastructure/monitoring/monitoring-client/src/main/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfiguration.kt similarity index 100% rename from infrastructure/monitoring/monitoring-client/src/main/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfiguration.kt rename to backend/infrastructure/monitoring/monitoring-client/src/main/kotlin/at/mocode/infrastructure/monitoring/client/MonitoringClientAutoConfiguration.kt diff --git a/infrastructure/monitoring/monitoring-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/backend/infrastructure/monitoring/monitoring-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports similarity index 100% rename from infrastructure/monitoring/monitoring-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename to backend/infrastructure/monitoring/monitoring-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/infrastructure/monitoring/monitoring-client/src/main/resources/monitoring-defaults.properties b/backend/infrastructure/monitoring/monitoring-client/src/main/resources/monitoring-defaults.properties similarity index 100% rename from infrastructure/monitoring/monitoring-client/src/main/resources/monitoring-defaults.properties rename to backend/infrastructure/monitoring/monitoring-client/src/main/resources/monitoring-defaults.properties diff --git a/infrastructure/monitoring/monitoring-server/build.gradle.kts b/backend/infrastructure/monitoring/monitoring-server/build.gradle.kts similarity index 100% rename from infrastructure/monitoring/monitoring-server/build.gradle.kts rename to backend/infrastructure/monitoring/monitoring-server/build.gradle.kts diff --git a/infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt b/backend/infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt similarity index 100% rename from infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt rename to backend/infrastructure/monitoring/monitoring-server/src/main/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplication.kt diff --git a/infrastructure/monitoring/monitoring-server/src/main/resources/application.properties b/backend/infrastructure/monitoring/monitoring-server/src/main/resources/application.properties similarity index 100% rename from infrastructure/monitoring/monitoring-server/src/main/resources/application.properties rename to backend/infrastructure/monitoring/monitoring-server/src/main/resources/application.properties diff --git a/infrastructure/monitoring/monitoring-server/src/test/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplicationTest.kt b/backend/infrastructure/monitoring/monitoring-server/src/test/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplicationTest.kt similarity index 100% rename from infrastructure/monitoring/monitoring-server/src/test/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplicationTest.kt rename to backend/infrastructure/monitoring/monitoring-server/src/test/kotlin/at/mocode/infrastructure/monitoring/MonitoringServerApplicationTest.kt diff --git a/domains/events/events-service/src/test/resources/logback-test.xml b/backend/infrastructure/monitoring/monitoring-server/src/test/resources/logback-test.xml similarity index 100% rename from domains/events/events-service/src/test/resources/logback-test.xml rename to backend/infrastructure/monitoring/monitoring-server/src/test/resources/logback-test.xml diff --git a/dockerfiles/domains/events-service/Dockerfile b/backend/services/events/Dockerfile similarity index 100% rename from dockerfiles/domains/events-service/Dockerfile rename to backend/services/events/Dockerfile diff --git a/domains/events/events-api/build.gradle.kts b/backend/services/events/events-api/build.gradle.kts similarity index 100% rename from domains/events/events-api/build.gradle.kts rename to backend/services/events/events-api/build.gradle.kts diff --git a/domains/events/events-api/src/main/kotlin/at/mocode/events/api/rest/VeranstaltungController.kt b/backend/services/events/events-api/src/main/kotlin/at/mocode/events/api/rest/VeranstaltungController.kt similarity index 100% rename from domains/events/events-api/src/main/kotlin/at/mocode/events/api/rest/VeranstaltungController.kt rename to backend/services/events/events-api/src/main/kotlin/at/mocode/events/api/rest/VeranstaltungController.kt diff --git a/domains/events/events-common/build.gradle.kts b/backend/services/events/events-common/build.gradle.kts similarity index 100% rename from domains/events/events-common/build.gradle.kts rename to backend/services/events/events-common/build.gradle.kts diff --git a/domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/CreateVeranstaltungUseCase.kt b/backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/CreateVeranstaltungUseCase.kt similarity index 100% rename from domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/CreateVeranstaltungUseCase.kt rename to backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/CreateVeranstaltungUseCase.kt diff --git a/domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/DeleteVeranstaltungUseCase.kt b/backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/DeleteVeranstaltungUseCase.kt similarity index 100% rename from domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/DeleteVeranstaltungUseCase.kt rename to backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/DeleteVeranstaltungUseCase.kt diff --git a/domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/GetVeranstaltungUseCase.kt b/backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/GetVeranstaltungUseCase.kt similarity index 100% rename from domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/GetVeranstaltungUseCase.kt rename to backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/GetVeranstaltungUseCase.kt diff --git a/domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/UpdateVeranstaltungUseCase.kt b/backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/UpdateVeranstaltungUseCase.kt similarity index 100% rename from domains/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/UpdateVeranstaltungUseCase.kt rename to backend/services/events/events-common/src/main/kotlin/at/mocode/events/application/usecase/UpdateVeranstaltungUseCase.kt diff --git a/domains/events/events-domain/build.gradle.kts b/backend/services/events/events-domain/build.gradle.kts similarity index 100% rename from domains/events/events-domain/build.gradle.kts rename to backend/services/events/events-domain/build.gradle.kts diff --git a/domains/events/events-domain/src/main/kotlin/at/mocode/events/EventManagement.kt b/backend/services/events/events-domain/src/main/kotlin/at/mocode/events/EventManagement.kt similarity index 100% rename from domains/events/events-domain/src/main/kotlin/at/mocode/events/EventManagement.kt rename to backend/services/events/events-domain/src/main/kotlin/at/mocode/events/EventManagement.kt diff --git a/domains/events/events-domain/src/main/kotlin/at/mocode/events/domain/model/Veranstaltung.kt b/backend/services/events/events-domain/src/main/kotlin/at/mocode/events/domain/model/Veranstaltung.kt similarity index 100% rename from domains/events/events-domain/src/main/kotlin/at/mocode/events/domain/model/Veranstaltung.kt rename to backend/services/events/events-domain/src/main/kotlin/at/mocode/events/domain/model/Veranstaltung.kt diff --git a/domains/events/events-domain/src/main/kotlin/at/mocode/events/domain/repository/VeranstaltungRepository.kt b/backend/services/events/events-domain/src/main/kotlin/at/mocode/events/domain/repository/VeranstaltungRepository.kt similarity index 100% rename from domains/events/events-domain/src/main/kotlin/at/mocode/events/domain/repository/VeranstaltungRepository.kt rename to backend/services/events/events-domain/src/main/kotlin/at/mocode/events/domain/repository/VeranstaltungRepository.kt diff --git a/domains/events/events-infrastructure/build.gradle.kts b/backend/services/events/events-infrastructure/build.gradle.kts similarity index 100% rename from domains/events/events-infrastructure/build.gradle.kts rename to backend/services/events/events-infrastructure/build.gradle.kts diff --git a/domains/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungRepositoryImpl.kt b/backend/services/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungRepositoryImpl.kt similarity index 100% rename from domains/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungRepositoryImpl.kt rename to backend/services/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungRepositoryImpl.kt diff --git a/domains/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungTable.kt b/backend/services/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungTable.kt similarity index 100% rename from domains/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungTable.kt rename to backend/services/events/events-infrastructure/src/main/kotlin/at/mocode/events/infrastructure/persistence/VeranstaltungTable.kt diff --git a/domains/events/events-service/build.gradle.kts b/backend/services/events/events-service/build.gradle.kts similarity index 100% rename from domains/events/events-service/build.gradle.kts rename to backend/services/events/events-service/build.gradle.kts diff --git a/domains/events/events-service/src/main/kotlin/at/mocode/events/service/EventsServiceApplication.kt b/backend/services/events/events-service/src/main/kotlin/at/mocode/events/service/EventsServiceApplication.kt similarity index 100% rename from domains/events/events-service/src/main/kotlin/at/mocode/events/service/EventsServiceApplication.kt rename to backend/services/events/events-service/src/main/kotlin/at/mocode/events/service/EventsServiceApplication.kt diff --git a/domains/events/events-service/src/main/kotlin/at/mocode/events/service/config/EventsDatabaseConfiguration.kt b/backend/services/events/events-service/src/main/kotlin/at/mocode/events/service/config/EventsDatabaseConfiguration.kt similarity index 100% rename from domains/events/events-service/src/main/kotlin/at/mocode/events/service/config/EventsDatabaseConfiguration.kt rename to backend/services/events/events-service/src/main/kotlin/at/mocode/events/service/config/EventsDatabaseConfiguration.kt diff --git a/domains/horses/horses-service/src/test/resources/logback-test.xml b/backend/services/events/events-service/src/test/resources/logback-test.xml similarity index 100% rename from domains/horses/horses-service/src/test/resources/logback-test.xml rename to backend/services/events/events-service/src/test/resources/logback-test.xml diff --git a/dockerfiles/domains/horses-service/Dockerfile b/backend/services/horses/Dockerfile similarity index 100% rename from dockerfiles/domains/horses-service/Dockerfile rename to backend/services/horses/Dockerfile diff --git a/domains/horses/horses-api/build.gradle.kts b/backend/services/horses/horses-api/build.gradle.kts similarity index 100% rename from domains/horses/horses-api/build.gradle.kts rename to backend/services/horses/horses-api/build.gradle.kts diff --git a/domains/horses/horses-api/src/main/kotlin/at/mocode/horses/api/rest/HorseController.kt b/backend/services/horses/horses-api/src/main/kotlin/at/mocode/horses/api/rest/HorseController.kt similarity index 100% rename from domains/horses/horses-api/src/main/kotlin/at/mocode/horses/api/rest/HorseController.kt rename to backend/services/horses/horses-api/src/main/kotlin/at/mocode/horses/api/rest/HorseController.kt diff --git a/domains/horses/horses-common/build.gradle.kts b/backend/services/horses/horses-common/build.gradle.kts similarity index 100% rename from domains/horses/horses-common/build.gradle.kts rename to backend/services/horses/horses-common/build.gradle.kts diff --git a/domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/CreateHorseUseCase.kt b/backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/CreateHorseUseCase.kt similarity index 100% rename from domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/CreateHorseUseCase.kt rename to backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/CreateHorseUseCase.kt diff --git a/domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/DeleteHorseUseCase.kt b/backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/DeleteHorseUseCase.kt similarity index 100% rename from domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/DeleteHorseUseCase.kt rename to backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/DeleteHorseUseCase.kt diff --git a/domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/GetHorseUseCase.kt b/backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/GetHorseUseCase.kt similarity index 100% rename from domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/GetHorseUseCase.kt rename to backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/GetHorseUseCase.kt diff --git a/domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/TransactionalCreateHorseUseCase.kt b/backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/TransactionalCreateHorseUseCase.kt similarity index 100% rename from domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/TransactionalCreateHorseUseCase.kt rename to backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/TransactionalCreateHorseUseCase.kt diff --git a/domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/UpdateHorseUseCase.kt b/backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/UpdateHorseUseCase.kt similarity index 100% rename from domains/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/UpdateHorseUseCase.kt rename to backend/services/horses/horses-common/src/main/kotlin/at/mocode/horses/application/usecase/UpdateHorseUseCase.kt diff --git a/domains/horses/horses-domain/build.gradle.kts b/backend/services/horses/horses-domain/build.gradle.kts similarity index 100% rename from domains/horses/horses-domain/build.gradle.kts rename to backend/services/horses/horses-domain/build.gradle.kts diff --git a/domains/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/model/DomPferd.kt b/backend/services/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/model/DomPferd.kt similarity index 100% rename from domains/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/model/DomPferd.kt rename to backend/services/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/model/DomPferd.kt diff --git a/domains/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/repository/HorseRepository.kt b/backend/services/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/repository/HorseRepository.kt similarity index 100% rename from domains/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/repository/HorseRepository.kt rename to backend/services/horses/horses-domain/src/main/kotlin/at/mocode/horses/domain/repository/HorseRepository.kt diff --git a/domains/horses/horses-infrastructure/build.gradle.kts b/backend/services/horses/horses-infrastructure/build.gradle.kts similarity index 100% rename from domains/horses/horses-infrastructure/build.gradle.kts rename to backend/services/horses/horses-infrastructure/build.gradle.kts diff --git a/domains/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseRepositoryImpl.kt b/backend/services/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseRepositoryImpl.kt similarity index 100% rename from domains/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseRepositoryImpl.kt rename to backend/services/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseRepositoryImpl.kt diff --git a/domains/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseTable.kt b/backend/services/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseTable.kt similarity index 100% rename from domains/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseTable.kt rename to backend/services/horses/horses-infrastructure/src/main/kotlin/at/mocode/horses/infrastructure/persistence/HorseTable.kt diff --git a/domains/horses/horses-service/build.gradle.kts b/backend/services/horses/horses-service/build.gradle.kts similarity index 100% rename from domains/horses/horses-service/build.gradle.kts rename to backend/services/horses/horses-service/build.gradle.kts diff --git a/domains/horses/horses-service/src/main/kotlin/at/mocode/horses/service/HorsesServiceApplication.kt b/backend/services/horses/horses-service/src/main/kotlin/at/mocode/horses/service/HorsesServiceApplication.kt similarity index 100% rename from domains/horses/horses-service/src/main/kotlin/at/mocode/horses/service/HorsesServiceApplication.kt rename to backend/services/horses/horses-service/src/main/kotlin/at/mocode/horses/service/HorsesServiceApplication.kt diff --git a/domains/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/ApplicationConfiguration.kt b/backend/services/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/ApplicationConfiguration.kt similarity index 100% rename from domains/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/ApplicationConfiguration.kt rename to backend/services/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/ApplicationConfiguration.kt diff --git a/domains/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/DatabaseConfiguration.kt b/backend/services/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/DatabaseConfiguration.kt similarity index 100% rename from domains/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/DatabaseConfiguration.kt rename to backend/services/horses/horses-service/src/main/kotlin/at/mocode/horses/service/config/DatabaseConfiguration.kt diff --git a/domains/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/HorseServiceIntegrationTest.kt b/backend/services/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/HorseServiceIntegrationTest.kt similarity index 100% rename from domains/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/HorseServiceIntegrationTest.kt rename to backend/services/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/HorseServiceIntegrationTest.kt diff --git a/domains/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionContextTest.kt b/backend/services/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionContextTest.kt similarity index 100% rename from domains/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionContextTest.kt rename to backend/services/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionContextTest.kt diff --git a/domains/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionalContextTest.kt b/backend/services/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionalContextTest.kt similarity index 100% rename from domains/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionalContextTest.kt rename to backend/services/horses/horses-service/src/test/kotlin/at/mocode/horses/service/integration/TransactionalContextTest.kt diff --git a/domains/masterdata/masterdata-service/src/test/resources/logback-test.xml b/backend/services/horses/horses-service/src/test/resources/logback-test.xml similarity index 100% rename from domains/masterdata/masterdata-service/src/test/resources/logback-test.xml rename to backend/services/horses/horses-service/src/test/resources/logback-test.xml diff --git a/dockerfiles/domains/masterdata-service/Dockerfile b/backend/services/masterdata/Dockerfile similarity index 100% rename from dockerfiles/domains/masterdata-service/Dockerfile rename to backend/services/masterdata/Dockerfile diff --git a/domains/masterdata/masterdata-api/build.gradle.kts b/backend/services/masterdata/masterdata-api/build.gradle.kts similarity index 100% rename from domains/masterdata/masterdata-api/build.gradle.kts rename to backend/services/masterdata/masterdata-api/build.gradle.kts diff --git a/domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/StatusPages.kt b/backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/StatusPages.kt similarity index 100% rename from domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/StatusPages.kt rename to backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/StatusPages.kt diff --git a/domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/AltersklasseController.kt b/backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/AltersklasseController.kt similarity index 100% rename from domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/AltersklasseController.kt rename to backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/AltersklasseController.kt diff --git a/domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/BundeslandController.kt b/backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/BundeslandController.kt similarity index 100% rename from domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/BundeslandController.kt rename to backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/BundeslandController.kt diff --git a/domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/CountryController.kt b/backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/CountryController.kt similarity index 100% rename from domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/CountryController.kt rename to backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/CountryController.kt diff --git a/domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/PlatzController.kt b/backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/PlatzController.kt similarity index 100% rename from domains/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/PlatzController.kt rename to backend/services/masterdata/masterdata-api/src/main/kotlin/at/mocode/masterdata/api/rest/PlatzController.kt diff --git a/domains/masterdata/masterdata-common/build.gradle.kts b/backend/services/masterdata/masterdata-common/build.gradle.kts similarity index 100% rename from domains/masterdata/masterdata-common/build.gradle.kts rename to backend/services/masterdata/masterdata-common/build.gradle.kts diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateAltersklasseUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateAltersklasseUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateAltersklasseUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateAltersklasseUseCase.kt diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateBundeslandUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateBundeslandUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateBundeslandUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateBundeslandUseCase.kt diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateCountryUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateCountryUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateCountryUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreateCountryUseCase.kt diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreatePlatzUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreatePlatzUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreatePlatzUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/CreatePlatzUseCase.kt diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetAltersklasseUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetAltersklasseUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetAltersklasseUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetAltersklasseUseCase.kt diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetBundeslandUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetBundeslandUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetBundeslandUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetBundeslandUseCase.kt diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetCountryUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetCountryUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetCountryUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetCountryUseCase.kt diff --git a/domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetPlatzUseCase.kt b/backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetPlatzUseCase.kt similarity index 100% rename from domains/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetPlatzUseCase.kt rename to backend/services/masterdata/masterdata-common/src/main/kotlin/at/mocode/masterdata/application/usecase/GetPlatzUseCase.kt diff --git a/domains/masterdata/masterdata-domain/build.gradle.kts b/backend/services/masterdata/masterdata-domain/build.gradle.kts similarity index 100% rename from domains/masterdata/masterdata-domain/build.gradle.kts rename to backend/services/masterdata/masterdata-domain/build.gradle.kts diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/AltersklasseDefinition.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/AltersklasseDefinition.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/AltersklasseDefinition.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/AltersklasseDefinition.kt diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/BundeslandDefinition.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/BundeslandDefinition.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/BundeslandDefinition.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/BundeslandDefinition.kt diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/LandDefinition.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/LandDefinition.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/LandDefinition.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/LandDefinition.kt diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/Platz.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/Platz.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/Platz.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/model/Platz.kt diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/AltersklasseRepository.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/AltersklasseRepository.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/AltersklasseRepository.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/AltersklasseRepository.kt diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/BundeslandRepository.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/BundeslandRepository.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/BundeslandRepository.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/BundeslandRepository.kt diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/LandRepository.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/LandRepository.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/LandRepository.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/LandRepository.kt diff --git a/domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/PlatzRepository.kt b/backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/PlatzRepository.kt similarity index 100% rename from domains/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/PlatzRepository.kt rename to backend/services/masterdata/masterdata-domain/src/main/kotlin/at/mocode/masterdata/domain/repository/PlatzRepository.kt diff --git a/domains/masterdata/masterdata-infrastructure/build.gradle.kts b/backend/services/masterdata/masterdata-infrastructure/build.gradle.kts similarity index 100% rename from domains/masterdata/masterdata-infrastructure/build.gradle.kts rename to backend/services/masterdata/masterdata-infrastructure/build.gradle.kts diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseRepositoryImpl.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseRepositoryImpl.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseRepositoryImpl.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseRepositoryImpl.kt diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseTable.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseTable.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseTable.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/AltersklasseTable.kt diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandRepositoryImpl.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandRepositoryImpl.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandRepositoryImpl.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandRepositoryImpl.kt diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandTable.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandTable.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandTable.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/BundeslandTable.kt diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandRepositoryImpl.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandRepositoryImpl.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandRepositoryImpl.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandRepositoryImpl.kt diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandTable.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandTable.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandTable.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/LandTable.kt diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzRepositoryImpl.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzRepositoryImpl.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzRepositoryImpl.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzRepositoryImpl.kt diff --git a/domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzTable.kt b/backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzTable.kt similarity index 100% rename from domains/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzTable.kt rename to backend/services/masterdata/masterdata-infrastructure/src/main/kotlin/at/mocode/masterdata/infrastructure/persistence/PlatzTable.kt diff --git a/domains/masterdata/masterdata-service/build.gradle.kts b/backend/services/masterdata/masterdata-service/build.gradle.kts similarity index 100% rename from domains/masterdata/masterdata-service/build.gradle.kts rename to backend/services/masterdata/masterdata-service/build.gradle.kts diff --git a/domains/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/MasterdataServiceApplication.kt b/backend/services/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/MasterdataServiceApplication.kt similarity index 100% rename from domains/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/MasterdataServiceApplication.kt rename to backend/services/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/MasterdataServiceApplication.kt diff --git a/domains/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataConfiguration.kt b/backend/services/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataConfiguration.kt similarity index 100% rename from domains/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataConfiguration.kt rename to backend/services/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataConfiguration.kt diff --git a/domains/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataDatabaseConfiguration.kt b/backend/services/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataDatabaseConfiguration.kt similarity index 100% rename from domains/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataDatabaseConfiguration.kt rename to backend/services/masterdata/masterdata-service/src/main/kotlin/at/mocode/masterdata/service/config/MasterdataDatabaseConfiguration.kt diff --git a/domains/masterdata/masterdata-service/src/main/resources/db/migration/V001__Create_Land_Table.sql b/backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V001__Create_Land_Table.sql similarity index 100% rename from domains/masterdata/masterdata-service/src/main/resources/db/migration/V001__Create_Land_Table.sql rename to backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V001__Create_Land_Table.sql diff --git a/domains/masterdata/masterdata-service/src/main/resources/db/migration/V002__Create_Bundesland_Table.sql b/backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V002__Create_Bundesland_Table.sql similarity index 100% rename from domains/masterdata/masterdata-service/src/main/resources/db/migration/V002__Create_Bundesland_Table.sql rename to backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V002__Create_Bundesland_Table.sql diff --git a/domains/masterdata/masterdata-service/src/main/resources/db/migration/V003__Create_Altersklasse_Table.sql b/backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V003__Create_Altersklasse_Table.sql similarity index 100% rename from domains/masterdata/masterdata-service/src/main/resources/db/migration/V003__Create_Altersklasse_Table.sql rename to backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V003__Create_Altersklasse_Table.sql diff --git a/domains/masterdata/masterdata-service/src/main/resources/db/migration/V004__Create_Platz_Table.sql b/backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V004__Create_Platz_Table.sql similarity index 100% rename from domains/masterdata/masterdata-service/src/main/resources/db/migration/V004__Create_Platz_Table.sql rename to backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V004__Create_Platz_Table.sql diff --git a/domains/masterdata/masterdata-service/src/main/resources/db/migration/V1__Create_Initial_Tables.sql b/backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V1__Create_Initial_Tables.sql similarity index 96% rename from domains/masterdata/masterdata-service/src/main/resources/db/migration/V1__Create_Initial_Tables.sql rename to backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V1__Create_Initial_Tables.sql index 7c330d15..9ade97d1 100644 --- a/domains/masterdata/masterdata-service/src/main/resources/db/migration/V1__Create_Initial_Tables.sql +++ b/backend/services/masterdata/masterdata-service/src/main/resources/db/migration/V1__Create_Initial_Tables.sql @@ -30,4 +30,4 @@ CREATE TABLE IF NOT EXISTS dom_person ( updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP ); --- Weitere Tabellen können hier hinzugefügt werden... +-- Weitere Tabellen können hier hinzugefügt werden ... diff --git a/infrastructure/monitoring/monitoring-server/src/test/resources/logback-test.xml b/backend/services/masterdata/masterdata-service/src/test/resources/logback-test.xml similarity index 100% rename from infrastructure/monitoring/monitoring-server/src/test/resources/logback-test.xml rename to backend/services/masterdata/masterdata-service/src/test/resources/logback-test.xml diff --git a/dockerfiles/domains/members-service/Dockerfile b/backend/services/members/Dockerfile similarity index 100% rename from dockerfiles/domains/members-service/Dockerfile rename to backend/services/members/Dockerfile diff --git a/dockerfiles/services/ping-service/Dockerfile b/backend/services/ping/Dockerfile similarity index 89% rename from dockerfiles/services/ping-service/Dockerfile rename to backend/services/ping/Dockerfile index 8a012d29..5da5050e 100644 --- a/dockerfiles/services/ping-service/Dockerfile +++ b/backend/services/ping/Dockerfile @@ -51,20 +51,14 @@ RUN chmod +x gradlew # Copy platform dependencies (changes less frequently) COPY platform/ platform/ -# Copy client directories (required by settings.gradle.kts) -COPY clients/ clients/ +# Copy frontend/client directories (required by settings.gradle.kts) +COPY frontend/ frontend/ # Copy core directories (required by settings.gradle.kts) COPY core/ core/ -# Copy infrastructure directories (required by settings.gradle.kts) -COPY infrastructure/ infrastructure/ - -# Copy domains directory (required by settings.gradle.kts) -COPY domains/ domains/ - -# Copy services directories (required by settings.gradle.kts) -COPY services/ services/ +# Copy backend (includes services and infrastructure in new structure) +COPY backend/ backend/ # Copy docs directory (required by settings.gradle.kts) COPY docs/ docs/ @@ -72,19 +66,19 @@ COPY docs/ docs/ # Copy root build configuration COPY build.gradle.kts ./ -# Copy ping modules (changes most frequently) -COPY services/ping/ping-api/ services/ping/ping-api/ -COPY services/ping/ping-service/ services/ping/ping-service/ +# Copy ping modules (changes most frequently) for both legacy and new structure +COPY backend/services/ping/ping-api/ backend/services/ping/ping-api/ +COPY backend/services/ping/ping-service/ backend/services/ping/ping-service/ # Download and cache dependencies in a separate layer with build cache RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ --mount=type=cache,target=/home/gradle/.gradle/wrapper \ - ./gradlew :services:ping:ping-service:dependencies --no-daemon --info + ./gradlew :backend:services:ping:ping-service:dependencies --no-daemon --info # Build the application with optimizations and build cache RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ --mount=type=cache,target=/home/gradle/.gradle/wrapper \ - ./gradlew :services:ping:ping-service:bootJar --no-daemon --info + ./gradlew :backend:services:ping:ping-service:bootJar --no-daemon --info # =================================================================== # Runtime stage: optimized JRE image for production @@ -138,7 +132,7 @@ RUN apk update && \ # Copy the built JAR from builder stage with proper ownership COPY --from=builder --chown=${APP_USER}:${APP_GROUP} \ - /workspace/services/ping/ping-service/build/libs/*.jar app.jar + /workspace/backend/services/ping/ping-service/build/libs/*.jar app.jar # Switch to non-root user USER ${APP_USER} diff --git a/backend/services/ping/ping-service/build.gradle.kts b/backend/services/ping/ping-service/build.gradle.kts index 1476ac67..290226c5 100644 --- a/backend/services/ping/ping-service/build.gradle.kts +++ b/backend/services/ping/ping-service/build.gradle.kts @@ -19,8 +19,8 @@ dependencies { // Platform und Core Dependencies implementation(projects.platform.platformDependencies) - implementation(project(":backend:services:ping:ping-api")) - implementation(projects.infrastructure.monitoring.monitoringClient) + implementation(projects.backend.services.ping.pingApi) + implementation(projects.backend.infrastructure.monitoring.monitoringClient) // Spring Boot Service Complete Bundle // Provides: web, validation, actuator, security, oauth2-client, oauth2-resource-server, diff --git a/domains/registry/oeps-importer/build.gradle.kts b/backend/services/registry/oeps-importer/build.gradle.kts similarity index 100% rename from domains/registry/oeps-importer/build.gradle.kts rename to backend/services/registry/oeps-importer/build.gradle.kts diff --git a/domains/registry/registry-api/build.gradle.kts b/backend/services/registry/registry-api/build.gradle.kts similarity index 100% rename from domains/registry/registry-api/build.gradle.kts rename to backend/services/registry/registry-api/build.gradle.kts diff --git a/domains/registry/registry-domain/build.gradle.kts b/backend/services/registry/registry-domain/build.gradle.kts similarity index 100% rename from domains/registry/registry-domain/build.gradle.kts rename to backend/services/registry/registry-domain/build.gradle.kts diff --git a/domains/registry/registry-service/build.gradle.kts b/backend/services/registry/registry-service/build.gradle.kts similarity index 100% rename from domains/registry/registry-service/build.gradle.kts rename to backend/services/registry/registry-service/build.gradle.kts diff --git a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/domain/repository/PingRepository.kt b/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/domain/repository/PingRepository.kt deleted file mode 100644 index 89672780..00000000 --- a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/domain/repository/PingRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package at.mocode.clients.shared.domain.repository - -import at.mocode.clients.shared.domain.model.PingData -import at.mocode.clients.shared.domain.model.Resource - -interface PingRepository { - suspend fun checkSystemStatus(): Resource -} diff --git a/config/.env b/config/.env new file mode 100644 index 00000000..2d7b3215 --- /dev/null +++ b/config/.env @@ -0,0 +1,8 @@ +# DEPRECATED – Single Source of Truth moved to docker/.env +# +# This file is no longer used by Docker Compose or any build scripts. +# Please configure environment variables in: +# docker/.env (create from docker/.env.example) +# +# Reason: Avoid duplicated/conflicting configuration files. +# Monitoring, Postgres, Redis, Keycloak, Gateway etc. read values from docker/.env. diff --git a/config/.env.dev b/config/.env.dev deleted file mode 100644 index 5ea2991f..00000000 --- a/config/.env.dev +++ /dev/null @@ -1,190 +0,0 @@ -# ============================================================================= -# Meldestelle - Development Environment Configuration -# ============================================================================= -# Development-specific environment variables -# ============================================================================= - -# ============================================================================= -# 1. APPLICATION CONFIGURATION -# ============================================================================= -APP_NAME=Meldestelle -APP_VERSION=1.0.0 -APP_DESCRIPTION='Pferdesport Meldestelle System' -APP_ENVIRONMENT=development -APP_HOST=0.0.0.0 - -# Development-specific settings -DEBUG_MODE=true -DEV_HOT_RELOAD=true - -# ============================================================================= -# 2. PORT MANAGEMENT -# ============================================================================= -# Gateway Ports -GATEWAY_PORT=8081 -GATEWAY_ADMIN_PORT=8080 - -# Service Ports -PING_SERVICE_PORT=8082 -MEMBERS_SERVICE_PORT=8083 -HORSES_SERVICE_PORT=8084 -EVENTS_SERVICE_PORT=8085 -MASTERDATA_SERVICE_PORT=8086 -AUTH_SERVICE_PORT=8087 - -# Client Application Ports -WEB_APP_PORT=4000 -DESKTOP_VNC_PORT=5901 -DESKTOP_WEB_VNC_PORT=6080 - -# Infrastructure Ports -CONSUL_PORT=8500 -REDIS_PORT=6379 -KAFKA_PORT=9092 -PROMETHEUS_PORT=9090 -GRAFANA_PORT=3000 - -# ============================================================================= -# 3. DATABASE CONFIGURATION -# ============================================================================= -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=meldestelle -DB_USER=meldestelle -DB_PASSWORD=meldestelle -DB_MAX_POOL_SIZE=10 -DB_MIN_POOL_SIZE=5 -DB_AUTO_MIGRATE=true - -POSTGRES_USER=meldestelle -POSTGRES_PASSWORD=meldestelle -POSTGRES_DB=meldestelle -POSTGRES_EXTERNAL_PORT=5432 - -# ============================================================================= -# 4. REDIS CONFIGURATION -# ============================================================================= -REDIS_EVENT_STORE_HOST=localhost -REDIS_EVENT_STORE_PORT=6379 -REDIS_EVENT_STORE_PASSWORD= -REDIS_EVENT_STORE_DATABASE=0 -REDIS_EVENT_STORE_CONNECTION_TIMEOUT=2000 -REDIS_EVENT_STORE_READ_TIMEOUT=2000 -REDIS_EVENT_STORE_USE_POOLING=true -REDIS_EVENT_STORE_MAX_POOL_SIZE=8 -REDIS_EVENT_STORE_MIN_POOL_SIZE=2 - -REDIS_CACHE_HOST=localhost -REDIS_CACHE_PORT=6379 -REDIS_CACHE_PASSWORD= -REDIS_CACHE_DATABASE=1 - -REDIS_EXTERNAL_PORT=6379 -REDIS_PASSWORD= - -# ============================================================================= -# 5. SECURITY CONFIGURATION -# ============================================================================= -JWT_SECRET=meldestelle-jwt-secret-key-for-development-change-in-production -JWT_ISSUER=meldestelle-api -JWT_AUDIENCE=meldestelle-clients -JWT_REALM=meldestelle -API_KEY=meldestelle-api-key-for-development - -# ============================================================================= -# 6. KEYCLOAK CONFIGURATION -# ============================================================================= -KC_BOOTSTRAP_ADMIN_USERNAME=admin -KC_BOOTSTRAP_ADMIN_PASSWORD=admin -KC_DB=postgres -KC_DB_URL=jdbc:postgresql://postgres:5432/meldestelle -KC_DB_SCHEMA=keycloak -KC_DB_USERNAME=meldestelle -KC_DB_PASSWORD=meldestelle -KC_HOSTNAME=localhost - -# ============================================================================= -# 7. SERVICE DISCOVERY -# ============================================================================= -CONSUL_HOST=consul -CONSUL_ENABLED=true -SERVICE_DISCOVERY_ENABLED=true -SERVICE_DISCOVERY_REGISTER_SERVICES=true -SERVICE_DISCOVERY_HEALTH_CHECK_PATH=/health -SERVICE_DISCOVERY_HEALTH_CHECK_INTERVAL=10 - -# ============================================================================= -# 8. MESSAGING (Kafka) -# ============================================================================= -ZOOKEEPER_CLIENT_PORT=2181 -KAFKA_BROKER_ID=1 -KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 -KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT -KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT -KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 - -# ============================================================================= -# 9. MONITORING -# ============================================================================= -GF_SECURITY_ADMIN_USER=meldestelle -GF_SECURITY_ADMIN_PASSWORD=meldestelle -GF_USERS_ALLOW_SIGN_UP=false - -METRICS_AUTH_USERNAME=admin -METRICS_AUTH_PASSWORD=metrics - -GRAFANA_HOSTNAME=grafana.meldestelle.local -PROMETHEUS_HOSTNAME=prometheus.meldestelle.local - -# ============================================================================= -# 10. LOGGING CONFIGURATION -# ============================================================================= -LOGGING_LEVEL=DEBUG -LOGGING_REQUESTS=true -LOGGING_RESPONSES=true -LOGGING_REQUEST_HEADERS=true -LOGGING_REQUEST_BODY=true -LOGGING_RESPONSE_HEADERS=true -LOGGING_RESPONSE_BODY=true -LOGGING_STRUCTURED=true -LOGGING_CORRELATION_ID=true -LOGGING_REQUEST_ID_HEADER=X-Request-ID - -# ============================================================================= -# 11. CORS AND RATE LIMITING -# ============================================================================= -SERVER_CORS_ENABLED=true -SERVER_CORS_ALLOWED_ORIGINS=* -RATELIMIT_ENABLED=true -RATELIMIT_GLOBAL_LIMIT=100 -RATELIMIT_GLOBAL_PERIOD_MINUTES=1 -RATELIMIT_INCLUDE_HEADERS=true - -# ============================================================================= -# 12. DOCKER BUILD ARGUMENTS -# ============================================================================= -# Centralized Docker build arguments for compose files -# These mirror the values from docker/build-args/ for standalone compose usage -DOCKER_GRADLE_VERSION=9.0.0 -DOCKER_JAVA_VERSION=21 -DOCKER_NODE_VERSION=20.11.0 -DOCKER_NGINX_VERSION=1.25-alpine -DOCKER_APP_VERSION=1.0.0 -BUILD_DATE=2025-09-13T23:32:00Z - -# Monitoring & Infrastructure versions -DOCKER_PROMETHEUS_VERSION=v2.54.1 -DOCKER_GRAFANA_VERSION=11.3.0 -DOCKER_KEYCLOAK_VERSION=26.4.0 - -# Spring profiles for Docker builds -DOCKER_SPRING_PROFILES_DEFAULT=default -DOCKER_SPRING_PROFILES_DOCKER=docker - -# ============================================================================= -# 13. SPRING PROFILES AND GATEWAY -# ============================================================================= -SPRING_PROFILES_ACTIVE=dev -GATEWAY_ADMIN_USER=admin -GATEWAY_ADMIN_PASSWORD=admin diff --git a/config/.env.example b/config/.env.example new file mode 100644 index 00000000..bbf53b5f --- /dev/null +++ b/config/.env.example @@ -0,0 +1,7 @@ +# DEPRECATED – Single Source of Truth moved to docker/.env.example +# +# This file is no longer used by Docker Compose or any build scripts. +# Please use and copy from: +# docker/.env.example → docker/.env +# +# Reason: Avoid duplicated/conflicting configuration files. diff --git a/config/.env.prod b/config/.env.prod deleted file mode 100644 index 6dc55366..00000000 --- a/config/.env.prod +++ /dev/null @@ -1,164 +0,0 @@ -# ============================================================================= -# Meldestelle - Production Environment Configuration -# ============================================================================= -# Production-specific environment variables -# IMPORTANT: Change all CHANGE_ME values before deployment! -# ============================================================================= - -# ============================================================================= -# 1. APPLICATION CONFIGURATION -# ============================================================================= -APP_NAME=Meldestelle -APP_VERSION=1.0.0 -APP_DESCRIPTION='Pferdesport Meldestelle System' -APP_ENVIRONMENT=production -APP_HOST=0.0.0.0 - -# Production settings -DEBUG_MODE=false -DEV_HOT_RELOAD=false - -# ============================================================================= -# 2. PORT MANAGEMENT -# ============================================================================= -# Gateway Ports -GATEWAY_PORT=8081 -GATEWAY_ADMIN_PORT=8080 - -# Service Ports -PING_SERVICE_PORT=8082 -MEMBERS_SERVICE_PORT=8083 -HORSES_SERVICE_PORT=8084 -EVENTS_SERVICE_PORT=8085 -MASTERDATA_SERVICE_PORT=8086 -AUTH_SERVICE_PORT=8087 - -# Infrastructure Ports -CONSUL_PORT=8500 -REDIS_PORT=6379 -KAFKA_PORT=9092 -PROMETHEUS_PORT=9090 -GRAFANA_PORT=3000 - -# ============================================================================= -# 3. DATABASE CONFIGURATION -# ============================================================================= -DB_HOST=postgres -DB_PORT=5432 -DB_NAME=meldestelle_prod -DB_USER=meldestelle_prod -DB_PASSWORD=CHANGE_ME_STRONG_DB_PASSWORD_HERE -DB_MAX_POOL_SIZE=20 -DB_MIN_POOL_SIZE=10 -DB_AUTO_MIGRATE=false - -POSTGRES_USER=meldestelle_prod -POSTGRES_PASSWORD=CHANGE_ME_STRONG_DB_PASSWORD_HERE -POSTGRES_DB=meldestelle_prod -POSTGRES_EXTERNAL_PORT=5432 - -# ============================================================================= -# 4. REDIS CONFIGURATION -# ============================================================================= -REDIS_EVENT_STORE_HOST=redis -REDIS_EVENT_STORE_PORT=6379 -REDIS_EVENT_STORE_PASSWORD=CHANGE_ME_STRONG_REDIS_PASSWORD_HERE -REDIS_EVENT_STORE_DATABASE=0 -REDIS_EVENT_STORE_CONNECTION_TIMEOUT=5000 -REDIS_EVENT_STORE_READ_TIMEOUT=5000 -REDIS_EVENT_STORE_USE_POOLING=true -REDIS_EVENT_STORE_MAX_POOL_SIZE=20 -REDIS_EVENT_STORE_MIN_POOL_SIZE=5 - -REDIS_CACHE_HOST=redis -REDIS_CACHE_PORT=6379 -REDIS_CACHE_PASSWORD=CHANGE_ME_STRONG_REDIS_PASSWORD_HERE -REDIS_CACHE_DATABASE=1 - -REDIS_EXTERNAL_PORT=6379 -REDIS_PASSWORD=CHANGE_ME_STRONG_REDIS_PASSWORD_HERE - -# ============================================================================= -# 5. SECURITY CONFIGURATION -# ============================================================================= -JWT_SECRET=CHANGE_ME_STRONG_JWT_SECRET_AT_LEAST_256_BITS_HERE -JWT_ISSUER=meldestelle-api-prod -JWT_AUDIENCE=meldestelle-clients-prod -JWT_REALM=meldestelle-prod -API_KEY=CHANGE_ME_STRONG_API_KEY_HERE - -# ============================================================================= -# 6. KEYCLOAK CONFIGURATION -# ============================================================================= -KEYCLOAK_ADMIN=CHANGE_ME_ADMIN_USERNAME -KEYCLOAK_ADMIN_PASSWORD=CHANGE_ME_STRONG_ADMIN_PASSWORD_HERE -KC_DB=postgres -KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak_prod -KC_DB_USERNAME=keycloak_prod -KC_DB_PASSWORD=CHANGE_ME_STRONG_KEYCLOAK_DB_PASSWORD_HERE -KC_HOSTNAME=auth.yourdomain.com - -# ============================================================================= -# 7. SERVICE DISCOVERY -# ============================================================================= -CONSUL_HOST=consul -CONSUL_ENABLED=true -SERVICE_DISCOVERY_ENABLED=true -SERVICE_DISCOVERY_REGISTER_SERVICES=true -SERVICE_DISCOVERY_HEALTH_CHECK_PATH=/health -SERVICE_DISCOVERY_HEALTH_CHECK_INTERVAL=30 - -# ============================================================================= -# 8. MESSAGING (Kafka) -# ============================================================================= -ZOOKEEPER_CLIENT_PORT=2181 -KAFKA_BROKER_ID=1 -KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 -KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT -KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT -KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 - -# ============================================================================= -# 9. MONITORING -# ============================================================================= -GF_SECURITY_ADMIN_USER=CHANGE_ME_GRAFANA_ADMIN_USERNAME -GF_SECURITY_ADMIN_PASSWORD=CHANGE_ME_STRONG_GRAFANA_PASSWORD_HERE -GF_USERS_ALLOW_SIGN_UP=false - -METRICS_AUTH_USERNAME=CHANGE_ME_METRICS_USERNAME -METRICS_AUTH_PASSWORD=CHANGE_ME_STRONG_METRICS_PASSWORD_HERE - -GRAFANA_HOSTNAME=monitoring.yourdomain.com -PROMETHEUS_HOSTNAME=metrics.yourdomain.com - -# ============================================================================= -# 10. LOGGING CONFIGURATION -# ============================================================================= -LOGGING_LEVEL=INFO -LOGGING_REQUESTS=false -LOGGING_RESPONSES=false -LOGGING_REQUEST_HEADERS=false -LOGGING_REQUEST_BODY=false -LOGGING_RESPONSE_HEADERS=false -LOGGING_RESPONSE_BODY=false -LOGGING_STRUCTURED=true -LOGGING_CORRELATION_ID=true -LOGGING_REQUEST_ID_HEADER=X-Request-ID - -# ============================================================================= -# 11. CORS AND RATE LIMITING -# ============================================================================= -SERVER_CORS_ENABLED=true -SERVER_CORS_ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com -RATELIMIT_ENABLED=true -RATELIMIT_GLOBAL_LIMIT=1000 -RATELIMIT_GLOBAL_PERIOD_MINUTES=1 -RATELIMIT_INCLUDE_HEADERS=true - -# ============================================================================= -# 12. SPRING PROFILES AND GATEWAY -# ============================================================================= -SPRING_PROFILES_ACTIVE=prod -GATEWAY_ADMIN_USER=CHANGE_ME_GATEWAY_ADMIN_USERNAME -GATEWAY_ADMIN_PASSWORD=CHANGE_ME_STRONG_GATEWAY_ADMIN_PASSWORD_HERE diff --git a/config/.env.staging b/config/.env.staging deleted file mode 100644 index 024fd97c..00000000 --- a/config/.env.staging +++ /dev/null @@ -1,164 +0,0 @@ -# ============================================================================= -# Meldestelle - Staging Environment Configuration -# ============================================================================= -# Staging-specific environment variables (production-like but for testing) -# ============================================================================= - -# ============================================================================= -# 1. APPLICATION CONFIGURATION -# ============================================================================= -APP_NAME=Meldestelle -APP_VERSION=1.0.0 -APP_DESCRIPTION='Pferdesport Meldestelle System' -APP_ENVIRONMENT=staging -APP_HOST=0.0.0.0 - -# Staging settings (production-like but with some debugging) -DEBUG_MODE=false -DEV_HOT_RELOAD=false - -# ============================================================================= -# 2. PORT MANAGEMENT -# ============================================================================= -# Gateway Ports -GATEWAY_PORT=8081 -GATEWAY_ADMIN_PORT=8080 - -# Service Ports -PING_SERVICE_PORT=8082 -MEMBERS_SERVICE_PORT=8083 -HORSES_SERVICE_PORT=8084 -EVENTS_SERVICE_PORT=8085 -MASTERDATA_SERVICE_PORT=8086 -AUTH_SERVICE_PORT=8087 - -# Infrastructure Ports -CONSUL_PORT=8500 -REDIS_PORT=6379 -KAFKA_PORT=9092 -PROMETHEUS_PORT=9090 -GRAFANA_PORT=3000 - -# ============================================================================= -# 3. DATABASE CONFIGURATION -# ============================================================================= -DB_HOST=postgres -DB_PORT=5432 -DB_NAME=meldestelle_staging -DB_USER=meldestelle_staging -DB_PASSWORD=staging_password_change_me -DB_MAX_POOL_SIZE=15 -DB_MIN_POOL_SIZE=5 -DB_AUTO_MIGRATE=true - -POSTGRES_USER=meldestelle_staging -POSTGRES_PASSWORD=staging_password_change_me -POSTGRES_DB=meldestelle_staging -POSTGRES_EXTERNAL_PORT=5432 - -# ============================================================================= -# 4. REDIS CONFIGURATION -# ============================================================================= -REDIS_EVENT_STORE_HOST=redis -REDIS_EVENT_STORE_PORT=6379 -REDIS_EVENT_STORE_PASSWORD=staging_redis_password -REDIS_EVENT_STORE_DATABASE=0 -REDIS_EVENT_STORE_CONNECTION_TIMEOUT=3000 -REDIS_EVENT_STORE_READ_TIMEOUT=3000 -REDIS_EVENT_STORE_USE_POOLING=true -REDIS_EVENT_STORE_MAX_POOL_SIZE=15 -REDIS_EVENT_STORE_MIN_POOL_SIZE=3 - -REDIS_CACHE_HOST=redis -REDIS_CACHE_PORT=6379 -REDIS_CACHE_PASSWORD=staging_redis_password -REDIS_CACHE_DATABASE=1 - -REDIS_EXTERNAL_PORT=6379 -REDIS_PASSWORD=staging_redis_password - -# ============================================================================= -# 5. SECURITY CONFIGURATION -# ============================================================================= -JWT_SECRET=staging-jwt-secret-key-not-for-production-use -JWT_ISSUER=meldestelle-api-staging -JWT_AUDIENCE=meldestelle-clients-staging -JWT_REALM=meldestelle-staging -API_KEY=staging-api-key-change-me - -# ============================================================================= -# 6. KEYCLOAK CONFIGURATION -# ============================================================================= -KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD=admin -KC_DB=postgres -KC_DB_URL=jdbc:postgresql://postgres:5432/meldestelle_staging -KC_DB_SCHEMA=keycloak -KC_DB_USERNAME=meldestelle_staging -KC_DB_PASSWORD=staging_password_change_me -KC_HOSTNAME=localhost - -# ============================================================================= -# 7. SERVICE DISCOVERY -# ============================================================================= -CONSUL_HOST=consul -CONSUL_ENABLED=true -SERVICE_DISCOVERY_ENABLED=true -SERVICE_DISCOVERY_REGISTER_SERVICES=true -SERVICE_DISCOVERY_HEALTH_CHECK_PATH=/health -SERVICE_DISCOVERY_HEALTH_CHECK_INTERVAL=15 - -# ============================================================================= -# 8. MESSAGING (Kafka) -# ============================================================================= -ZOOKEEPER_CLIENT_PORT=2181 -KAFKA_BROKER_ID=1 -KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 -KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT -KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT -KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 - -# ============================================================================= -# 9. MONITORING -# ============================================================================= -GF_SECURITY_ADMIN_USER=staging_admin -GF_SECURITY_ADMIN_PASSWORD=staging_grafana_password -GF_USERS_ALLOW_SIGN_UP=false - -METRICS_AUTH_USERNAME=staging_metrics -METRICS_AUTH_PASSWORD=staging_metrics_password - -GRAFANA_HOSTNAME=grafana-staging.meldestelle.local -PROMETHEUS_HOSTNAME=prometheus-staging.meldestelle.local - -# ============================================================================= -# 10. LOGGING CONFIGURATION -# ============================================================================= -LOGGING_LEVEL=INFO -LOGGING_REQUESTS=true -LOGGING_RESPONSES=false -LOGGING_REQUEST_HEADERS=false -LOGGING_REQUEST_BODY=false -LOGGING_RESPONSE_HEADERS=false -LOGGING_RESPONSE_BODY=false -LOGGING_STRUCTURED=true -LOGGING_CORRELATION_ID=true -LOGGING_REQUEST_ID_HEADER=X-Request-ID - -# ============================================================================= -# 11. CORS AND RATE LIMITING -# ============================================================================= -SERVER_CORS_ENABLED=true -SERVER_CORS_ALLOWED_ORIGINS=https://staging.meldestelle.local,https://app-staging.meldestelle.local -RATELIMIT_ENABLED=true -RATELIMIT_GLOBAL_LIMIT=500 -RATELIMIT_GLOBAL_PERIOD_MINUTES=1 -RATELIMIT_INCLUDE_HEADERS=true - -# ============================================================================= -# 12. SPRING PROFILES AND GATEWAY -# ============================================================================= -SPRING_PROFILES_ACTIVE=staging -GATEWAY_ADMIN_USER=staging_gateway_admin -GATEWAY_ADMIN_PASSWORD=staging_gateway_password diff --git a/config/.env.template b/config/.env.template deleted file mode 100644 index dc6f7b81..00000000 --- a/config/.env.template +++ /dev/null @@ -1,178 +0,0 @@ -# ============================================================================= -# Meldestelle - Umgebungsvariablen Vorlage -# ============================================================================= -# Dies ist die SINGLE SOURCE OF TRUTH für alle Umgebungsvariablen. -# Kopieren Sie zu .env.dev, .env.prod, .env.staging oder .env.test und anpassen. -# -# ⚠️ SICHERHEITSWARNUNG: -# - Niemals Produktions-Secrets in die Versionskontrolle committen -# - JWT_SECRET in der Produktion ändern -# - Starke Passwörter für Produktionsumgebungen verwenden -# - API-Schlüssel regelmäßig rotieren -# ============================================================================= - -# ============================================================================= -# 1. ANWENDUNGSKONFIGURATION -# ============================================================================= -APP_NAME=Meldestelle -APP_VERSION=1.0.0 -APP_DESCRIPTION='Pferdesport Meldestelle System' -APP_ENVIRONMENT=development -APP_HOST=0.0.0.0 - -# Entwicklungsspezifische Einstellungen -DEBUG_MODE=true -DEV_HOT_RELOAD=true - -# ============================================================================= -# 2. PORT-VERWALTUNG - SINGLE SOURCE OF TRUTH -# ============================================================================= -# Gateway Ports -GATEWAY_PORT=8081 -GATEWAY_ADMIN_PORT=8080 - -# Service Ports (eindeutige Zuteilung) -PING_SERVICE_PORT=8082 -MEMBERS_SERVICE_PORT=8083 -HORSES_SERVICE_PORT=8084 -EVENTS_SERVICE_PORT=8085 -MASTERDATA_SERVICE_PORT=8086 -AUTH_SERVICE_PORT=8087 - -# Infrastruktur Ports -CONSUL_PORT=8500 -REDIS_PORT=6379 -KAFKA_PORT=9092 -PROMETHEUS_PORT=9090 -GRAFANA_PORT=3000 - -# ============================================================================= -# 3. DATENBANK-KONFIGURATION (PostgreSQL) -# ============================================================================= -# Anwendungs-Datenbankeinstellungen -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=meldestelle -DB_USER=meldestelle -DB_PASSWORD=meldestelle -DB_MAX_POOL_SIZE=10 -DB_MIN_POOL_SIZE=5 -DB_AUTO_MIGRATE=true - -# Docker PostgreSQL Container-Einstellungen -POSTGRES_USER=meldestelle -POSTGRES_PASSWORD=meldestelle -POSTGRES_DB=meldestelle -POSTGRES_EXTERNAL_PORT=5432 - -# ============================================================================= -# 4. REDIS-KONFIGURATION -# ============================================================================= -# Event Store Konfiguration -REDIS_EVENT_STORE_HOST=localhost -REDIS_EVENT_STORE_PORT=6379 -REDIS_EVENT_STORE_PASSWORD= -REDIS_EVENT_STORE_DATABASE=0 -REDIS_EVENT_STORE_CONNECTION_TIMEOUT=2000 -REDIS_EVENT_STORE_READ_TIMEOUT=2000 -REDIS_EVENT_STORE_USE_POOLING=true -REDIS_EVENT_STORE_MAX_POOL_SIZE=8 -REDIS_EVENT_STORE_MIN_POOL_SIZE=2 - -# Cache-Konfiguration -REDIS_CACHE_HOST=localhost -REDIS_CACHE_PORT=6379 -REDIS_CACHE_PASSWORD= -REDIS_CACHE_DATABASE=1 - -# Redis Docker-Einstellungen -REDIS_EXTERNAL_PORT=6379 -REDIS_PASSWORD= - -# ============================================================================= -# 5. SICHERHEITSKONFIGURATION -# ============================================================================= -JWT_SECRET=meldestelle-jwt-secret-key-for-development-change-in-production -JWT_ISSUER=meldestelle-api -JWT_AUDIENCE=meldestelle-clients -JWT_REALM=meldestelle -API_KEY=meldestelle-api-key-for-development - -# ============================================================================= -# 6. KEYCLOAK CONFIGURATION -# ============================================================================= -KEYCLOAK_ADMIN=admin -KEYCLOAK_ADMIN_PASSWORD=admin -KC_DB=postgres -KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak -KC_DB_USERNAME=meldestelle -KC_DB_PASSWORD=meldestelle -KC_HOSTNAME=auth.meldestelle.local - -# ============================================================================= -# 7. SERVICE DISCOVERY (Consul) -# ============================================================================= -CONSUL_HOST=consul -CONSUL_ENABLED=true -SERVICE_DISCOVERY_ENABLED=true -SERVICE_DISCOVERY_REGISTER_SERVICES=true -SERVICE_DISCOVERY_HEALTH_CHECK_PATH=/health -SERVICE_DISCOVERY_HEALTH_CHECK_INTERVAL=10 - -# ============================================================================= -# 8. MESSAGING (Kafka) -# ============================================================================= -ZOOKEEPER_CLIENT_PORT=2181 -KAFKA_BROKER_ID=1 -KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 -KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT -KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT -KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 - -# ============================================================================= -# 9. MONITORING -# ============================================================================= -# Grafana Configuration -GF_SECURITY_ADMIN_USER=admin -GF_SECURITY_ADMIN_PASSWORD=admin -GF_USERS_ALLOW_SIGN_UP=false - -# Metrics Authentication -METRICS_AUTH_USERNAME=admin -METRICS_AUTH_PASSWORD=metrics - -# Monitoring hostnames -GRAFANA_HOSTNAME=grafana.meldestelle.local -PROMETHEUS_HOSTNAME=prometheus.meldestelle.local - -# ============================================================================= -# 10. LOGGING CONFIGURATION -# ============================================================================= -LOGGING_LEVEL=DEBUG -LOGGING_REQUESTS=true -LOGGING_RESPONSES=true -LOGGING_REQUEST_HEADERS=true -LOGGING_REQUEST_BODY=true -LOGGING_RESPONSE_HEADERS=true -LOGGING_RESPONSE_BODY=true -LOGGING_STRUCTURED=true -LOGGING_CORRELATION_ID=true -LOGGING_REQUEST_ID_HEADER=X-Request-ID - -# ============================================================================= -# 11. CORS AND RATE LIMITING -# ============================================================================= -SERVER_CORS_ENABLED=true -SERVER_CORS_ALLOWED_ORIGINS=* -RATELIMIT_ENABLED=true -RATELIMIT_GLOBAL_LIMIT=100 -RATELIMIT_GLOBAL_PERIOD_MINUTES=1 -RATELIMIT_INCLUDE_HEADERS=true - -# ============================================================================= -# 12. SPRING PROFILES AND GATEWAY -# ============================================================================= -SPRING_PROFILES_ACTIVE=dev -GATEWAY_ADMIN_USER=admin -GATEWAY_ADMIN_PASSWORD=admin diff --git a/config/.env.test b/config/.env.test deleted file mode 100644 index c1186e42..00000000 --- a/config/.env.test +++ /dev/null @@ -1,163 +0,0 @@ -# ============================================================================= -# Meldestelle - Test Environment Configuration -# ============================================================================= -# Test-specific environment variables (optimized for automated testing) -# ============================================================================= - -# ============================================================================= -# 1. APPLICATION CONFIGURATION -# ============================================================================= -APP_NAME=Meldestelle -APP_VERSION=1.0.0 -APP_DESCRIPTION='Pferdesport Meldestelle System' -APP_ENVIRONMENT=test -APP_HOST=localhost - -# Test settings (fast and minimal for CI/CD) -DEBUG_MODE=true -DEV_HOT_RELOAD=false - -# ============================================================================= -# 2. PORT MANAGEMENT -# ============================================================================= -# Gateway Ports (use different ports to avoid conflicts during parallel testing) -GATEWAY_PORT=9081 -GATEWAY_ADMIN_PORT=9080 - -# Service Ports -PING_SERVICE_PORT=9082 -MEMBERS_SERVICE_PORT=9083 -HORSES_SERVICE_PORT=9084 -EVENTS_SERVICE_PORT=9085 -MASTERDATA_SERVICE_PORT=9086 -AUTH_SERVICE_PORT=9087 - -# Infrastructure Ports -CONSUL_PORT=9500 -REDIS_PORT=9379 -KAFKA_PORT=9092 -PROMETHEUS_PORT=9090 -GRAFANA_PORT=9000 - -# ============================================================================= -# 3. DATABASE CONFIGURATION -# ============================================================================= -DB_HOST=localhost -DB_PORT=5433 -DB_NAME=meldestelle_test -DB_USER=meldestelle_test -DB_PASSWORD=test_password -DB_MAX_POOL_SIZE=5 -DB_MIN_POOL_SIZE=1 -DB_AUTO_MIGRATE=true - -POSTGRES_USER=meldestelle_test -POSTGRES_PASSWORD=test_password -POSTGRES_DB=meldestelle_test -POSTGRES_EXTERNAL_PORT=5433 - -# ============================================================================= -# 4. REDIS CONFIGURATION -# ============================================================================= -REDIS_EVENT_STORE_HOST=localhost -REDIS_EVENT_STORE_PORT=9379 -REDIS_EVENT_STORE_PASSWORD= -REDIS_EVENT_STORE_DATABASE=0 -REDIS_EVENT_STORE_CONNECTION_TIMEOUT=1000 -REDIS_EVENT_STORE_READ_TIMEOUT=1000 -REDIS_EVENT_STORE_USE_POOLING=true -REDIS_EVENT_STORE_MAX_POOL_SIZE=3 -REDIS_EVENT_STORE_MIN_POOL_SIZE=1 - -REDIS_CACHE_HOST=localhost -REDIS_CACHE_PORT=9379 -REDIS_CACHE_PASSWORD= -REDIS_CACHE_DATABASE=1 - -REDIS_EXTERNAL_PORT=9379 -REDIS_PASSWORD= - -# ============================================================================= -# 5. SECURITY CONFIGURATION -# ============================================================================= -JWT_SECRET=test-jwt-secret-key-for-testing-only -JWT_ISSUER=meldestelle-api-test -JWT_AUDIENCE=meldestelle-clients-test -JWT_REALM=meldestelle-test -API_KEY=test-api-key - -# ============================================================================= -# 6. KEYCLOAK CONFIGURATION -# ============================================================================= -KEYCLOAK_ADMIN=test_admin -KEYCLOAK_ADMIN_PASSWORD=test_password -KC_DB=postgres -KC_DB_URL=jdbc:postgresql://localhost:5433/keycloak_test -KC_DB_USERNAME=keycloak_test -KC_DB_PASSWORD=test_password -KC_HOSTNAME=localhost - -# ============================================================================= -# 7. SERVICE DISCOVERY -# ============================================================================= -CONSUL_HOST=localhost -CONSUL_ENABLED=false -SERVICE_DISCOVERY_ENABLED=false -SERVICE_DISCOVERY_REGISTER_SERVICES=false -SERVICE_DISCOVERY_HEALTH_CHECK_PATH=/health -SERVICE_DISCOVERY_HEALTH_CHECK_INTERVAL=5 - -# ============================================================================= -# 8. MESSAGING (Kafka) -# ============================================================================= -ZOOKEEPER_CLIENT_PORT=2182 -KAFKA_BROKER_ID=1 -KAFKA_ZOOKEEPER_CONNECT=localhost:2182 -KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 -KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT -KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT -KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 - -# ============================================================================= -# 9. MONITORING -# ============================================================================= -GF_SECURITY_ADMIN_USER=test_admin -GF_SECURITY_ADMIN_PASSWORD=test_password -GF_USERS_ALLOW_SIGN_UP=false - -METRICS_AUTH_USERNAME=test_metrics -METRICS_AUTH_PASSWORD=test_password - -GRAFANA_HOSTNAME=localhost -PROMETHEUS_HOSTNAME=localhost - -# ============================================================================= -# 10. LOGGING CONFIGURATION -# ============================================================================= -LOGGING_LEVEL=DEBUG -LOGGING_REQUESTS=true -LOGGING_RESPONSES=true -LOGGING_REQUEST_HEADERS=true -LOGGING_REQUEST_BODY=true -LOGGING_RESPONSE_HEADERS=true -LOGGING_RESPONSE_BODY=true -LOGGING_STRUCTURED=true -LOGGING_CORRELATION_ID=true -LOGGING_REQUEST_ID_HEADER=X-Request-ID - -# ============================================================================= -# 11. CORS AND RATE LIMITING -# ============================================================================= -SERVER_CORS_ENABLED=true -SERVER_CORS_ALLOWED_ORIGINS=* -RATELIMIT_ENABLED=false -RATELIMIT_GLOBAL_LIMIT=10000 -RATELIMIT_GLOBAL_PERIOD_MINUTES=1 -RATELIMIT_INCLUDE_HEADERS=true - -# ============================================================================= -# 12. SPRING PROFILES AND GATEWAY -# ============================================================================= -SPRING_PROFILES_ACTIVE=test -GATEWAY_ADMIN_USER=test_admin -GATEWAY_ADMIN_PASSWORD=test_password diff --git a/config/README.md b/config/README.md deleted file mode 100644 index 274f77f2..00000000 --- a/config/README.md +++ /dev/null @@ -1,269 +0,0 @@ -# Zentrale Konfigurationsverwaltung - Single Source of Truth - -> **Version:** 4.0.0 -> **Datum:** 15. September 2025 -> **Status:** ✅ Produktiv - Eliminiert 38+ Port-Redundanzen und 72+ Spring-Profile-Duplikate - -## 🎯 Überblick - -Das **zentrale Konfigurationssystem** eliminiert Redundanzen über das gesamte Meldestelle-Projekt und stellt sicher, dass alle Konfigurationswerte aus einer **einzigen Quelle der Wahrheit** stammen. - -### Vor der Zentralisierung (Problem) - -``` -Port 8082 war in 38+ Dateien dupliziert: -├── gradle.properties -├── docker-compose.services.yml -├── dockerfiles/services/ping-service/Dockerfile -├── scripts/test/integration-test.sh -├── config/monitoring/prometheus.dev.yml -└── ... 33 weitere Dateien! -``` - -### Nach der Zentralisierung (Lösung) - -``` -Port 8082 einmalig in config/central.toml definiert: -├── config/central.toml [SINGLE SOURCE OF TRUTH] -└── scripts/config-sync.sh sync [Automatische Synchronisation] - └── 38+ Dateien automatisch aktualisiert ✓ -``` - -## 📁 Verzeichnisstruktur - -``` -config/ -├── central.toml # 🎯 MASTER-Konfigurationsdatei -├── README.md # 📖 Diese Dokumentation -├── .env.template # 🔧 Environment-Variables Template (Legacy) -└── monitoring/ # 📊 Monitoring-Konfigurationen - ├── prometheus.yml - ├── prometheus.dev.yml - └── grafana/ -``` - -## 🛠️ Verwendung - -### Schnellstart - -```bash -# 1. Aktuelle Konfiguration anzeigen -./scripts/config-sync.sh status - -# 2. Alle Konfigurationen synchronisieren -./scripts/config-sync.sh sync - -# 3. Konfiguration validieren -./scripts/config-sync.sh validate -``` - -### Port ändern (Beispiel) - -```bash -# 1. central.toml bearbeiten -vim config/central.toml - -[ports] -ping-service = 8092 # Geändert von 8082 - -# 2. Alle abhängigen Dateien aktualisieren -./scripts/config-sync.sh sync - -# ✅ Ergebnis: 38+ Dateien automatisch synchronisiert! -``` - -### Spring Profile ändern - -```bash -# 1. central.toml bearbeiten -[spring-profiles.defaults] -services = "production" # Geändert von "docker" - -# 2. Synchronisieren -./scripts/config-sync.sh sync - -# ✅ Ergebnis: 72+ Profile-Referenzen automatisch aktualisiert! -``` - -## 📋 Konfigurationsbereiche - -### 1. **Ports** - Eliminiert 38+ Redundanzen - -```toml -[ports] -# Infrastructure Services -api-gateway = 8081 -auth-server = 8087 -monitoring-server = 8088 - -# Application Services -ping-service = 8082 -members-service = 8083 -horses-service = 8084 -events-service = 8085 -masterdata-service = 8086 - -# External Infrastructure -postgres = 5432 -redis = 6379 -consul = 8500 -prometheus = 9090 -grafana = 3000 -``` - -**Synchronisiert folgende Dateien:** - -- `gradle.properties` - Service-Port-Eigenschaften -- `docker-compose*.yml` - Port-Mappings und Environment-Variablen -- `dockerfiles/*/Dockerfile` - EXPOSE-Statements -- `scripts/test/*.sh` - Test-Endpunkt-URLs -- `config/monitoring/*.yml` - Prometheus-Targets -- Und 25+ weitere Dateien! - -### 2. **Spring Profiles** - Eliminiert 72+ Duplikate - -```toml -[spring-profiles] -default = "default" -development = "dev" -docker = "docker" -production = "prod" -test = "test" - -[spring-profiles.defaults] -infrastructure = "default" # Infrastructure Services -services = "docker" # Application Services -clients = "dev" # Client Applications -``` - -**Synchronisiert folgende Dateien:** - -- Alle `dockerfiles/*/Dockerfile` - `SPRING_PROFILES_ACTIVE` Build-Args -- `docker-compose*.yml` - Spring-Profile Environment-Variablen -- `docker/build-args/*.env` - Build-Argument-Dateien -- Und 60+ weitere Referenzen! - -### 3. **Service Discovery** - Standardisiert URLs - -```toml -[services.ping-service] -name = "ping-service" -port = 8082 -internal-host = "ping-service" -external-host = "localhost" -internal-url = "http://ping-service:8082" -external-url = "http://localhost:8082" -health-endpoint = "/actuator/health/readiness" -metrics-endpoint = "/actuator/prometheus" -info-endpoint = "/actuator/info" -``` - -## 🚀 Scripts und Automatisierung - -### `scripts/config-sync.sh` - Haupttool - -```bash -# Alle Konfigurationen synchronisieren -./scripts/config-sync.sh sync - -# Nur bestimmte Bereiche synchronisieren -./scripts/config-sync.sh gradle # gradle.properties -./scripts/config-sync.sh compose # Docker Compose files -./scripts/config-sync.sh env # Environment files -./scripts/config-sync.sh docker-args # Docker build arguments -./scripts/config-sync.sh monitoring # Prometheus/Grafana config -./scripts/config-sync.sh tests # Test scripts - -# Status und Validierung -./scripts/config-sync.sh status # Aktuelle Konfiguration anzeigen -./scripts/config-sync.sh validate # TOML-Syntax validieren - -# Hilfe -./scripts/config-sync.sh --help -``` - -## 🎯 Best Practices - -### ✅ DO (Empfohlen) - -```bash -# Vor Änderungen Status prüfen -./scripts/config-sync.sh status - -# Nach Änderungen validieren -./scripts/config-sync.sh validate - -# Regelmäßig synchronisieren -./scripts/config-sync.sh sync - -# Backups vor wichtigen Änderungen -cp config/central.toml config/central.toml.backup -``` - -### ❌ DON'T (Vermeiden) - -```bash -# ❌ Niemals direkte Datei-Bearbeitung -vim docker-compose.yml # Änderungen gehen verloren! -vim gradle.properties # Wird überschrieben! - -# ✅ Stattdessen zentrale Konfiguration verwenden -vim config/central.toml -./scripts/config-sync.sh sync -``` - -## 🔍 Debugging und Troubleshooting - -### Häufige Probleme - -#### Problem: Synchronisation schlägt fehl - -```bash -# Lösung: Validierung prüfen -./scripts/config-sync.sh validate - -# TOML-Syntax-Fehler beheben -vim config/central.toml -``` - -#### Problem: Inkonsistente Konfiguration - -```bash -# Lösung: Status prüfen und re-synchronisieren -./scripts/config-sync.sh status -./scripts/config-sync.sh sync -``` - -#### Problem: Backup wiederherstellen - -```bash -# Backups anzeigen -ls -la *.bak.* - -# Wiederherstellen -cp gradle.properties.bak.20250915_103927 gradle.properties -``` - -### Validierung - -```bash -# Umfassende Validierung -./scripts/config-sync.sh validate - -# Prüft: -# ✓ TOML-Syntax -# ✓ Duplicate Sections -# ✓ Port-Konflikte -# ✓ Ungültige Werte -``` - -## 🚀 Migration und Integration - -Die zentrale Konfigurationsverwaltung ist **rückwärtskompatibel** und kann schrittweise eingeführt werden: - -1. **config/central.toml** erstellen ✅ -2. **scripts/config-sync.sh** ausführen ✅ -3. **Backups prüfen** und validieren ✅ -4. **Entwickler-Workflow** anpassen ✅ - -**🎉 Mit der zentralen Konfigurationsverwaltung haben Sie einen wartungsfreundlichen, skalierbaren und fehlerresistenten Ansatz für die Verwaltung aller Konfigurationswerte in Ihrem Meldestelle-Projekt!** diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 00000000..272018e3 --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,52 @@ +build: + maxIssues: 0 + excludeCorrectable: false + +config: + validation: true + warningsAsErrors: false + +processors: + active: true + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + +style: + active: true + MagicNumber: + active: false + WildcardImport: + active: false + MaxLineLength: + active: true + maxLineLength: 140 + UnusedImports: + active: true + +complexity: + active: true + LongMethod: + active: true + threshold: 80 + TooManyFunctions: + active: true + thresholdInClasses: 30 + +performance: + active: true + +potential-bugs: + active: true + +exceptions: + active: true diff --git a/config/postgres/postgresql.conf b/config/postgres/postgresql.conf index 507689ad..dea4e80c 100644 --- a/config/postgres/postgresql.conf +++ b/config/postgres/postgresql.conf @@ -7,7 +7,7 @@ max_connections = 100 superuser_reserved_connections = 3 # Memory Settings -# These will be overridden by environment variables in docker-compose.yml +# These will be overridden by environment variables in docker-compose.yaml shared_buffers = 256MB # min 128kB work_mem = 16MB # min 64kB maintenance_work_mem = 64MB # min 1MB diff --git a/.env b/docker/.env similarity index 86% rename from .env rename to docker/.env index 8b623a70..5e831ef4 100644 --- a/.env +++ b/docker/.env @@ -51,7 +51,13 @@ GATEWAY_SERVER_PORT=8081 PING_SERVICE_PORT=8082:8082 PING_DEBUG_PORT=5006:5006 -# --- CLIENT APPLICATIONS --- -WEB_APP_PORT=4000:4000 +# --- WEB CLIENTS --- +# Web-App (Nginx inside container listens on 80) +WEB_APP_PORT=8080:80 + +# Desktop-App (VNC + noVNC) DESKTOP_APP_VNC_PORT=5901:5901 DESKTOP_APP_NOVNC_PORT=6080:6080 + +# Optional: Redis Passwort aktivieren (setzt --requirepass) +# REDIS_PASSWORD=change-me-strong diff --git a/docker/.env.example b/docker/.env.example index 441e1f8c..7c70fdd5 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,3 +1,31 @@ +<<<<<<< HEAD +# ========================================== +# Meldestelle – Docker Compose Environment +# Single Source of Truth (SSoT) +# ========================================== + +# --- PROJECT --- +COMPOSE_PROJECT_NAME=meldestelle + +# --- PORT MAPPINGS (host:container) --- +POSTGRES_PORT=5432:5432 +REDIS_PORT=6379:6379 +KC_PORT=8180:8080 +PGADMIN_PORT=8888:80 +PROMETHEUS_PORT=9090:9090 +GF_PORT=3000:3000 +CONSUL_PORT=8500:8500 +GATEWAY_PORT=8081:8081 +GATEWAY_DEBUG_PORT=5005:5005 +GATEWAY_SERVER_PORT=8081 +PING_SERVICE_PORT=8082:8082 +PING_DEBUG_PORT=5006:5006 +WEB_APP_PORT=4000:4000 +DESKTOP_APP_VNC_PORT=5901:5901 +DESKTOP_APP_NOVNC_PORT=6080:6080 + +# --- POSTGRES --- +======= # Core project name used as prefix for container names COMPOSE_PROJECT_NAME=meldestelle @@ -18,15 +46,35 @@ DESKTOP_APP_VNC_PORT=5900:5900 DESKTOP_APP_NOVNC_PORT=6080:6080 # Postgres +>>>>>>> origin/main POSTGRES_USER=meldestelle POSTGRES_PASSWORD=meldestelle POSTGRES_DB=meldestelle +<<<<<<< HEAD +# --- REDIS --- +# Optional password for Redis; leave empty to disable authentication in dev +REDIS_PASSWORD= + +# --- KEYCLOAK --- +======= # Keycloak +>>>>>>> origin/main KC_ADMIN_USER=admin KC_ADMIN_PASSWORD=admin KC_HOSTNAME=localhost +<<<<<<< HEAD +# --- PGADMIN --- +PGADMIN_EMAIL=admin@example.com +PGADMIN_PASSWORD=admin + +# --- GRAFANA --- +GF_ADMIN_USER=admin +GF_ADMIN_PASSWORD=admin + +# --- DOCKER BUILD OVERRIDES (optional) --- +======= # PgAdmin PGADMIN_EMAIL=admin@example.com PGADMIN_PASSWORD=admin @@ -36,6 +84,7 @@ GF_ADMIN_USER=admin GF_ADMIN_PASSWORD=admin # Docker build versions (optional overrides) +>>>>>>> origin/main DOCKER_GRADLE_VERSION=9.1.0 DOCKER_JAVA_VERSION=21 DOCKER_NODE_VERSION=22.21.0 diff --git a/docker/compose.hardcoded.yaml b/docker/compose.hardcoded.yaml new file mode 100644 index 00000000..edf08806 --- /dev/null +++ b/docker/compose.hardcoded.yaml @@ -0,0 +1,153 @@ +name: meldestelle-hardcoded + +services: + # --- DATENBANK --- + postgres: + image: postgres:16-alpine + container_name: meldestelle-postgres + restart: unless-stopped + ports: + - "5432:5432" + environment: + POSTGRES_USER: pg-user + POSTGRES_PASSWORD: pg-password + POSTGRES_DB: meldestelle + volumes: + - postgres-data:/var/lib/postgresql/data + # Falls du Init-Scripte hast, lassen wir die erstmal weg, + # um Fehlerquellen zu reduzieren, oder lassen den Pfad, falls er existiert: + - ./docker/core/postgres:/docker-entrypoint-initdb.d:Z + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U pg-user -d meldestelle" ] + interval: 1s + timeout: 5s + retries: 3 + start_period: 30s + networks: + - meldestelle-network + + # --- DATENBANK-MANAGEMENT-TOOL --- + pgadmin: + image: dpage/pgadmin4:8 + container_name: pgadmin4_container + restart: unless-stopped + ports: + - "8888:80" + environment: + PGADMIN_DEFAULT_EMAIL: user@domain.com + PGADMIN_DEFAULT_PASSWORD: strong-password + volumes: + - pgadmin-data:/var/lib/pgadmin + healthcheck: + test: [ "CMD-SHELL", "wget --spider -q http://localhost:80/ || exit 1" ] + interval: 1s + timeout: 5s + retries: 3 + start_period: 30s + networks: + - meldestelle-network + + # --- CACHE --- + redis: + image: redis:7-alpine + container_name: meldestelle-redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis-data:/data + command: redis-server --appendonly yes + healthcheck: + test: [ "CMD", "redis-cli" ] + interval: 1s + timeout: 5s + retries: 3 + networks: + - meldestelle-network + + # --- IDENTITY PROVIDER (Wartet auf Postgres) --- + keycloak: + image: quay.io/keycloak/keycloak:26.4 + container_name: meldestelle-keycloak + restart: unless-stopped + environment: + KC_HEALTH_ENABLED: true + KC_METRICS_ENABLED: true + KC_BOOTSTRAP_ADMIN_USERNAME: kc-admin + KC_BOOTSTRAP_ADMIN_PASSWORD: kc-password + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://postgres:5432/meldestelle + KC_DB_USERNAME: pg-user + KC_DB_PASSWORD: pg-password + KC_HOSTNAME: localhost + ports: + - "8180:8080" + depends_on: + postgres: + condition: service_healthy + volumes: + - ./docker/core/keycloak:/opt/keycloak/data/import:Z + command: start-dev --import-realm + healthcheck: + test: [ "CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000" ] + interval: 20s + timeout: 10s + retries: 5 + start_period: 60s + networks: + - meldestelle-network + + # --- MONITORING --- + prometheus: + image: prom/prometheus:v2.54.1 + container_name: meldestelle-prometheus + restart: unless-stopped + ports: + - "9090:9090" + volumes: + - prometheus-data:/prometheus + - ./docker/monitoring/prometheus:/etc/prometheus:Z + command: + - --config.file=/etc/prometheus/prometheus.yml + - --storage.tsdb.retention.time=15d + healthcheck: + test: [ "CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy" ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + networks: + - meldestelle-network + + grafana: + image: grafana/grafana:11.3.0 + container_name: meldestelle-grafana + environment: + GF_SECURITY_ADMIN_USER: gf-admin + GF_SECURITY_ADMIN_PASSWORD: gf-password + ports: + - "3000:3000" + volumes: + - grafana-data:/var/lib/grafana + - ./docker/monitoring/grafana:/etc/grafana/provisioning:Z + depends_on: + - prometheus + healthcheck: + test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health" ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + networks: + - meldestelle-network + +volumes: + postgres-data: + pgadmin-data: + redis-data: + prometheus-data: + grafana-data: + +networks: + meldestelle-network: + driver: bridge diff --git a/dockerfiles/infrastructure/keycloak/Dockerfile b/docker/core/keycloak/Dockerfile similarity index 100% rename from dockerfiles/infrastructure/keycloak/Dockerfile rename to docker/core/keycloak/Dockerfile diff --git a/docker/docker-compose.clients.yml b/docker/docker-compose.clients.yaml similarity index 100% rename from docker/docker-compose.clients.yml rename to docker/docker-compose.clients.yaml diff --git a/docker/docker-compose.services.yml b/docker/docker-compose.services.yaml similarity index 100% rename from docker/docker-compose.services.yml rename to docker/docker-compose.services.yaml diff --git a/docker/docker-compose.yml b/docker/docker-compose.yaml similarity index 84% rename from docker/docker-compose.yml rename to docker/docker-compose.yaml index 8ff0bfba..fce1ac66 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yaml @@ -17,6 +17,10 @@ services: volumes: - postgres-data:/var/lib/postgresql/data - ./core/postgres:/docker-entrypoint-initdb.d:Z + # Central postgres.conf from config (optional) + - ../config/postgres/postgresql.conf:/etc/postgresql/postgresql.conf:Z + # Use central postgresql.conf if present + command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] interval: 5s @@ -36,7 +40,10 @@ services: - "${REDIS_PORT}" volumes: - redis-data:/data - command: redis-server --appendonly yes + # Central redis config + - ../config/redis/redis.conf:/usr/local/etc/redis/redis.conf:Z + # Use central redis.conf and optionally add --requirepass if REDIS_PASSWORD is set + command: ["sh", "-lc", "exec redis-server /usr/local/etc/redis/redis.conf ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"] healthcheck: test: [ "CMD", "redis-cli" ] interval: 5s @@ -112,7 +119,8 @@ services: - "${PROMETHEUS_PORT}" volumes: - prometheus-data:/prometheus - - ./monitoring/prometheus:/etc/prometheus:Z + # Use central config as single source of truth + - ../config/monitoring/prometheus:/etc/prometheus:Z command: - --config.file=/etc/prometheus/prometheus.yml - --storage.tsdb.retention.time=15d @@ -138,7 +146,10 @@ services: - "${GF_PORT}" volumes: - grafana-data:/var/lib/grafana - - ./monitoring/grafana:/etc/grafana/provisioning:Z + # Provisioning (datasources/dashboards) from central config + - ../config/monitoring/grafana/provisioning:/etc/grafana/provisioning:Z + # Dashboards directory (referenced by provisioning file path: /var/lib/grafana/dashboards) + - ../config/monitoring/grafana/dashboards:/var/lib/grafana/dashboards:Z depends_on: - prometheus healthcheck: @@ -176,7 +187,7 @@ services: api-gateway: build: context: .. - dockerfile: dockerfiles/infrastructure/gateway/Dockerfile + dockerfile: backend/infrastructure/gateway/Dockerfile args: # Build-Args aus deinen .env Dateien (werden hier statisch benötigt für den Build) GRADLE_VERSION: 9.1.0 @@ -195,13 +206,13 @@ services: # --- VERBINDUNGEN --- # Keycloak URL (INTERN im Docker Netzwerk!) # Beachte: http://container-name:8080 (nicht localhost, nicht 8180) - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: http://${COMPOSE_PROJECT_NAME}-keycloak:8080/realms/meldestelle + SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: http://keycloak:8080/realms/meldestelle SPRING_CLOUD_CONSUL_HOST: consul SPRING_CLOUD_CONSUL_PORT: 8500 # WICHTIG: Das Gateway muss wissen, wie es von anderen Containern erreicht wird (nicht localhost!) SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME: api-gateway # Postgres Verbindung (für Routes/Session, falls nötig) - SPRING_DATASOURCE_URL: jdbc:postgresql://${COMPOSE_PROJECT_NAME}-postgres:5432/${POSTGRES_DB} + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB} SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER} SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} # Logging @@ -225,7 +236,7 @@ services: ping-service: build: context: .. - dockerfile: dockerfiles/services/ping-service/Dockerfile + dockerfile: backend/services/ping/Dockerfile args: GRADLE_VERSION: 9.1.0 JAVA_VERSION: 21 @@ -247,13 +258,13 @@ services: SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME: ping-service # - DATENBANK VERBINDUNG - - SPRING_DATASOURCE_URL: jdbc:postgresql://${COMPOSE_PROJECT_NAME}-postgres:5432/${POSTGRES_DB} + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB} SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER} SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} SPRING_JPA_HIBERNATE_DDL_AUTO: validate # --- REDIS --- - SPRING_DATA_REDIS_HOST: ${COMPOSE_PROJECT_NAME}-redis + SPRING_DATA_REDIS_HOST: redis SPRING_DATA_REDIS_PORT: 6379 depends_on: consul: @@ -273,7 +284,7 @@ services: web-app: build: context: .. - dockerfile: dockerfiles/clients/web-app/Dockerfile + dockerfile: docker/frontends/web-app/Dockerfile args: GRADLE_VERSION: ${DOCKER_GRADLE_VERSION:-9.1.0} JAVA_VERSION: ${DOCKER_JAVA_VERSION:-21} @@ -284,6 +295,9 @@ services: restart: unless-stopped ports: - "${WEB_APP_PORT}" + volumes: + # Mount production nginx config (can be adjusted per env) + - ../config/nginx/nginx.prod.conf:/etc/nginx/nginx.conf:Z,ro depends_on: api-gateway: condition: service_started @@ -295,7 +309,7 @@ services: desktop-app: build: context: .. - dockerfile: dockerfiles/clients/desktop-app/Dockerfile + dockerfile: docker/frontends/desktop-app/Dockerfile container_name: ${COMPOSE_PROJECT_NAME}-desktop-app restart: unless-stopped environment: diff --git a/dockerfiles/clients/desktop-app/Dockerfile b/docker/frontends/desktop-app/Dockerfile similarity index 100% rename from dockerfiles/clients/desktop-app/Dockerfile rename to docker/frontends/desktop-app/Dockerfile diff --git a/dockerfiles/clients/desktop-app/entrypoint.sh b/docker/frontends/desktop-app/entrypoint.sh similarity index 100% rename from dockerfiles/clients/desktop-app/entrypoint.sh rename to docker/frontends/desktop-app/entrypoint.sh diff --git a/dockerfiles/clients/desktop-app/health-check.sh b/docker/frontends/desktop-app/health-check.sh similarity index 100% rename from dockerfiles/clients/desktop-app/health-check.sh rename to docker/frontends/desktop-app/health-check.sh diff --git a/dockerfiles/clients/desktop-app/supervisord.conf b/docker/frontends/desktop-app/supervisord.conf similarity index 100% rename from dockerfiles/clients/desktop-app/supervisord.conf rename to docker/frontends/desktop-app/supervisord.conf diff --git a/dockerfiles/clients/web-app/Dockerfile b/docker/frontends/web-app/Dockerfile similarity index 100% rename from dockerfiles/clients/web-app/Dockerfile rename to docker/frontends/web-app/Dockerfile diff --git a/dockerfiles/clients/web-app/downloads/index.html b/docker/frontends/web-app/downloads/index.html similarity index 100% rename from dockerfiles/clients/web-app/downloads/index.html rename to docker/frontends/web-app/downloads/index.html diff --git a/dockerfiles/clients/web-app/nginx.conf b/docker/frontends/web-app/nginx.conf similarity index 100% rename from dockerfiles/clients/web-app/nginx.conf rename to docker/frontends/web-app/nginx.conf diff --git a/dockerfiles/infrastructure/monitoring-server/Dockerfile b/docker/monitoring/Dockerfile similarity index 100% rename from dockerfiles/infrastructure/monitoring-server/Dockerfile rename to docker/monitoring/Dockerfile diff --git a/dockerfiles/templates/kotlin-multiplatform-web.Dockerfile b/docker/templates/kotlin-multiplatform-web.Dockerfile similarity index 100% rename from dockerfiles/templates/kotlin-multiplatform-web.Dockerfile rename to docker/templates/kotlin-multiplatform-web.Dockerfile diff --git a/dockerfiles/templates/spring-boot-service.Dockerfile b/docker/templates/spring-boot-service.Dockerfile similarity index 100% rename from dockerfiles/templates/spring-boot-service.Dockerfile rename to docker/templates/spring-boot-service.Dockerfile diff --git a/docs/clients/visionen/AntwortenOffenerFragenArchitekturReview.md b/docs/clients/visionen/AntwortenOffenerFragenArchitekturReview.md index 2e47083a..2dca2d81 100644 --- a/docs/clients/visionen/AntwortenOffenerFragenArchitekturReview.md +++ b/docs/clients/visionen/AntwortenOffenerFragenArchitekturReview.md @@ -9,7 +9,7 @@ **Eintrag im Guide:** -```kotlin +```text // GUIDELINE: Dependency Injection // Wir nutzen Koin. Module werden im `di` Package des Features definiert. @@ -25,7 +25,8 @@ val inventoryModule = module { // 2. Nutzung des ApiClients (Best Practice) // Wir injizieren IMMER den "apiClient" (mit Auth-Header), niemals den Default Client. val networkModule = module { - single(named("apiClient")) { ... } // Konfiguriert in :core:network + // Hinweis: Platzhalter (kein ausführbarer Code im Dokument) + single(named("apiClient")) { /* bereitgestellt in :core:network */ } } val myFeatureModule = module { @@ -133,14 +134,14 @@ suspend fun updateStock(item: Item) { **Eintrag im Guide:** -```kotlin +```text // GUIDELINE: Feature Isolation // 1. Features importieren NIEMALS andere Features im `build.gradle.kts`. // 2. Kommunikation nur über Navigation (Router). // 3. Gemeinsam genutzte Datenobjekte (z.B. UserID, ShopID) liegen in :core:domain. // FALSCH: -import com.project.features.billing.Invoice // Abhängigkeit zu anderem Feature! +// import com.project.features.billing.Invoice // Abhängigkeit zu anderem Feature! (nur zu Illustrationszwecken) // RICHTIG: // Feature A navigiert zu Feature B via Route diff --git a/docs/clients/visionen/MultiplatformApp_Ktor_SQLDelight_Doku.pdf b/docs/clients/visionen/MultiplatformApp_Ktor_SQLDelight_Doku.pdf new file mode 100644 index 00000000..50930d44 Binary files /dev/null and b/docs/clients/visionen/MultiplatformApp_Ktor_SQLDelight_Doku.pdf differ diff --git a/docs/how-to/start-local.md b/docs/how-to/start-local.md deleted file mode 100644 index fb65c857..00000000 --- a/docs/how-to/start-local.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -owner: project-maintainers -status: active -review_cycle: 90d -last_reviewed: 2025-10-15 -summary: Kürzeste Anleitung, um das komplette System lokal zu starten und zu prüfen, ob alles läuft. -bc: infrastructure -doc_type: how-to ---- - -# How-To: Lokale Umgebung starten (Quickstart) - -Diese Kurz-Anleitung bringt deine lokale Umgebung in wenigen Minuten zum Laufen. - -## Starten - -- Komplettes System (Infra + Services + Clients) - -```bash -make full-up -``` - -- Nur Backend (Infra + Gateway + Microservices) - -```bash -make services-up -``` - -- Nur Clients (Infra + Web-App) - -```bash -make clients-up -``` - -Logs ansehen (z. B. Backend): - -```bash -make services-logs -``` - -## Weitere Makefile-Befehle - -Für eine vollständige Liste aller verfügbaren Befehle siehe: - -- `make help` (im Terminal) -- [Docker Development Guide](../../.junie/guidelines/technology-guides/docker/docker-development.md#-vollständige-makefile-referenz) - -## Wichtige URLs - -- Web App: -- API Gateway: (Health: /actuator/health) -- Keycloak (Auth): -- Consul (Service Discovery): - -Weitere Ports findest du unter: [reference/ports-and-urls.md](../reference/ports-and-urls.md) - -## Health-Checks - -```bash -# Gateway -curl -i http://localhost:8081/actuator/health - -# Web-App (falls vorhanden) -curl -i http://localhost:4000/health || true -``` - -## Auth (Keycloak) - -- Admin-Login (default): - - - Username: KC_BOOTSTRAP_ADMIN_USERNAME (default: admin) - - Password: KC_BOOTSTRAP_ADMIN_PASSWORD (default: admin) - -- Beim ersten Start wird der Realm aus docker/services/keycloak/meldestelle-realm.json importiert. - -## Häufige Probleme - -- Dienste sind nicht erreichbar → Container laufen? `make full-logs` bzw. `make services-logs` prüfen. -- 401/403 beim API-Aufruf → Prüfen, ob ein gültiges Bearer-Token gesendet wird und Keycloak erreichbar ist. -- CORS im Browser → API über das Gateway () aufrufen und nicht direkt die Services (8082–8086). -- Port-Kollisionen → Belegte Ports mit `lsof -i :PORT` prüfen oder Ports anpassen. - -## Stoppen - -```bash - make full-down - # oder spezifisch: - make services-down - make clients-down - make infrastructure-down -``` diff --git a/domains/events/README-EVENTS.md b/domains/events/README-EVENTS.md deleted file mode 100644 index 019c9bf7..00000000 --- a/domains/events/README-EVENTS.md +++ /dev/null @@ -1,473 +0,0 @@ -# Events Module - -## Überblick - -Das Events-Modul ist eine umfassende Lösung zur Verwaltung von Pferdesportveranstaltungen. Es implementiert eine saubere Architektur mit Domain-Driven Design und bietet vollständige CRUD-Operationen sowie erweiterte Geschäftslogik für die Veranstaltungsplanung und -verwaltung. - -## Funktionalität - -### Verwaltete Entität - -#### Veranstaltung (Event) - -- **Grundinformationen**: Name, Beschreibung -- **Terminverwaltung**: Startdatum, Enddatum, Anmeldeschluss -- **Ort und Organisation**: Veranstaltungsort, Veranstalter-Verein-ID -- **Veranstaltungsdetails**: Sparten, Aktivitätsstatus, Öffentlichkeit, maximale Teilnehmerzahl -- **Audit-Felder**: Erstellungs- und Aktualisierungszeitstempel -- **Geschäftslogik**: Validierung, Anmeldestatus, Dauernberechnung - -### Geschäftsoperationen - -Das Modul bietet 10+ spezialisierte Repository-Operationen: - -#### Basis-CRUD-Operationen - -- `findById(id)` - Veranstaltung nach UUID suchen -- `save(veranstaltung)` - Veranstaltung speichern (erstellen/aktualisieren) -- `delete(id)` - Veranstaltung löschen - -#### Such-Operationen - -- `findByName(searchTerm, limit)` - Nach Namen suchen (Teilübereinstimmung) -- `findByVeranstalterVereinId(vereinId, activeOnly)` - Veranstaltungen eines Vereins -- `findAllActive(limit, offset)` - Alle aktiven Veranstaltungen -- `findPublicEvents(activeOnly)` - Öffentliche Veranstaltungen - -#### Datumsbasierte Abfragen - -- `findByDateRange(startDate, endDate, activeOnly)` - Veranstaltungen in Datumsbereich -- `findByStartDate(date, activeOnly)` - Veranstaltungen nach Startdatum - -#### Zähl-Operationen - -- `countActive()` - Anzahl aktiver Veranstaltungen -- `countByVeranstalterVereinId(vereinId, activeOnly)` - Anzahl Veranstaltungen pro Verein - -## Architektur - -Das Modul folgt der Clean Architecture mit klarer Trennung der Verantwortlichkeiten: - -``` -events/ -├── events-domain/ # Domain Layer -│ ├── model/ # Domain Models -│ │ └── Veranstaltung.kt # Veranstaltungs-Entität mit Geschäftslogik -│ ├── repository/ # Repository Interfaces -│ │ └── VeranstaltungRepository.kt # 10+ Geschäftsoperationen -│ └── EventManagement.kt # Domain Service/Facade -├── events-application/ # Application Layer -│ └── usecase/ # Use Cases -│ ├── CreateVeranstaltungUseCase.kt -│ ├── GetVeranstaltungUseCase.kt -│ ├── UpdateVeranstaltungUseCase.kt -│ └── DeleteVeranstaltungUseCase.kt -├── events-infrastructure/ # Infrastructure Layer -│ └── persistence/ # Database Implementation -│ ├── VeranstaltungRepositoryImpl.kt -│ └── VeranstaltungTable.kt -├── events-api/ # API Layer -│ └── rest/ # REST Controllers -│ └── VeranstaltungController.kt -└── events-service/ # Service Layer - └── EventsServiceApplication.kt -``` - -### Domain Layer - -- **1 Domain Model** mit reichhaltiger Geschäftslogik -- **1 Repository Interface** mit 10+ Geschäftsoperationen -- **Domain Service** für komplexe Veranstaltungslogik -- **Keine Abhängigkeiten** zu anderen Layern - -### Application Layer - -- **Use Cases** für CRUD-Operationen -- **Orchestrierung** von Domain-Services -- **Anwendungslogik** ohne UI-Abhängigkeiten - -### Infrastructure Layer - -- **Datenbankzugriff** mit Exposed ORM -- **Repository-Implementierung** mit PostgreSQL -- **Datenbankschema** und Migrationen - -### API Layer - -- **REST-Controller** für HTTP-Endpunkte -- **DTO-Mapping** zwischen Domain und API -- **Validierung** und Fehlerbehandlung - -### Service Layer - -- **Spring Boot Anwendung** -- **Dependency Injection** Konfiguration -- **Service-Konfiguration** - -## Domain Model Details - -### Veranstaltung-Entität - -```kotlin -data class Veranstaltung( - val veranstaltungId: Uuid, - - // Grundinformationen - var name: String, - var beschreibung: String? = null, - - // Termine - var startDatum: LocalDate, - var endDatum: LocalDate, - - // Ort und Organisation - var ort: String, - var veranstalterVereinId: Uuid, - - // Veranstaltungsdetails - var sparten: List = emptyList(), - var istAktiv: Boolean = true, - var istOeffentlich: Boolean = true, - var maxTeilnehmer: Int? = null, - var anmeldeschluss: LocalDate? = null, - - // Audit-Felder - val createdAt: Instant, - var updatedAt: Instant -) -``` - -### Geschäftslogik-Methoden - -- `isRegistrationOpen()` - Prüfung ob Anmeldung noch möglich ist -- `getDurationInDays()` - Berechnung der Veranstaltungsdauer in Tagen -- `isMultiDay()` - Prüfung ob mehrtägige Veranstaltung -- `validate()` - Datenvalidierung mit Fehlerliste -- `withUpdatedTimestamp()` - Kopie mit aktualisiertem Zeitstempel - -### Enumerationen - -#### SparteE (Sportsparten) - -- `DRESSUR` - Dressurreiten -- `SPRINGEN` - Springreiten -- `VIELSEITIGKEIT` - Vielseitigkeitsreiten -- `FAHREN` - Fahrsport -- `VOLTIGIEREN` - Voltigieren -- `WESTERN` - Westernreiten -- `DISTANZ` - Distanzreiten - -## Repository-Operationen - -### Erweiterte Such-Features - -```kotlin -// Veranstaltungen nach Namen suchen -val events = veranstaltungRepository.findByName("Turnier", limit = 10) - -// Veranstaltungen eines Vereins finden -val clubEvents = veranstaltungRepository.findByVeranstalterVereinId( - vereinId = clubId, - activeOnly = true -) - -// Veranstaltungen in Datumsbereich suchen -val summerEvents = veranstaltungRepository.findByDateRange( - startDate = LocalDate(2024, 6, 1), - endDate = LocalDate(2024, 8, 31), - activeOnly = true -) - -// Öffentliche Veranstaltungen finden -val publicEvents = veranstaltungRepository.findPublicEvents(activeOnly = true) -``` - -### Datumsbasierte Abfragen - -```kotlin -// Veranstaltungen an einem bestimmten Tag -val todayEvents = veranstaltungRepository.findByStartDate( - date = LocalDate.now(), - activeOnly = true -) - -// Alle aktiven Veranstaltungen -val activeEvents = veranstaltungRepository.findAllActive(limit = 100) -``` - -### Statistiken und Zählungen - -```kotlin -// Anzahl aktiver Veranstaltungen -val totalActive = veranstaltungRepository.countActive() - -// Anzahl Veranstaltungen pro Verein -val clubEventCount = veranstaltungRepository.countByVeranstalterVereinId( - vereinId = clubId, - activeOnly = true -) -``` - -## Use Cases - -### CreateVeranstaltungUseCase - -Erstellt eine neue Veranstaltung mit Validierung und Geschäftsregeln. - -```kotlin -class CreateVeranstaltungUseCase( - private val veranstaltungRepository: VeranstaltungRepository -) { - suspend fun execute(veranstaltung: Veranstaltung): Veranstaltung { - // Validierung - val errors = veranstaltung.validate() - if (errors.isNotEmpty()) { - throw ValidationException(errors) - } - - // Geschäftsregeln prüfen - if (veranstaltung.anmeldeschluss != null && - veranstaltung.anmeldeschluss!! > veranstaltung.startDatum) { - throw BusinessRuleException("Anmeldeschluss muss vor Veranstaltungsbeginn liegen") - } - - return veranstaltungRepository.save(veranstaltung) - } -} -``` - -### GetVeranstaltungUseCase - -Ruft Veranstaltungsinformationen ab mit verschiedenen Suchkriterien. - -### UpdateVeranstaltungUseCase - -Aktualisiert Veranstaltungsinformationen mit Validierung. - -### DeleteVeranstaltungUseCase - -Löscht eine Veranstaltung (soft delete durch Deaktivierung). - -## API-Endpunkte - -Das Events-Modul stellt REST-Endpunkte über den VeranstaltungController bereit: - -- `GET /api/events` - Alle aktiven Veranstaltungen abrufen -- `GET /api/events/{id}` - Veranstaltung nach ID abrufen -- `GET /api/events/search?name={name}` - Veranstaltungen nach Namen suchen -- `GET /api/events/club/{clubId}` - Veranstaltungen eines Vereins -- `GET /api/events/public` - Öffentliche Veranstaltungen -- `GET /api/events/date-range?start={start}&end={end}` - Veranstaltungen in Datumsbereich -- `GET /api/events/date/{date}` - Veranstaltungen an einem bestimmten Tag -- `POST /api/events` - Neue Veranstaltung erstellen -- `PUT /api/events/{id}` - Veranstaltung aktualisieren -- `DELETE /api/events/{id}` - Veranstaltung löschen - -## Konfiguration - -### Datenbankschema - -Das Modul verwendet eine `events`-Tabelle mit folgenden Spalten: - -- `veranstaltung_id` (UUID, Primary Key) -- `name` (Required) -- `beschreibung` (Text, Optional) -- `start_datum`, `end_datum` (Date, Required) -- `ort` (Required) -- `veranstalter_verein_id` (UUID, Foreign Key) -- `sparten` (JSON Array) -- `ist_aktiv`, `ist_oeffentlich` (Boolean) -- `max_teilnehmer` (Integer, Optional) -- `anmeldeschluss` (Date, Optional) -- `created_at`, `updated_at` (Timestamps) - -### Service-Konfiguration - -```yaml -# application.yml -events: - service: - name: events-service - port: 8084 - database: - url: jdbc:postgresql://localhost:5432/meldestelle - table: events - business-rules: - max-duration-days: 30 - min-registration-period-days: 7 - allow-past-events: false -``` - -## Tests - -### Integration Tests - -Das Modul enthält umfassende Integrationstests: - -```kotlin -@Test -fun `should create event with valid data`() { - // Test für Veranstaltungserstellung -} - -@Test -fun `should find events by date range`() { - // Test für datumsbasierte Suche -} - -@Test -fun `should validate registration deadline`() { - // Test für Anmeldeschluss-Validierung -} - -@Test -fun `should find public events only`() { - // Test für öffentliche Veranstaltungen -} -``` - -### Test-Datenbank - -Verwendet H2 In-Memory-Datenbank für Tests mit automatischem Schema-Setup. - -## Deployment - -### Docker - -```dockerfile -FROM openjdk:21-jre-slim -COPY events-service.jar app.jar -EXPOSE 8084 -ENTRYPOINT ["java", "-jar", "/app.jar"] -``` - -### Kubernetes - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: events-service -spec: - replicas: 2 - selector: - matchLabels: - app: events-service - template: - spec: - containers: - - name: events-service - image: meldestelle/events-service:latest - ports: - - containerPort: 8084 -``` - -## Monitoring - -### Metriken - -- Anzahl aktiver Veranstaltungen -- Anzahl öffentlicher Veranstaltungen -- Durchschnittliche Veranstaltungsdauer -- API-Response-Zeiten -- Datenbankverbindungs-Pool -- Validierungsfehler-Rate - -### Health Checks - -- Datenbankverbindung -- Service-Verfügbarkeit -- Speicherverbrauch -- Externe System-Verbindungen - -## Entwicklung - -### Lokale Entwicklung - -```bash -# Service starten -./gradlew :events:events-service:bootRun - -# Tests ausführen -./gradlew :events:test - -# Integration Tests -./gradlew :events:events-service:test -``` - -### Code-Qualität - -- **Kotlin Coding Standards** -- **100% Test Coverage** für Domain Layer -- **Integration Tests** für alle Use Cases -- **API-Dokumentation** mit OpenAPI - -## Geschäftsregeln - -### Veranstaltungsplanung - -1. **Datumsvalidierung**: Enddatum muss nach oder gleich Startdatum sein -2. **Anmeldeschluss**: Muss vor Veranstaltungsbeginn liegen -3. **Teilnehmerbegrenzung**: Maximale Teilnehmerzahl muss positiv sein -4. **Öffentlichkeit**: Private Veranstaltungen nur für Vereinsmitglieder - -### Sparten-Management - -- Unterstützung für alle österreichischen Pferdesport-Sparten -- Mehrfachauswahl möglich für kombinierte Veranstaltungen -- Sparten-spezifische Validierungsregeln - -### Vereins-Integration - -- Verknüpfung mit Vereinsverwaltung -- Berechtigung zur Veranstaltungserstellung -- Vereins-spezifische Konfigurationen - -## Integration - -### Externe Systeme - -#### OEPS-Integration - -- Synchronisation mit OEPS-Veranstaltungskalender -- Automatische Meldung bei OEPS-relevanten Veranstaltungen -- Import von OEPS-Veranstaltungsdaten - -#### FEI-Integration - -- Unterstützung für internationale Veranstaltungen -- FEI-Regularien und -Standards -- Automatische Klassifizierung - -### Interne Module - -#### Members-Modul - -- Teilnehmerverwaltung -- Anmeldestatus-Tracking -- Mitgliedschaftsvalidierung - -#### Horses-Modul - -- Pferdeanmeldungen -- Eignung für Sparten -- Registrierungsstatus - -## Zukünftige Erweiterungen - -1. **Anmeldungssystem** - Vollständiges Teilnehmeranmeldungssystem -2. **Zeitplanung** - Detaillierte Zeitpläne und Startlisten -3. **Ergebniserfassung** - Integration mit Bewertungssystemen -4. **Livestreaming** - Integration mit Streaming-Plattformen -5. **Mobile App** - Mobile Anwendung für Teilnehmer -6. **Zahlungsintegration** - Startgebühren und Zahlungsabwicklung -7. **Wetterintegration** - Wettervorhersage und -warnungen -8. **Kapazitätsmanagement** - Stallplätze und Parkplätze -9. **Catering-Management** - Verpflegung und Bewirtung -10. **Sponsoring** - Sponsoren-Management und -präsentation - ---- - -**Letzte Aktualisierung**: 25. Juli 2025 - -Für weitere Informationen zur Gesamtarchitektur siehe [README.md](../../README.md). diff --git a/domains/horses/README-HORSES.md b/domains/horses/README-HORSES.md deleted file mode 100644 index 0f4605ba..00000000 --- a/domains/horses/README-HORSES.md +++ /dev/null @@ -1,561 +0,0 @@ -# Horses Module - -## Überblick - -Das Horses-Modul ist eine umfassende Lösung zur Verwaltung von Pferden für Pferdesportorganisationen. Es implementiert eine saubere Architektur mit Domain-Driven Design und bietet vollständige CRUD-Operationen sowie erweiterte Geschäftslogik für die Pferderegistrierung und -verwaltung. - -## Funktionalität - -### Verwaltete Entität - -#### Pferd (DomPferd) - -- **Grundinformationen**: Name, Geschlecht, Geburtsdatum, Rasse, Farbe -- **Besitz und Verantwortung**: Besitzer-ID, verantwortliche Person -- **Zuchtinformationen**: Züchtername, Zuchtbuchnummer -- **Identifikationsnummern**: Lebensnummer, Chipnummer, Passnummer, OEPS-Nummer, FEI-Nummer -- **Abstammung**: Vater, Mutter, Muttervater -- **Körperliche Merkmale**: Stockmaß (Höhe in cm) -- **Status und Verwaltung**: Aktivitätsstatus, Bemerkungen, Datenquelle -- **Audit-Felder**: Erstellungs- und Aktualisierungszeitstempel - -### Geschäftsoperationen - -Das Modul bietet 25+ spezialisierte Repository-Operationen: - -#### Basis-CRUD-Operationen - -- `findById(id)` - Pferd nach UUID suchen -- `save(horse)` - Pferd speichern (erstellen/aktualisieren) -- `delete(id)` - Pferd löschen - -#### Such-Operationen nach Identifikationsnummern - -- `findByLebensnummer(lebensnummer)` - Nach Lebensnummer suchen -- `findByChipNummer(chipNummer)` - Nach Chipnummer suchen -- `findByPassNummer(passNummer)` - Nach Passnummer suchen -- `findByOepsNummer(oepsNummer)` - Nach OEPS-Nummer suchen -- `findByFeiNummer(feiNummer)` - Nach FEI-Nummer suchen - -#### Such-Operationen nach Eigenschaften - -- `findByName(searchTerm, limit)` - Nach Namen suchen (Teilübereinstimmung) -- `findByOwnerId(ownerId, activeOnly)` - Pferde eines Besitzers -- `findByResponsiblePersonId(personId, activeOnly)` - Pferde einer verantwortlichen Person -- `findByGeschlecht(geschlecht, activeOnly, limit)` - Nach Geschlecht filtern -- `findByRasse(rasse, activeOnly, limit)` - Nach Rasse filtern - -#### Datumsbasierte Abfragen - -- `findByBirthYear(birthYear, activeOnly)` - Pferde nach Geburtsjahr -- `findByBirthYearRange(fromYear, toYear, activeOnly)` - Pferde nach Geburtsjahr-Bereich - -#### Registrierungs-Abfragen - -- `findAllActive(limit)` - Alle aktiven Pferde -- `findOepsRegistered(activeOnly)` - OEPS-registrierte Pferde -- `findFeiRegistered(activeOnly)` - FEI-registrierte Pferde - -#### Validierungs-Operationen - -- `existsByLebensnummer(lebensnummer)` - Prüfung auf doppelte Lebensnummer -- `existsByChipNummer(chipNummer)` - Prüfung auf doppelte Chipnummer -- `existsByPassNummer(passNummer)` - Prüfung auf doppelte Passnummer -- `existsByOepsNummer(oepsNummer)` - Prüfung auf doppelte OEPS-Nummer -- `existsByFeiNummer(feiNummer)` - Prüfung auf doppelte FEI-Nummer - -#### Zähl-Operationen - -- `countActive()` - Anzahl aktiver Pferde -- `countByOwnerId(ownerId, activeOnly)` - Anzahl Pferde pro Besitzer -- `countOepsRegistered(activeOnly)` - Anzahl OEPS-registrierter Pferde ✨ **NEU** -- `countFeiRegistered(activeOnly)` - Anzahl FEI-registrierter Pferde ✨ **NEU** - -## Architektur - -Das Modul folgt der Clean Architecture mit klarer Trennung der Verantwortlichkeiten: - -``` -horses/ -├── horses-domain/ # Domain Layer -│ ├── model/ # Domain Models -│ │ └── DomPferd.kt # Pferd-Entität mit Geschäftslogik -│ └── repository/ # Repository Interfaces -│ └── HorseRepository.kt # 25+ Geschäftsoperationen -├── horses-application/ # Application Layer -│ └── usecase/ # Use Cases -│ ├── CreateHorseUseCase.kt -│ ├── GetHorseUseCase.kt -│ ├── UpdateHorseUseCase.kt -│ └── DeleteHorseUseCase.kt -├── horses-infrastructure/ # Infrastructure Layer -│ └── persistence/ # Database Implementation -│ ├── HorseRepositoryImpl.kt -│ └── HorseTable.kt -├── horses-api/ # API Layer -│ └── rest/ # REST Controllers -│ └── HorseController.kt -└── horses-service/ # Service Layer - ├── HorsesServiceApplication.kt - └── test/ # Integration Tests - └── HorseServiceIntegrationTest.kt -``` - -### Domain Layer - -- **1 Domain Model** mit reichhaltiger Geschäftslogik -- **1 Repository Interface** mit 25+ Geschäftsoperationen -- **Geschäftsregeln** für Pferderegistrierung und -validierung -- **Keine Abhängigkeiten** zu anderen Layern - -### Application Layer - -- **Use Cases** für CRUD-Operationen -- **Orchestrierung** von Domain-Services -- **Anwendungslogik** ohne UI-Abhängigkeiten - -### Infrastructure Layer - -- **Datenbankzugriff** mit Exposed ORM -- **Repository-Implementierung** mit PostgreSQL -- **Datenbankschema** und Migrationen - -### API Layer - -- **REST-Controller** für HTTP-Endpunkte - -## 🚀 Aktuelle Optimierungen (2025-07-25) - -Das Horses-Modul wurde kürzlich analysiert, vervollständigt und optimiert. Folgende Verbesserungen wurden implementiert: - -### ✨ Neue Funktionalitäten - -#### Erweiterte Such-Endpunkte - -Neue REST-Endpunkte für vollständige Identifikationsnummer-Suche: - -- `GET /api/horses/search/passport/{nummer}` - Suche nach Passnummer -- `GET /api/horses/search/oeps/{nummer}` - Suche nach OEPS-Nummer -- `GET /api/horses/search/fei/{nummer}` - Suche nach FEI-Nummer - -#### Optimierte Statistik-Operationen - -- Neue effiziente Zähl-Methoden für OEPS und FEI registrierte Pferde -- Performance-Verbesserung von O(n) auf O(1) Komplexität für Statistiken -- Datenbankoptimierte COUNT-Abfragen statt Laden aller Datensätze - -### ⚡ Performance-Optimierungen - -#### Datenbankeffizienz - -- **Vorher**: Statistik-Endpunkt lud alle Pferde und verwendete `.size` -- **Nachher**: Effiziente COUNT-Abfragen direkt in der Datenbank -- **Auswirkung**: Drastische Reduzierung der Speichernutzung und Antwortzeiten - -#### Architektur-Konsistenz - -- Alle API-Endpunkte verwenden jetzt konsistent die Use-Case-Schicht -- Eliminierung direkter Repository-Aufrufe in der API-Schicht -- Saubere Trennung der Architektur-Schichten - -### 🏗️ Architektur-Verbesserungen - -#### Clean Architecture Compliance - -- **Konsistente Schichtung**: Alle Endpunkte folgen dem Use-Case-Pattern -- **Fehlerbehandlung**: Einheitliche Fehlerantworten über alle Endpunkte -- **Validierung**: Umfassende Eingabevalidierung mit geteilten Utilities -- **HTTP-Standards**: Korrekte Status-Codes und REST-Konventionen - -#### Code-Qualität - -- Verbesserte Lesbarkeit und Wartbarkeit -- Konsistente Namenskonventionen -- Umfassende Dokumentation aller neuen Funktionen - -### 📊 Qualitätsmetriken - -#### Vor der Optimierung - -- ❌ Fehlende Such-Endpunkte für 3 Identifikationstypen -- ❌ Ineffiziente Statistik-Abfragen (O(n) Komplexität) -- ❌ Inkonsistente Architektur (einige Endpunkte umgingen Use Cases) -- ❌ Performance-Probleme bei großen Datensätzen - -#### Nach der Optimierung - -- ✅ Vollständige API-Abdeckung für alle Identifikationstypen -- ✅ Effiziente Statistik-Abfragen (O(1) Komplexität) -- ✅ Konsistente Clean Architecture durchgehend -- ✅ Optimierte Performance für alle Operationen - -### 🔮 Zukünftige Empfehlungen - -#### Caching-Schicht - -- Implementierung einer Caching-Schicht für häufig abgerufene Daten -- Individuelle Pferde-Lookups mit angemessener TTL -- Statistiken und Zählungen mit Cache-Invalidierung - -#### Async-Operationen - -- Asynchrone Verarbeitung für Batch-Operationen -- Komplexe Such-Abfragen mit Async-Pattern -- Statistik-Berechnungen im Hintergrund - -#### Monitoring und Logging - -- Umfassendes Monitoring für API-Antwortzeiten -- Datenbank-Query-Performance-Überwachung -- Fehlerrate-Tracking und -Analyse -- **DTO-Mapping** zwischen Domain und API -- **Validierung** und Fehlerbehandlung - -### Service Layer - -- **Spring Boot Anwendung** -- **Dependency Injection** Konfiguration -- **Integrationstests** - -## Domain Model Details - -### DomPferd-Entität - -```kotlin -data class DomPferd( - val pferdId: Uuid, - - // Grundinformationen - var pferdeName: String, - var geschlecht: PferdeGeschlechtE, - var geburtsdatum: LocalDate? = null, - var rasse: String? = null, - var farbe: String? = null, - - // Besitz und Verantwortung - var besitzerId: Uuid? = null, - var verantwortlichePersonId: Uuid? = null, - - // Zuchtinformationen - var zuechterName: String? = null, - var zuchtbuchNummer: String? = null, - - // Identifikationsnummern - var lebensnummer: String? = null, - var chipNummer: String? = null, - var passNummer: String? = null, - var oepsNummer: String? = null, - var feiNummer: String? = null, - - // Abstammung - var vaterName: String? = null, - var mutterName: String? = null, - var mutterVaterName: String? = null, - - // Körperliche Merkmale - var stockmass: Int? = null, // Höhe in cm - - // Status und Verwaltung - var istAktiv: Boolean = true, - var bemerkungen: String? = null, - var datenQuelle: DatenQuelleE = DatenQuelleE.MANUELL, - - // Audit-Felder - val createdAt: Instant, - var updatedAt: Instant -) -``` - -### Geschäftslogik-Methoden - -- `getDisplayName()` - Anzeigename mit Geburtsjahr -- `hasCompleteIdentification()` - Prüfung auf vollständige Identifikation -- `isOepsRegistered()` - OEPS-Registrierungsstatus -- `isFeiRegistered()` - FEI-Registrierungsstatus -- `getAge()` - Altersberechnung in Jahren -- `validateForRegistration()` - Validierung für Registrierung -- `withUpdatedTimestamp()` - Kopie mit aktualisiertem Zeitstempel - -### Enumerationen - -#### PferdeGeschlechtE - -- `HENGST` - Hengst (männlich, nicht kastriert) -- `STUTE` - Stute (weiblich) -- `WALLACH` - Wallach (männlich, kastriert) - -#### DatenQuelleE - -- `MANUELL` - Manuelle Eingabe -- `IMPORT` - Datenimport -- `SYNCHRONISATION` - Synchronisation mit externen Systemen - -## Repository-Operationen - -### Erweiterte Such-Features - -```kotlin -// Pferde nach Identifikationsnummer suchen -val horse = horseRepository.findByLebensnummer("AT123456789") -val chipHorse = horseRepository.findByChipNummer("982000123456789") - -// Pferde eines Besitzers finden -val ownerHorses = horseRepository.findByOwnerId(ownerId, activeOnly = true) - -// Pferde nach Eigenschaften filtern -val stallions = horseRepository.findByGeschlecht(PferdeGeschlechtE.HENGST) -val warmbloods = horseRepository.findByRasse("Warmblut", activeOnly = true) - -// Pferde nach Geburtsjahr suchen -val youngHorses = horseRepository.findByBirthYearRange(2020, 2024) -``` - -### Registrierungs-Abfragen - -```kotlin -// OEPS-registrierte Pferde finden -val oepsHorses = horseRepository.findOepsRegistered(activeOnly = true) - -// FEI-registrierte Pferde finden -val feiHorses = horseRepository.findFeiRegistered(activeOnly = true) - -// Alle aktiven Pferde -val activeHorses = horseRepository.findAllActive(limit = 1000) -``` - -### Validierung und Duplikatsprüfung - -```kotlin -// Prüfung auf doppelte Identifikationsnummern -val lebensnummerExists = horseRepository.existsByLebensnummer("AT123456789") -val chipExists = horseRepository.existsByChipNummer("982000123456789") -val oepsExists = horseRepository.existsByOepsNummer("AUT12345") -``` - -## Use Cases - -### CreateHorseUseCase - -Erstellt ein neues Pferd mit Validierung und Duplikatsprüfung. - -```kotlin -class CreateHorseUseCase( - private val horseRepository: HorseRepository -) { - suspend fun execute(horse: DomPferd): DomPferd { - // Validierung - val errors = horse.validateForRegistration() - if (errors.isNotEmpty()) { - throw ValidationException(errors) - } - - // Duplikatsprüfung - horse.lebensnummer?.let { nummer -> - if (horseRepository.existsByLebensnummer(nummer)) { - throw DuplicateException("Lebensnummer bereits vorhanden") - } - } - - return horseRepository.save(horse) - } -} -``` - -### GetHorseUseCase - -Ruft Pferdeinformationen ab mit verschiedenen Suchkriterien. - -### UpdateHorseUseCase - -Aktualisiert Pferdeinformationen mit Validierung. - -### DeleteHorseUseCase - -Löscht ein Pferd (soft delete durch Deaktivierung). - -## API-Endpunkte - -Das Horses-Modul stellt REST-Endpunkte über den HorseController bereit: - -- `GET /api/horses` - Alle aktiven Pferde abrufen -- `GET /api/horses/{id}` - Pferd nach ID abrufen -- `GET /api/horses/search?name={name}` - Pferde nach Namen suchen -- `GET /api/horses/owner/{ownerId}` - Pferde eines Besitzers -- `GET /api/horses/identification/{number}` - Pferd nach Identifikationsnummer -- `GET /api/horses/oeps-registered` - OEPS-registrierte Pferde -- `GET /api/horses/fei-registered` - FEI-registrierte Pferde -- `POST /api/horses` - Neues Pferd erstellen -- `PUT /api/horses/{id}` - Pferd aktualisieren -- `DELETE /api/horses/{id}` - Pferd löschen - -## Konfiguration - -### Datenbankschema - -Das Modul verwendet eine `horses`-Tabelle mit folgenden Spalten: - -- `pferd_id` (UUID, Primary Key) -- `pferde_name` (Required) -- `geschlecht` (Enum: HENGST, STUTE, WALLACH) -- `geburtsdatum`, `rasse`, `farbe` (Optional) -- `besitzer_id`, `verantwortliche_person_id` (UUID, Foreign Keys) -- `zuechter_name`, `zuchtbuch_nummer` (Optional) -- `lebensnummer`, `chip_nummer`, `pass_nummer` (Unique, Optional) -- `oeps_nummer`, `fei_nummer` (Unique, Optional) -- `vater_name`, `mutter_name`, `mutter_vater_name` (Optional) -- `stockmass` (Integer, Optional) -- `ist_aktiv` (Boolean) -- `bemerkungen` (Text, Optional) -- `daten_quelle` (Enum) -- `created_at`, `updated_at` (Timestamps) - -### Service-Konfiguration - -```yaml -# application.yml -horses: - service: - name: horses-service - port: 8083 - database: - url: jdbc:postgresql://localhost:5432/meldestelle - table: horses - validation: - require-identification: true - allow-duplicate-names: false -``` - -## Tests - -### Integration Tests - -Das Modul enthält umfassende Integrationstests: - -```kotlin -@Test -fun `should create horse with valid data`() { - // Test für Pferdeerstellung -} - -@Test -fun `should find horses by owner`() { - // Test für Besitzer-basierte Suche -} - -@Test -fun `should validate unique identification numbers`() { - // Test für Eindeutigkeit der Identifikationsnummern -} -``` - -### Test-Datenbank - -Verwendet H2 In-Memory-Datenbank für Tests mit automatischem Schema-Setup. - -## Deployment - -### Docker - -```dockerfile -FROM openjdk:21-jre-slim -COPY horses-service.jar app.jar -EXPOSE 8083 -ENTRYPOINT ["java", "-jar", "/app.jar"] -``` - -### Kubernetes - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: horses-service -spec: - replicas: 2 - selector: - matchLabels: - app: horses-service - template: - spec: - containers: - - name: horses-service - image: meldestelle/horses-service:latest - ports: - - containerPort: 8083 -``` - -## Monitoring - -### Metriken - -- Anzahl aktiver Pferde -- Anzahl registrierter Pferde (OEPS/FEI) -- API-Response-Zeiten -- Datenbankverbindungs-Pool -- Validierungsfehler-Rate - -### Health Checks - -- Datenbankverbindung -- Service-Verfügbarkeit -- Speicherverbrauch -- Externe System-Verbindungen - -## Entwicklung - -### Lokale Entwicklung - -```bash -# Service starten -./gradlew :horses:horses-service:bootRun - -# Tests ausführen -./gradlew :horses:test - -# Integration Tests -./gradlew :horses:horses-service:test -``` - -### Code-Qualität - -- **Kotlin Coding Standards** -- **100% Test Coverage** für Domain Layer -- **Integration Tests** für alle Use Cases -- **API-Dokumentation** mit OpenAPI - -## Compliance und Standards - -### OEPS-Integration - -- Unterstützung für OEPS-Nummern -- Validierung nach OEPS-Standards -- Synchronisation mit OEPS-Datenbank - -### FEI-Integration - -- Unterstützung für FEI-Nummern -- Internationale Registrierungsstandards -- Compliance mit FEI-Regularien - -### Datenschutz - -- DSGVO-konforme Datenhaltung -- Anonymisierung von Testdaten -- Audit-Trail für alle Änderungen - -## Zukünftige Erweiterungen - -1. **Gesundheitsdaten** - Veterinärmedizinische Aufzeichnungen -2. **Leistungsdaten** - Turnierergebnisse und Bewertungen -3. **Versicherungsdaten** - Integration mit Versicherungssystemen -4. **Foto-Management** - Bildverwaltung für Pferde -5. **Stammbaum-Visualisierung** - Grafische Darstellung der Abstammung -6. **Import/Export** - Datenimport aus externen Systemen -7. **Mobile App** - Mobile Anwendung für Pferdebesitzer -8. **QR-Code-Integration** - QR-Codes für schnelle Identifikation - ---- - -**Letzte Aktualisierung**: 25. Juli 2025 - -Für weitere Informationen zur Gesamtarchitektur siehe [README.md](../../README.md). diff --git a/domains/masterdata/README-MASTERDATA.md b/domains/masterdata/README-MASTERDATA.md deleted file mode 100644 index 0d28ba64..00000000 --- a/domains/masterdata/README-MASTERDATA.md +++ /dev/null @@ -1,361 +0,0 @@ -# Masterdata Module - -## Überblick - -Das Masterdata-Modul ist eine umfassende Lösung zur Verwaltung von Stammdaten für Pferdesportveranstaltungen. Es implementiert eine saubere Architektur mit Domain-Driven Design und bietet vollständige CRUD-Operationen für alle Stammdaten-Entitäten. - -## Funktionalität - -### Verwaltete Entitäten - -#### 1. Länder (LandDefinition) - -- **ISO-Codes**: Alpha-2, Alpha-3 und numerische Codes nach ISO 3166-1 -- **EU/EWR-Mitgliedschaft**: Tracking der Mitgliedschaft in EU und Europäischem Wirtschaftsraum -- **Mehrsprachigkeit**: Deutsche und englische Ländernamen -- **Validierung**: Duplikatsprüfung und ISO-Code-Validierung - -#### 2. Bundesländer (BundeslandDefinition) - -- **OEPS-Codes**: Spezielle Codes für österreichische Bundesländer -- **ISO 3166-2 Codes**: Internationale Standardcodes für subnationale Einheiten -- **Länder-Zuordnung**: Verknüpfung mit übergeordneten Ländern -- **Flexible Struktur**: Unterstützt Bundesländer, Kantone, Regionen - -#### 3. Altersklassen (AltersklasseDefinition) - -- **Berechtigung**: Komplexe Regeln für Teilnahmeberechtigung -- **Sparten-Filter**: Disziplinspezifische Altersklassen (Dressur, Springen, etc.) -- **Geschlechts-Filter**: Geschlechtsspezifische Kategorien -- **Altersvalidierung**: Automatische Überprüfung der Teilnahmeberechtigung -- **OETO-Integration**: Verknüpfung mit österreichischen Turnierordnungsregeln - -#### 4. Turnierplätze (Platz) - -- **Platztypen**: Dressurplatz, Springplatz, Geländestrecke, etc. -- **Abmessungen**: Standardisierte Platzgrößen (20x60m, 20x40m, etc.) -- **Bodenarten**: Sand, Gras, Kunststoff, etc. -- **Eignung**: Validierung der Eignung für spezifische Disziplinen -- **Turnier-Zuordnung**: Organisation nach Turnieren - -## Architektur - -Das Modul folgt der Clean Architecture mit klarer Trennung der Verantwortlichkeiten: - -``` -masterdata/ -├── masterdata-domain/ # Domain Layer -│ ├── model/ # Domain Models -│ └── repository/ # Repository Interfaces -├── masterdata-application/ # Application Layer -│ └── usecase/ # Use Cases -├── masterdata-infrastructure/ # Infrastructure Layer -│ └── persistence/ # Database Implementation -├── masterdata-api/ # API Layer -│ └── rest/ # REST Controllers -└── masterdata-service/ # Service Layer - ├── config/ # Configuration - └── resources/db/migration/ # Database Migrations -``` - -### Domain Layer - -- **4 Domain Models** mit reichhaltiger Geschäftslogik -- **4 Repository Interfaces** mit 60+ Geschäftsoperationen -- **Keine Abhängigkeiten** zu anderen Layern - -### Application Layer - -- **8 Use Cases** mit umfassender Funktionalität -- **Validierung**: Eingabevalidierung mit spezifischen Fehlercodes -- **Geschäftslogik**: Duplikatsprüfung, Berechtigungsvalidierung - -### Infrastructure Layer - -- **4 Database Tables** mit Indizes und Constraints -- **Repository Implementierungen** mit vollständigen CRUD-Operationen -- **Migration Scripts** mit Beispieldaten - -### API Layer - -- **4 REST Controllers** mit 37 Endpunkten -- **DTO Pattern** für saubere API-Verträge -- **Fehlerbehandlung** mit strukturierten Antworten - -## API Endpunkte - -### Countries (Länder) - -``` -GET /api/masterdata/countries # Alle aktiven Länder -GET /api/masterdata/countries/{id} # Land nach ID -GET /api/masterdata/countries/iso2/{code} # Land nach ISO Alpha-2 -GET /api/masterdata/countries/iso3/{code} # Land nach ISO Alpha-3 -GET /api/masterdata/countries/search # Länder suchen -GET /api/masterdata/countries/eu # EU-Mitgliedsländer -GET /api/masterdata/countries/ewr # EWR-Mitgliedsländer -POST /api/masterdata/countries # Neues Land erstellen -PUT /api/masterdata/countries/{id} # Land aktualisieren -DELETE /api/masterdata/countries/{id} # Land löschen -``` - -### Federal States (Bundesländer) - -``` -GET /api/masterdata/bundeslaender # Alle aktiven Bundesländer -GET /api/masterdata/bundeslaender/{id} # Bundesland nach ID -GET /api/masterdata/bundeslaender/oeps/{code} # Bundesland nach OEPS-Code -GET /api/masterdata/bundeslaender/iso/{code} # Bundesland nach ISO-Code -GET /api/masterdata/bundeslaender/country/{id} # Bundesländer nach Land -GET /api/masterdata/bundeslaender/search # Bundesländer suchen -GET /api/masterdata/bundeslaender/count/{countryId} # Anzahl nach Land -POST /api/masterdata/bundeslaender # Neues Bundesland erstellen -PUT /api/masterdata/bundeslaender/{id} # Bundesland aktualisieren -DELETE /api/masterdata/bundeslaender/{id} # Bundesland löschen -``` - -### Age Classes (Altersklassen) - -``` -GET /api/masterdata/altersklassen # Alle aktiven Altersklassen -GET /api/masterdata/altersklassen/{id} # Altersklasse nach ID -GET /api/masterdata/altersklassen/code/{code} # Altersklasse nach Code -GET /api/masterdata/altersklassen/search # Altersklassen suchen -GET /api/masterdata/altersklassen/age/{age} # Altersklassen für Alter -GET /api/masterdata/altersklassen/sparte/{sparte} # Altersklassen nach Sparte -GET /api/masterdata/altersklassen/eligible/{id} # Berechtigung prüfen -POST /api/masterdata/altersklassen # Neue Altersklasse erstellen -PUT /api/masterdata/altersklassen/{id} # Altersklasse aktualisieren -DELETE /api/masterdata/altersklassen/{id} # Altersklasse löschen -``` - -### Venues (Turnierplätze) - -``` -GET /api/masterdata/plaetze/{id} # Platz nach ID -GET /api/masterdata/plaetze/tournament/{turnierId} # Plätze nach Turnier -GET /api/masterdata/plaetze/search # Plätze suchen -GET /api/masterdata/plaetze/type/{typ} # Plätze nach Typ -GET /api/masterdata/plaetze/ground/{boden} # Plätze nach Boden -GET /api/masterdata/plaetze/dimension/{dimension} # Plätze nach Abmessung -GET /api/masterdata/plaetze/suitable # Geeignete Plätze -GET /api/masterdata/plaetze/count/tournament/{turnierId} # Anzahl nach Turnier -GET /api/masterdata/plaetze/count/type/{typ}/tournament/{turnierId} # Anzahl nach Typ -GET /api/masterdata/plaetze/grouped/tournament/{turnierId} # Gruppiert nach Typ -GET /api/masterdata/plaetze/validate/{id} # Eignung validieren -POST /api/masterdata/plaetze # Neuen Platz erstellen -PUT /api/masterdata/plaetze/{id} # Platz aktualisieren -DELETE /api/masterdata/plaetze/{id} # Platz löschen -``` - -## Datenbank Schema - -### Land Tabelle - -```sql -CREATE TABLE land ( - id UUID PRIMARY KEY, - iso_alpha2_code VARCHAR(2) NOT NULL UNIQUE, - iso_alpha3_code VARCHAR(3) NOT NULL UNIQUE, - iso_numerischer_code VARCHAR(3), - name_deutsch VARCHAR(100) NOT NULL, - name_englisch VARCHAR(100), - wappen_url VARCHAR(500), - ist_eu_mitglied BOOLEAN, - ist_ewr_mitglied BOOLEAN, - ist_aktiv BOOLEAN DEFAULT true, - sortier_reihenfolge INTEGER, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); -``` - -### Bundesland Tabelle - -```sql -CREATE TABLE bundesland ( - id UUID PRIMARY KEY, - land_id UUID NOT NULL REFERENCES land(id), - oeps_code VARCHAR(10), - iso_3166_2_code VARCHAR(10), - name VARCHAR(100) NOT NULL, - kuerzel VARCHAR(10), - wappen_url VARCHAR(500), - ist_aktiv BOOLEAN DEFAULT true, - sortier_reihenfolge INTEGER, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); -``` - -### Altersklasse Tabelle - -```sql -CREATE TABLE altersklasse ( - id UUID PRIMARY KEY, - altersklasse_code VARCHAR(50) NOT NULL UNIQUE, - bezeichnung VARCHAR(200) NOT NULL, - min_alter INTEGER, - max_alter INTEGER, - stichtag_regel_text VARCHAR(500), - sparte_filter VARCHAR(50), - geschlecht_filter CHAR(1), - oeto_regel_referenz_id UUID, - ist_aktiv BOOLEAN DEFAULT true, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); -``` - -### Platz Tabelle - -```sql -CREATE TABLE platz ( - id UUID PRIMARY KEY, - turnier_id UUID NOT NULL, - name VARCHAR(200) NOT NULL, - dimension VARCHAR(50), - boden VARCHAR(100), - typ VARCHAR(50) NOT NULL, - ist_aktiv BOOLEAN DEFAULT true, - sortier_reihenfolge INTEGER, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); -``` - -## Verwendung - -### Service starten - -```bash -# Masterdata Service starten -./gradlew :masterdata:masterdata-service:bootRun - -# Mit spezifischem Profil -./gradlew :masterdata:masterdata-service:bootRun --args='--spring.profiles.active=dev' -``` - -### API Beispiele - -#### Land erstellen - -```bash -curl -X POST http://localhost:8081/api/masterdata/countries \ - -H "Content-Type: application/json" \ - -d '{ - "isoAlpha2Code": "AT", - "isoAlpha3Code": "AUT", - "isoNumerischerCode": "040", - "nameDeutsch": "Österreich", - "nameEnglisch": "Austria", - "istEuMitglied": true, - "istEwrMitglied": true - }' -``` - -#### Altersklassen für 16-jährigen Dressurreiter abrufen - -```bash -curl "http://localhost:8081/api/masterdata/altersklassen/age/16?sparte=DRESSUR" -``` - -#### Geeignete Dressurplätze finden - -```bash -curl "http://localhost:8081/api/masterdata/plaetze/suitable?typ=DRESSURPLATZ&dimension=20x60m" -``` - -## Konfiguration - -### Umgebungsvariablen - -```bash -# Database -MASTERDATA_DB_URL=jdbc:postgresql://localhost:5432/meldestelle -MASTERDATA_DB_USERNAME=meldestelle -MASTERDATA_DB_PASSWORD=password - -# Cache -MASTERDATA_CACHE_ENABLED=true -MASTERDATA_CACHE_TTL=3600 - -# Validation -MASTERDATA_VALIDATION_STRICT=true -``` - -### Application Properties - -```yaml -masterdata: - validation: - strict: true - duplicate-check: true - cache: - enabled: true - ttl: 3600 - database: - migration: - auto: true -``` - -## Tests - -### Unit Tests ausführen - -```bash -./gradlew :masterdata:test -``` - -### Integration Tests ausführen - -```bash -./gradlew :masterdata:integrationTest -``` - -### Spezifische Tests - -```bash -# Repository Tests -./gradlew :masterdata:masterdata-infrastructure:test - -# Use Case Tests -./gradlew :masterdata:masterdata-application:test - -# API Tests -./gradlew :masterdata:masterdata-api:test -``` - -## Entwicklung - -### Neue Entität hinzufügen - -1. **Domain Model** in `masterdata-domain/model/` erstellen -2. **Repository Interface** in `masterdata-domain/repository/` definieren -3. **Database Table** in `masterdata-infrastructure/persistence/` implementieren -4. **Repository Implementation** erstellen -5. **Use Cases** in `masterdata-application/usecase/` implementieren -6. **REST Controller** in `masterdata-api/rest/` erstellen -7. **Migration Script** in `masterdata-service/resources/db/migration/` hinzufügen -8. **Dependency Injection** in `MasterdataConfiguration` konfigurieren - -### Code-Qualität - -- **Clean Architecture**: Strikte Trennung der Layer -- **Domain-Driven Design**: Reichhaltige Domain Models -- **SOLID Principles**: Befolgt alle SOLID-Prinzipien -- **Comprehensive Testing**: Unit- und Integrationstests -- **Documentation**: Vollständige deutsche Dokumentation - -## Metriken - -- **Zeilen Code**: ~3,500+ produktionsreife Zeilen -- **Domain Models**: 4 umfassende Entitäten -- **Repository Methoden**: 60+ Geschäftsoperationen -- **API Endpunkte**: 37 REST-Endpunkte -- **Datenbank Tabellen**: 4 optimierte Tabellen mit 25+ Indizes -- **Test Coverage**: Umfassende Unit- und Integrationstests - -## Lizenz - -Dieses Modul ist Teil des Meldestelle-Projekts und unterliegt derselben Lizenz. diff --git a/frontend/core/design-system/build.gradle.kts b/frontend/core/design-system/build.gradle.kts index fd136b92..3793a8ec 100644 --- a/frontend/core/design-system/build.gradle.kts +++ b/frontend/core/design-system/build.gradle.kts @@ -14,7 +14,6 @@ kotlin { js(IR) { browser() // nodejs() - binaries.executable() } // WASM, nur wenn explizit aktiviert @@ -22,14 +21,13 @@ kotlin { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) wasmJs { browser() - binaries.executable() } } sourceSets { commonMain.dependencies { // Shared module dependency - implementation(project(":clients:shared")) + implementation(projects.frontend.shared) // Compose dependencies implementation(compose.runtime) diff --git a/frontend/core/local-db/build.gradle.kts b/frontend/core/local-db/build.gradle.kts new file mode 100644 index 00000000..90e8ba4f --- /dev/null +++ b/frontend/core/local-db/build.gradle.kts @@ -0,0 +1,51 @@ +@file:OptIn(ExperimentalKotlinGradlePluginApi::class) + +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.sqldelight) +} + +kotlin { + jvmToolchain(21) + + jvm() + js { + browser { + testTask { enabled = false } + } + } + + sourceSets { + commonMain.dependencies { + implementation(libs.koin.core) + implementation(libs.sqldelight.coroutines) + implementation(libs.kotlinx.coroutines.core) + } + + jvmMain.dependencies { + implementation(libs.sqldelight.driver.sqlite) + } + + jsMain.dependencies { + implementation(libs.sqldelight.driver.webworker) + implementation(npm("@cashapp/sqldelight-sqljs-worker", libs.versions.sqldelight.get())) + implementation(npm("sql.js", "^1.8.0")) + } + + commonTest.dependencies { + implementation(libs.kotlin.test) + } + } +} + +sqldelight { + databases { + register("MeldestelleDb") { + packageName.set("at.mocode.frontend.core.localdb") + // Sources are placed under src/commonMain/sqldelight by convention + } + } +} diff --git a/frontend/core/local-db/src/commonMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.kt b/frontend/core/local-db/src/commonMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.kt new file mode 100644 index 00000000..98458d64 --- /dev/null +++ b/frontend/core/local-db/src/commonMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.kt @@ -0,0 +1,21 @@ +package at.mocode.frontend.core.localdb + +import app.cash.sqldelight.db.SqlDriver +import org.koin.dsl.module + +// Generated database class name from SQLDelight configuration +expect class DatabaseDriverFactory() { + suspend fun createDriver(): SqlDriver +} + +// Convenience to create the typed database from a driver +expect class DatabaseProvider() { + suspend fun createDatabase(): MeldestelleDb +} + +// Koin module that exposes the database as a singleton +val localDbModule = module { + single { DatabaseDriverFactory() } + // Provide only the suspend-capable provider; consumers create the DB in a coroutine + single { DatabaseProvider() } +} diff --git a/frontend/core/local-db/src/commonMain/sqldelight/at/mocode/frontend/core/localdb/MeldestelleDb.sq b/frontend/core/local-db/src/commonMain/sqldelight/at/mocode/frontend/core/localdb/MeldestelleDb.sq new file mode 100644 index 00000000..074d1cd5 --- /dev/null +++ b/frontend/core/local-db/src/commonMain/sqldelight/at/mocode/frontend/core/localdb/MeldestelleDb.sq @@ -0,0 +1,10 @@ +CREATE TABLE LocalSettings ( + key TEXT NOT NULL PRIMARY KEY, + value TEXT NOT NULL +); + +insertOrReplace: +INSERT OR REPLACE INTO LocalSettings(key, value) VALUES (?, ?); + +selectAll: +SELECT * FROM LocalSettings; diff --git a/frontend/core/local-db/src/jsMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.js.kt b/frontend/core/local-db/src/jsMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.js.kt new file mode 100644 index 00000000..14f8822d --- /dev/null +++ b/frontend/core/local-db/src/jsMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.js.kt @@ -0,0 +1,25 @@ +package at.mocode.frontend.core.localdb + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.worker.WebWorkerDriver +import kotlinx.coroutines.await +import org.w3c.dom.Worker + +actual class DatabaseDriverFactory { + actual suspend fun createDriver(): SqlDriver { + val worker = js( + "new Worker(new URL('@cashapp/sqldelight-sqljs-worker/sqljs.worker.js', import.meta.url))" + ) as Worker + val driver = WebWorkerDriver(worker) + // Create schema asynchronously + MeldestelleDb.Schema.create(driver).await() + return driver + } +} + +actual class DatabaseProvider { + actual suspend fun createDatabase(): MeldestelleDb { + val driver = DatabaseDriverFactory().createDriver() + return MeldestelleDb(driver) + } +} diff --git a/frontend/core/local-db/src/jvmMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.jvm.kt b/frontend/core/local-db/src/jvmMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.jvm.kt new file mode 100644 index 00000000..0cf22e05 --- /dev/null +++ b/frontend/core/local-db/src/jvmMain/kotlin/at/mocode/frontend/core/localdb/DatabaseProvider.jvm.kt @@ -0,0 +1,20 @@ +package at.mocode.frontend.core.localdb + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver + +actual class DatabaseDriverFactory { + actual suspend fun createDriver(): SqlDriver { + val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) + // Create schema on first run (in-memory is always new) + MeldestelleDb.Schema.create(driver) + return driver + } +} + +actual class DatabaseProvider { + actual suspend fun createDatabase(): MeldestelleDb { + val driver = DatabaseDriverFactory().createDriver() + return MeldestelleDb(driver) + } +} diff --git a/frontend/core/navigation/build.gradle.kts b/frontend/core/navigation/build.gradle.kts index b54b1df3..24264fdf 100644 --- a/frontend/core/navigation/build.gradle.kts +++ b/frontend/core/navigation/build.gradle.kts @@ -18,14 +18,12 @@ kotlin { js { browser() - binaries.executable() } if (enableWasm) { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) wasmJs { browser() - binaries.executable() } } diff --git a/frontend/core/network/build.gradle.kts b/frontend/core/network/build.gradle.kts new file mode 100644 index 00000000..33025349 --- /dev/null +++ b/frontend/core/network/build.gradle.kts @@ -0,0 +1,70 @@ +@file:OptIn(ExperimentalKotlinGradlePluginApi::class) + +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) +} + +kotlin { + val enableWasm = providers.gradleProperty("enableWasm").orNull == "true" + + jvmToolchain(21) + + jvm() + js { + browser { + testTask { enabled = false } + } + } + + if (enableWasm) { + @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) + wasmJs { browser() } + } + + sourceSets { + commonMain.dependencies { + // Ktor Client core + JSON and Auth + Logging + Timeouts + Retry + api(libs.ktor.client.core) + implementation(libs.ktor.client.contentNegotiation) + implementation(libs.ktor.client.serialization.kotlinx.json) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.logging) + // ktor-client-resources optional; disabled until version is added to catalog + + // Kotlinx core bundles + implementation(libs.bundles.kotlinx.core) + + // DI (Koin) + api(libs.koin.core) + + // Project modules via typesafe accessors + // (none here; kept for consistency) + } + + jvmMain.dependencies { + implementation(libs.ktor.client.cio) + } + + jsMain.dependencies { + implementation(libs.ktor.client.js) + } + + if (enableWasm) { + val wasmJsMain = getByName("wasmJsMain") + wasmJsMain.dependencies { + implementation(libs.ktor.client.js) + } + } + } +} + +tasks.withType { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + freeCompilerArgs.addAll("-opt-in=kotlin.RequiresOptIn") + } +} diff --git a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkConfig.kt b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkConfig.kt new file mode 100644 index 00000000..d2e50126 --- /dev/null +++ b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkConfig.kt @@ -0,0 +1,17 @@ +package at.mocode.frontend.core.network + +import kotlin.native.concurrent.ThreadLocal + +/** + * Network configuration with sensible defaults and environment overrides. + * Defaults to the local API Gateway on port 8081. + */ +@ThreadLocal +object NetworkConfig { + /** + * Base URL for the API Gateway. + * JVM: reads from ENV `API_BASE_URL`, falling back to http://localhost:8081 + * JS/WASM: uses compile-time or runtime override if provided, otherwise http://localhost:8081 + */ + val baseUrl: String = PlatformConfig.resolveApiBaseUrl() +} diff --git a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkModule.kt b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkModule.kt new file mode 100644 index 00000000..5d545c31 --- /dev/null +++ b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/NetworkModule.kt @@ -0,0 +1,75 @@ +package at.mocode.frontend.core.network + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.HttpRequestRetry +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.auth.Auth +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.url +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import org.koin.core.qualifier.named +import org.koin.dsl.module + +/** + * Koin module that provides a preconfigured Ktor HttpClient under the named qualifier "apiClient". + * The client uses the environment-aware base URL from NetworkConfig. + */ +val networkModule = module { + single(named("apiClient")) { + HttpClient { + // JSON (kotlinx) configuration + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + isLenient = true + encodeDefaults = true + } + ) + } + + // Request timeouts + install(HttpTimeout) { + requestTimeoutMillis = 15_000 + connectTimeoutMillis = 10_000 + socketTimeoutMillis = 15_000 + } + + // Automatic simple retry on network exceptions and 5xx + install(HttpRequestRetry) { + maxRetries = 3 + retryIf { _, response -> + val s = response?.status?.value ?: 0 + s == 0 || s >= 500 + } + exponentialDelay() + } + + // Authentication plugin (Bearer refresh can be wired later) + install(Auth) { + // TODO: Wire token provider/refresh when auth is implemented + } + + // Logging for development + install(Logging) { + logger = object : Logger { + override fun log(message: String) { + println("[apiClient] $message") + } + } + level = LogLevel.INFO + } + + // Set base URL + defaultRequest { + // Set only the base URL; endpoints will append paths + url(NetworkConfig.baseUrl) + } + } + } +} diff --git a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.kt b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.kt new file mode 100644 index 00000000..69553360 --- /dev/null +++ b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.kt @@ -0,0 +1,5 @@ +package at.mocode.frontend.core.network + +expect object PlatformConfig { + fun resolveApiBaseUrl(): String +} diff --git a/frontend/core/network/src/jsMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.js.kt b/frontend/core/network/src/jsMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.js.kt new file mode 100644 index 00000000..bf7aa8ed --- /dev/null +++ b/frontend/core/network/src/jsMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.js.kt @@ -0,0 +1,29 @@ +package at.mocode.frontend.core.network + +import kotlinx.browser.window + +@Suppress("UnsafeCastFromDynamic") +actual object PlatformConfig { + actual fun resolveApiBaseUrl(): String { + // 1) Prefer a global JS variable (can be injected by index.html or nginx) + val global = + js("typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {}))") + val fromGlobal = try { + (global.API_BASE_URL as? String)?.trim().orEmpty() + } catch (_: dynamic) { + "" + } + if (fromGlobal.isNotEmpty()) return fromGlobal.removeSuffix("/") + + // 2) Try window location origin (same origin gateway/proxy setup) + val origin = try { + window.location.origin + } catch (_: dynamic) { + null + } + if (!origin.isNullOrBlank()) return origin.removeSuffix("/") + + // 3) Fallback to the local gateway + return "http://localhost:8081" + } +} diff --git a/frontend/core/network/src/jvmMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.jvm.kt b/frontend/core/network/src/jvmMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.jvm.kt new file mode 100644 index 00000000..485f6804 --- /dev/null +++ b/frontend/core/network/src/jvmMain/kotlin/at/mocode/frontend/core/network/PlatformConfig.jvm.kt @@ -0,0 +1,11 @@ +package at.mocode.frontend.core.network + +actual object PlatformConfig { + actual fun resolveApiBaseUrl(): String { + // Prefer environment variable + val env = System.getenv("API_BASE_URL")?.trim().orEmpty() + if (env.isNotEmpty()) return env.removeSuffix("/") + // Fallback default to the local gateway + return "http://localhost:8081" + } +} diff --git a/frontend/features/auth-feature/build.gradle.kts b/frontend/features/auth-feature/build.gradle.kts new file mode 100644 index 00000000..379a108d --- /dev/null +++ b/frontend/features/auth-feature/build.gradle.kts @@ -0,0 +1,126 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +/** + * Dieses Modul kapselt die gesamte UI und Logik für das Authentication-Feature. + * Es kennt seine eigenen technischen Abhängigkeiten (Ktor, Coroutines) + * und den UI-Baukasten (common-ui), aber es kennt keine anderen Features. + */ +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.composeMultiplatform) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.kotlinSerialization) +} + +group = "at.mocode.clients" +version = "1.0.0" + +kotlin { + val enableWasm = providers.gradleProperty("enableWasm").orNull == "true" + + jvmToolchain(21) + + jvm() + + js { + browser { + testTask { + enabled = false + } + } + } + + // WASM, nur wenn explizit aktiviert + if (enableWasm) { + @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) + wasmJs { + browser() + } + } + + sourceSets { + commonMain.dependencies { + // UI Kit (Design System) + implementation(projects.frontend.core.designSystem) + + // Shared Konfig & Utilities (AppConfig + BuildConfig) + implementation(projects.frontend.shared) + + // Compose dependencies + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.materialIconsExtended) + + // Ktor client for HTTP calls + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentNegotiation) + implementation(libs.ktor.client.serialization.kotlinx.json) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.client.auth) + + // Coroutines and serialization + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.serialization.json) + + // DateTime for multiplatform time handling + implementation(libs.kotlinx.datetime) + + // ViewModel lifecycle + implementation(libs.androidx.lifecycle.viewmodelCompose) + implementation(libs.androidx.lifecycle.runtimeCompose) + + } + + commonTest.dependencies { + implementation(libs.kotlin.test) + implementation(libs.kotlinx.coroutines.test) + implementation("io.ktor:ktor-client-mock:${libs.versions.ktor.get()}") + } + + jvmTest.dependencies { + implementation(libs.mockk) + implementation(projects.platform.platformTesting) + implementation(libs.bundles.testing.jvm) + } + + jvmMain.dependencies { + implementation(libs.ktor.client.cio) + } + + jsMain.dependencies { + implementation(libs.ktor.client.js) + implementation(libs.ktor.client.auth) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.datetime) + } + + // WASM SourceSet, nur wenn aktiviert + if (enableWasm) { + val wasmJsMain = getByName("wasmJsMain") + wasmJsMain.dependencies { + implementation(libs.ktor.client.js) // WASM verwendet JS-Client [cite: 7] + + // ✅ HINZUFÜGEN: Compose für shared UI components für WASM + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + } + } + } +} + +// KMP Compile-Optionen +tasks.withType { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + freeCompilerArgs.addAll( + "-opt-in=kotlin.RequiresOptIn", + // Suppress beta warning for expect/actual classes as per project decision + "-Xexpect-actual-classes" + ) + } +} diff --git a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthApiClient.kt b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthApiClient.kt similarity index 99% rename from clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthApiClient.kt rename to frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthApiClient.kt index e0158efa..cceecdca 100644 --- a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthApiClient.kt +++ b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthApiClient.kt @@ -1,6 +1,6 @@ package at.mocode.clients.authfeature -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants import io.ktor.client.call.* import io.ktor.client.request.forms.* import io.ktor.http.* diff --git a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthTokenManager.kt b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthTokenManager.kt similarity index 100% rename from clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthTokenManager.kt rename to frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthTokenManager.kt diff --git a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthenticatedHttpClient.kt b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthenticatedHttpClient.kt similarity index 96% rename from clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthenticatedHttpClient.kt rename to frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthenticatedHttpClient.kt index 75088212..ae03f96f 100644 --- a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthenticatedHttpClient.kt +++ b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/AuthenticatedHttpClient.kt @@ -1,6 +1,6 @@ package at.mocode.clients.authfeature -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants import io.ktor.client.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* diff --git a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginScreen.kt b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginScreen.kt similarity index 100% rename from clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginScreen.kt rename to frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginScreen.kt diff --git a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginViewModel.kt b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginViewModel.kt similarity index 98% rename from clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginViewModel.kt rename to frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginViewModel.kt index 8a603c72..6960b253 100644 --- a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginViewModel.kt +++ b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/LoginViewModel.kt @@ -3,7 +3,7 @@ package at.mocode.clients.authfeature import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import at.mocode.clients.authfeature.AuthenticatedHttpClient.addAuthHeader -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants import io.ktor.client.request.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParams.kt b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParams.kt similarity index 100% rename from clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParams.kt rename to frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParams.kt diff --git a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkce.kt b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkce.kt similarity index 94% rename from clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkce.kt rename to frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkce.kt index b5bbad0b..a26eaa72 100644 --- a/clients/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkce.kt +++ b/frontend/features/auth-feature/src/commonMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkce.kt @@ -1,6 +1,6 @@ package at.mocode.clients.authfeature.oauth -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants data class PkceState( val state: String, diff --git a/clients/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJs.kt b/frontend/features/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJs.kt similarity index 100% rename from clients/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJs.kt rename to frontend/features/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJs.kt diff --git a/clients/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJs.kt b/frontend/features/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJs.kt similarity index 98% rename from clients/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJs.kt rename to frontend/features/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJs.kt index c741a1b1..c7295bc3 100644 --- a/clients/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJs.kt +++ b/frontend/features/auth-feature/src/jsMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJs.kt @@ -1,6 +1,6 @@ package at.mocode.clients.authfeature.oauth -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants import kotlinx.browser.window import kotlinx.coroutines.await import org.khronos.webgl.ArrayBuffer diff --git a/clients/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJvm.kt b/frontend/features/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJvm.kt similarity index 100% rename from clients/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJvm.kt rename to frontend/features/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/AuthCallbackParamsJvm.kt diff --git a/clients/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJvm.kt b/frontend/features/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJvm.kt similarity index 97% rename from clients/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJvm.kt rename to frontend/features/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJvm.kt index d8b7dba2..24985f25 100644 --- a/clients/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJvm.kt +++ b/frontend/features/auth-feature/src/jvmMain/kotlin/at/mocode/clients/authfeature/oauth/OAuthPkceJvm.kt @@ -1,6 +1,6 @@ package at.mocode.clients.authfeature.oauth -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants import java.security.MessageDigest import java.security.SecureRandom import java.util.Base64 diff --git a/clients/auth-feature/build.gradle.kts b/frontend/features/members-feature/build.gradle.kts similarity index 98% rename from clients/auth-feature/build.gradle.kts rename to frontend/features/members-feature/build.gradle.kts index 620d2db3..f62efa82 100644 --- a/clients/auth-feature/build.gradle.kts +++ b/frontend/features/members-feature/build.gradle.kts @@ -46,7 +46,7 @@ kotlin { implementation(project(":frontend:core:design-system")) // Shared Konfig & Utilities (AppConfig + BuildConfig) - implementation(project(":clients:shared")) + implementation(project(":frontend:shared")) // Compose dependencies implementation(compose.runtime) diff --git a/clients/ping-feature/build.gradle.kts b/frontend/features/ping-feature/build.gradle.kts similarity index 92% rename from clients/ping-feature/build.gradle.kts rename to frontend/features/ping-feature/build.gradle.kts index 681cda1b..7495749b 100644 --- a/clients/ping-feature/build.gradle.kts +++ b/frontend/features/ping-feature/build.gradle.kts @@ -28,7 +28,6 @@ kotlin { enabled = false } } - binaries.executable() } // WASM, nur wenn explizit aktiviert @@ -36,20 +35,19 @@ kotlin { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) wasmJs { browser() - binaries.executable() } } sourceSets { commonMain.dependencies { // Contract from backend - implementation(project(":backend:services:ping:ping-api")) + implementation(projects.backend.services.ping.pingApi) // UI Kit (Design System) - implementation(project(":frontend:core:design-system")) + implementation(projects.frontend.core.designSystem) // Shared Konfig & Utilities - implementation(project(":clients:shared")) + implementation(projects.frontend.shared) // Compose dependencies implementation(compose.runtime) diff --git a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiClient.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiClient.kt similarity index 95% rename from clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiClient.kt rename to frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiClient.kt index 142bada1..c75ee8a5 100644 --- a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiClient.kt +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiClient.kt @@ -4,7 +4,7 @@ import at.mocode.ping.api.PingApi import at.mocode.ping.api.PingResponse import at.mocode.ping.api.EnhancedPingResponse import at.mocode.ping.api.HealthResponse -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.plugins.contentnegotiation.* diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiFactory.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiFactory.kt new file mode 100644 index 00000000..215652f4 --- /dev/null +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiFactory.kt @@ -0,0 +1,14 @@ +package at.mocode.clients.pingfeature + +import at.mocode.ping.api.PingApi +import io.ktor.client.HttpClient + +/** + * Factory for providing a PingApi implementation. + * + * If an HttpClient is provided (e.g., DI-provided "apiClient"), a DI-aware + * implementation is returned. Otherwise, a self-contained client is used + * as a fallback to keep the feature working without DI. + */ +fun providePingApi(httpClient: HttpClient? = null): PingApi = + if (httpClient != null) PingApiKoinClient(httpClient) else PingApiClient() diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiKoinClient.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiKoinClient.kt new file mode 100644 index 00000000..b2133d2d --- /dev/null +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingApiKoinClient.kt @@ -0,0 +1,29 @@ +package at.mocode.clients.pingfeature + +import at.mocode.ping.api.EnhancedPingResponse +import at.mocode.ping.api.HealthResponse +import at.mocode.ping.api.PingApi +import at.mocode.ping.api.PingResponse +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get + +/** + * PingApi implementation that uses a provided HttpClient (e.g., DI-provided "apiClient"). + */ +class PingApiKoinClient(private val client: HttpClient) : PingApi { + + override suspend fun simplePing(): PingResponse { + return client.get("/api/ping/simple").body() + } + + override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse { + return client.get("/api/ping/enhanced") { + url.parameters.append("simulate", simulate.toString()) + }.body() + } + + override suspend fun healthCheck(): HealthResponse { + return client.get("/api/ping/health").body() + } +} diff --git a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingScreen.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingScreen.kt similarity index 100% rename from clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingScreen.kt rename to frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingScreen.kt diff --git a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingViewModel.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingViewModel.kt similarity index 100% rename from clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingViewModel.kt rename to frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/PingViewModel.kt diff --git a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/api/ReitsportTestApi.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/api/ReitsportTestApi.kt similarity index 100% rename from clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/api/ReitsportTestApi.kt rename to frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/api/ReitsportTestApi.kt diff --git a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/AuthEnums.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/AuthEnums.kt similarity index 100% rename from clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/AuthEnums.kt rename to frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/AuthEnums.kt diff --git a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportDomainModels.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportDomainModels.kt similarity index 100% rename from clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportDomainModels.kt rename to frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportDomainModels.kt diff --git a/clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportRoles.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportRoles.kt similarity index 100% rename from clients/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportRoles.kt rename to frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/clients/pingfeature/model/ReitsportRoles.kt diff --git a/clients/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingApiClientTest.kt b/frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingApiClientTest.kt similarity index 100% rename from clients/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingApiClientTest.kt rename to frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingApiClientTest.kt diff --git a/clients/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingViewModelTest.kt b/frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingViewModelTest.kt similarity index 100% rename from clients/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingViewModelTest.kt rename to frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/PingViewModelTest.kt diff --git a/clients/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/TestPingApiClient.kt b/frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/TestPingApiClient.kt similarity index 100% rename from clients/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/TestPingApiClient.kt rename to frontend/features/ping-feature/src/commonTest/kotlin/at/mocode/clients/pingfeature/TestPingApiClient.kt diff --git a/clients/shared/build.gradle.kts b/frontend/shared/build.gradle.kts similarity index 97% rename from clients/shared/build.gradle.kts rename to frontend/shared/build.gradle.kts index 48b71da0..790088cb 100644 --- a/clients/shared/build.gradle.kts +++ b/frontend/shared/build.gradle.kts @@ -32,7 +32,6 @@ kotlin { enabled = false } } - binaries.executable() // ... } @@ -64,7 +63,7 @@ kotlin { implementation(libs.koin.compose.viewmodel) // Network module (provides DI `apiClient`) - implementation(project(":frontend:core:network")) + implementation(projects.frontend.core.network) // Compose für shared UI components (common) implementation(compose.runtime) diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/.gitkeep b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/core/AppConfig.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/core/AppConfig.kt similarity index 84% rename from clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/core/AppConfig.kt rename to frontend/shared/src/commonMain/kotlin/at/mocode/shared/core/AppConfig.kt index e8bb115c..79761062 100644 --- a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/core/AppConfig.kt +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/core/AppConfig.kt @@ -1,4 +1,4 @@ -package at.mocode.clients.shared.core +package at.mocode.shared.core data class AppConfig( val gatewayUrl: String, diff --git a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/core/AppConstants.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/core/AppConstants.kt similarity index 97% rename from clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/core/AppConstants.kt rename to frontend/shared/src/commonMain/kotlin/at/mocode/shared/core/AppConstants.kt index 76f31daf..07f555bb 100644 --- a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/core/AppConstants.kt +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/core/AppConstants.kt @@ -1,4 +1,4 @@ -package at.mocode.clients.shared.core +package at.mocode.shared.core /** * Shared application configuration constants for clients. diff --git a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/data/repository/PingRepositoryImpl.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/data/repository/PingRepositoryImpl.kt similarity index 76% rename from clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/data/repository/PingRepositoryImpl.kt rename to frontend/shared/src/commonMain/kotlin/at/mocode/shared/data/repository/PingRepositoryImpl.kt index ddcf01a1..f8ecb2c9 100644 --- a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/data/repository/PingRepositoryImpl.kt +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/data/repository/PingRepositoryImpl.kt @@ -1,8 +1,8 @@ -package at.mocode.clients.shared.data.repository +package at.mocode.shared.data.repository -import at.mocode.clients.shared.domain.model.PingData -import at.mocode.clients.shared.domain.model.Resource -import at.mocode.clients.shared.domain.repository.PingRepository +import at.mocode.shared.domain.model.PingData +import at.mocode.shared.domain.model.Resource +import at.mocode.shared.domain.repository.PingRepository import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* diff --git a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/di/NetworkModule.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/di/NetworkModule.kt similarity index 92% rename from clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/di/NetworkModule.kt rename to frontend/shared/src/commonMain/kotlin/at/mocode/shared/di/NetworkModule.kt index dd1bbfdc..ee7dbeb8 100644 --- a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/di/NetworkModule.kt +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/di/NetworkModule.kt @@ -1,6 +1,6 @@ -package at.mocode.clients.shared.di +package at.mocode.shared.di -import at.mocode.clients.shared.core.AppConfig +import at.mocode.shared.core.AppConfig import io.ktor.client.* import io.ktor.client.plugins.* import io.ktor.client.plugins.contentnegotiation.* diff --git a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/di/SharedModule.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/di/SharedModule.kt similarity index 90% rename from clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/di/SharedModule.kt rename to frontend/shared/src/commonMain/kotlin/at/mocode/shared/di/SharedModule.kt index 10202c64..b9b532e2 100644 --- a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/di/SharedModule.kt +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/di/SharedModule.kt @@ -1,6 +1,6 @@ -package at.mocode.clients.shared.di +package at.mocode.shared.di -import at.mocode.clients.shared.core.devConfig +import at.mocode.shared.core.devConfig import at.mocode.frontend.core.network.networkModule import org.koin.core.context.startKoin import org.koin.dsl.KoinAppDeclaration diff --git a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/domain/model/ApiModels.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/domain/model/ApiModels.kt similarity index 56% rename from clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/domain/model/ApiModels.kt rename to frontend/shared/src/commonMain/kotlin/at/mocode/shared/domain/model/ApiModels.kt index 4eeb71af..f56c6105 100644 --- a/clients/shared/src/commonMain/kotlin/at/mocode/clients/shared/domain/model/ApiModels.kt +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/domain/model/ApiModels.kt @@ -1,4 +1,4 @@ -package at.mocode.clients.shared.domain.model +package at.mocode.shared.domain.model import kotlinx.serialization.Serializable @@ -15,7 +15,8 @@ data class ApiResponse( @Serializable data class ApiError( val code: String, - val message: String + val message: String, + val details: Map = emptyMap() ) /** @@ -37,3 +38,22 @@ data class PingData( val timestamp: String, val service: String ) + +/** + * Minimale User- und Auth-Models für Shared-Kernel (Quick-Fix für Build). + * Hinweis: Für MP-25 können diese in :frontend:core:domain verschoben/ausgebaut werden. + */ +@Serializable +data class AuthToken( + val accessToken: String, + val tokenType: String = "Bearer", + val expiresAtEpochMillis: Long? = null +) + +@Serializable +data class User( + val id: String, + val username: String, + val displayName: String? = null, + val roles: List = emptyList() +) diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/domain/repository/PingRepository.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/domain/repository/PingRepository.kt new file mode 100644 index 00000000..45c36d3b --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/domain/repository/PingRepository.kt @@ -0,0 +1,8 @@ +package at.mocode.shared.domain.repository + +import at.mocode.shared.domain.model.PingData +import at.mocode.shared.domain.model.Resource + +interface PingRepository { + suspend fun checkSystemStatus(): Resource +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/DeepLinkHandler.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/DeepLinkHandler.kt new file mode 100644 index 00000000..aca9ef1e --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/DeepLinkHandler.kt @@ -0,0 +1,194 @@ +package at.mocode.shared.navigation + +import at.mocode.shared.presentation.store.AppStore + +/** + * Deep link handling for the application + */ +class DeepLinkHandler( + private val navigationManager: NavigationManager, + private val store: AppStore +) { + + /** + * Deep link configuration + */ + data class DeepLinkConfig( + val scheme: String = "meldestelle", + val host: String = "app", + val allowedDomains: Set = setOf("meldestelle.com", "localhost") + ) + + private val config = DeepLinkConfig() + + /** + * Handle a deep link URL + */ + fun handleDeepLink(url: String): Boolean { + return try { + val parsedLink = parseDeepLink(url) + if (parsedLink != null) { + processDeepLink(parsedLink) + true + } else { + false + } + } catch (e: Exception) { + // Log error in real implementation + false + } + } + + /** + * Parse deep link URL into components + */ + private fun parseDeepLink(url: String): DeepLink? { + return when { + url.startsWith("${config.scheme}://") -> parseCustomSchemeLink(url) + url.startsWith("https://") || url.startsWith("http://") -> parseWebLink(url) + else -> null + } + } + + /** + * Parse custom scheme deep links (e.g., meldestelle://app/dashboard) + */ + private fun parseCustomSchemeLink(url: String): DeepLink? { + val withoutScheme = url.removePrefix("${config.scheme}://") + val parts = withoutScheme.split("/") + + if (parts.isEmpty() || parts[0] != config.host) { + return null + } + + val path = "/" + parts.drop(1).joinToString("/") + val route = if (path == "/") Routes.HOME else path + + return DeepLink( + type = DeepLinkType.CUSTOM_SCHEME, + route = route, + params = RouteUtils.parseRouteParams(route), + originalUrl = url + ) + } + + /** + * Parse web deep links (e.g., https://meldestelle.com/dashboard) + */ + private fun parseWebLink(url: String): DeepLink? { + // Simple URL parsing - in real implementation use proper URL parser + val urlParts = url.split("/") + if (urlParts.size < 3) return null + + val domain = urlParts[2] + if (!config.allowedDomains.contains(domain)) { + return null + } + + val path = "/" + urlParts.drop(3).joinToString("/") + val route = if (path == "/" || path.isEmpty()) Routes.HOME else path + + return DeepLink( + type = DeepLinkType.WEB_LINK, + route = route, + params = RouteUtils.parseRouteParams(route), + originalUrl = url + ) + } + + /** + * Process a parsed deep link + */ + private fun processDeepLink(deepLink: DeepLink) { + val authState = store.state.value.auth + val cleanRoute = RouteUtils.getCleanRoute(deepLink.route) + + // Check if route requires authentication + if (RouteUtils.requiresAuth(cleanRoute)) { + if (!authState.isAuthenticated) { + // Save the intended route and redirect to log in + saveIntendedRoute(deepLink.route) + navigationManager.navigateTo(Routes.Auth.LOGIN) + return + } + } + + // Check if route requires admin privileges + if (RouteUtils.requiresAdmin(cleanRoute)) { + val hasAdminRole = authState.user?.roles?.contains("admin") ?: false + if (!hasAdminRole) { + // Redirect to unauthorized or home + navigationManager.navigateTo(Routes.HOME) + return + } + } + + // Navigate to the route + navigationManager.navigateTo(deepLink.route) + } + + /** + * Save the intended route for after authentication + */ + private fun saveIntendedRoute(route: String) { + // In real implementation, save to persistent storage + // For now; we'll store it in a simple variable + intendedRoute = route + } + + /** + * Get and clear the intended route + */ + fun getAndClearIntendedRoute(): String? { + val route = intendedRoute + intendedRoute = null + return route + } + + /** + * Check if there's a pending intended route + */ + fun hasIntendedRoute(): Boolean = intendedRoute != null + + /** + * Generate a deep link for a route + */ + fun generateDeepLink(route: String, useCustomScheme: Boolean = true): String { + return if (useCustomScheme) { + "${config.scheme}://${config.host}$route" + } else { + "https://${config.allowedDomains.first()}$route" + } + } + + /** + * Validate if a route is valid for deep linking + */ + fun isValidDeepLinkRoute(route: String): Boolean { + return RouteUtils.isValidRoute(route) && + !route.startsWith("/auth/") && // Auth routes shouldn't be deep linked + route != Routes.Auth.LOGIN + } + + companion object { + private var intendedRoute: String? = null + } +} + +/** + * Deep link data class + */ +data class DeepLink( + val type: DeepLinkType, + val route: String, + val params: Map, + val originalUrl: String +) + +/** + * Types of deep links + */ +enum class DeepLinkType { + CUSTOM_SCHEME, // meldestelle://app/route + WEB_LINK // https://meldestelle.com/route +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/NavigationManager.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/NavigationManager.kt new file mode 100644 index 00000000..2a46434d --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/NavigationManager.kt @@ -0,0 +1,178 @@ +package at.mocode.shared.navigation + +import at.mocode.shared.presentation.actions.AppAction +import at.mocode.shared.presentation.store.AppStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * Navigation manager for handling routing and navigation logic + */ +class NavigationManager( + private val store: AppStore +) { + + /** + * Current route as a flow + */ + val currentRoute: Flow = store.state.map { it.navigation.currentRoute } + + /** + * Navigation history as a flow + */ + val navigationHistory: Flow> = store.state.map { it.navigation.history } + + /** + * Can go back flag as a flow + */ + val canGoBack: Flow = store.state.map { it.navigation.canGoBack } + + /** + * Navigate to a specific route + */ + fun navigateTo(route: String) { + store.dispatch(AppAction.Navigation.NavigateTo(route)) + } + + /** + * Navigate back to the previous route + */ + fun navigateBack() { + store.dispatch(AppAction.Navigation.NavigateBack) + } + + /** + * Replace current route without adding to history + */ + fun replaceRoute(route: String) { + store.dispatch(AppAction.Navigation.UpdateHistory(route)) + } + + /** + * Clear navigation history and navigate to the route + */ + fun navigateAndClearHistory(route: String) { + // First clear by replacing with the new route + store.dispatch(AppAction.Navigation.UpdateHistory(route)) + } + + /** + * Get current route value (non-reactive) + */ + fun getCurrentRoute(): String = store.state.value.navigation.currentRoute + + /** + * Check if we can navigate back + */ + fun canNavigateBack(): Boolean = store.state.value.navigation.canGoBack +} + +/** + * Route definitions for the application + */ +object Routes { + const val HOME = "/" + const val LOGIN = "/login" + const val DASHBOARD = "/dashboard" + const val PROFILE = "/profile" + const val SETTINGS = "/settings" + const val PING = "/ping" + + // Auth-related routes + object Auth { + const val LOGIN = "/auth/login" + const val LOGOUT = "/auth/logout" + const val REGISTER = "/auth/register" + const val FORGOT_PASSWORD = "/auth/forgot-password" + } + + // Admin routes + object Admin { + const val DASHBOARD = "/admin/dashboard" + const val USERS = "/admin/users" + const val SETTINGS = "/admin/settings" + } + + // Feature routes + object Features { + const val PING = "/features/ping" + const val REPORTS = "/features/reports" + const val NOTIFICATIONS = "/features/notifications" + } +} + +/** + * Route validation and utilities + */ +object RouteUtils { + + /** + * Check if a route requires authentication + */ + fun requiresAuth(route: String): Boolean { + return when { + route.startsWith("/auth/") && route != Routes.Auth.LOGIN -> false + route == Routes.HOME -> false + route == Routes.LOGIN -> false + else -> true + } + } + + /** + * Check if a route is for admin only + */ + fun requiresAdmin(route: String): Boolean { + return route.startsWith("/admin/") + } + + /** + * Get the default route for authenticated users + */ + fun getDefaultAuthenticatedRoute(): String = Routes.DASHBOARD + + /** + * Get the default route for unauthenticated users + */ + fun getDefaultUnauthenticatedRoute(): String = Routes.LOGIN + + /** + * Validate route format + */ + fun isValidRoute(route: String): Boolean { + return route.startsWith("/") && route.isNotBlank() + } + + /** + * Parse route parameters (simple implementation) + */ + fun parseRouteParams(route: String): Map { + val params = mutableMapOf() + + // Simple query parameter parsing + if (route.contains("?")) { + val parts = route.split("?") + if (parts.size == 2) { + val queryParams = parts[1].split("&") + queryParams.forEach { param -> + val keyValue = param.split("=") + if (keyValue.size == 2) { + params[keyValue[0]] = keyValue[1] + } + } + } + } + + return params + } + + /** + * Get clean route without parameters + */ + fun getCleanRoute(route: String): String { + return if (route.contains("?")) { + route.split("?")[0] + } else { + route + } + } +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/NavigationPersistence.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/NavigationPersistence.kt new file mode 100644 index 00000000..d31af88f --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/navigation/NavigationPersistence.kt @@ -0,0 +1,75 @@ +package at.mocode.shared.navigation + +import at.mocode.shared.presentation.state.NavigationState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** + * Interface für das Persistieren von Navigation State + */ +interface NavigationPersistence { + suspend fun saveNavigationState(state: NavigationState) + fun getNavigationState(): Flow + suspend fun clearNavigationState() +} + +/** + * Default implementation ohne echte Persistierung (In-Memory) + * Platform-spezifische Implementierungen können echte Persistierung bereitstellen + */ +class DefaultNavigationPersistence : NavigationPersistence { + private var currentState: NavigationState? = null + + override suspend fun saveNavigationState(state: NavigationState) { + currentState = state + } + + override fun getNavigationState(): Flow { + return flowOf(currentState) + } + + override suspend fun clearNavigationState() { + currentState = null + } +} + +/** + * Navigation History Manager mit Persistierung + */ +class NavigationHistoryManager( + private val persistence: NavigationPersistence +) { + companion object { + private const val MAX_HISTORY_SIZE = 50 + } + + suspend fun saveRoute(route: String, history: List) { + val state = NavigationState( + currentRoute = route, + history = history.takeLast(MAX_HISTORY_SIZE), + canGoBack = history.isNotEmpty() + ) + persistence.saveNavigationState(state) + } + + fun getPersistedState() = persistence.getNavigationState() + + suspend fun clear() = persistence.clearNavigationState() + + /** + * Optimiert die History für bessere Performance + */ + private fun optimizeHistory(history: List): List { + // Entfernt Duplikate in Folge und behält nur die letzten N Einträge + return history + .fold(emptyList()) { acc, route -> + if (acc.lastOrNull() != route) acc + route else acc + } + .takeLast(MAX_HISTORY_SIZE) + } + + suspend fun addToHistory(newRoute: String, currentHistory: List) { + val optimizedHistory = optimizeHistory(currentHistory + newRoute) + saveRoute(newRoute, optimizedHistory.dropLast(1)) + } +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/HttpClientConfig.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/HttpClientConfig.kt new file mode 100644 index 00000000..607d3b24 --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/HttpClientConfig.kt @@ -0,0 +1,27 @@ +package at.mocode.shared.network + +import io.ktor.client.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.json.Json + +object HttpClientConfig { + + fun createClient( + baseUrl: String = "http://localhost:8080" + ): HttpClient = HttpClient { + + // Content negotiation with JSON (based on PingApiClient pattern) + install(ContentNegotiation) { + json(Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + }) + } + } + + fun createClientWithBaseUrl(baseUrl: String): HttpClient { + return createClient(baseUrl) + } +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/NetworkException.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/NetworkException.kt new file mode 100644 index 00000000..8b824f61 --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/NetworkException.kt @@ -0,0 +1,172 @@ +package at.mocode.shared.network + +import at.mocode.shared.domain.model.ApiError +import io.ktor.client.network.sockets.* +import io.ktor.client.plugins.* +import kotlinx.io.IOException + +/** + * Custom exceptions for network operations + */ +sealed class NetworkException( + message: String, + cause: Throwable? = null, + val apiError: ApiError +) : Exception(message, cause) { + + class ConnectionException( + message: String = "Connection failed", + cause: Throwable? = null + ) : NetworkException( + message = message, + cause = cause, + apiError = ApiError( + code = "CONNECTION_ERROR", + message = message, + details = mapOf("type" to "network_connectivity") + ) + ) + + class TimeoutException( + message: String = "Request timed out", + cause: Throwable? = null + ) : NetworkException( + message = message, + cause = cause, + apiError = ApiError( + code = "TIMEOUT_ERROR", + message = message, + details = mapOf("type" to "request_timeout") + ) + ) + + class ServerException( + statusCode: Int, + message: String = "Server error", + cause: Throwable? = null + ) : NetworkException( + message = message, + cause = cause, + apiError = ApiError( + code = "SERVER_ERROR", + message = message, + details = mapOf( + "type" to "server_error", + "status_code" to statusCode.toString() + ) + ) + ) + + class ClientException( + statusCode: Int, + message: String = "Client error", + cause: Throwable? = null + ) : NetworkException( + message = message, + cause = cause, + apiError = ApiError( + code = "CLIENT_ERROR", + message = message, + details = mapOf( + "type" to "client_error", + "status_code" to statusCode.toString() + ) + ) + ) + + class AuthenticationException( + message: String = "Authentication failed", + cause: Throwable? = null + ) : NetworkException( + message = message, + cause = cause, + apiError = ApiError( + code = "AUTHENTICATION_ERROR", + message = message, + details = mapOf("type" to "authentication_failure") + ) + ) + + class AuthorizationException( + message: String = "Authorization failed", + cause: Throwable? = null + ) : NetworkException( + message = message, + cause = cause, + apiError = ApiError( + code = "AUTHORIZATION_ERROR", + message = message, + details = mapOf("type" to "authorization_failure") + ) + ) + + class UnknownException( + message: String = "Unknown error occurred", + cause: Throwable? = null + ) : NetworkException( + message = message, + cause = cause, + apiError = ApiError( + code = "UNKNOWN_ERROR", + message = message, + details = mapOf("type" to "unknown_error") + ) + ) +} + +/** + * Extension function to convert various exceptions to NetworkException + */ +fun Throwable.toNetworkException(): NetworkException { + return when (this) { + is ConnectTimeoutException -> NetworkException.TimeoutException( + message = "Connection timeout: ${this.message}", + cause = this + ) + + is SocketTimeoutException -> NetworkException.TimeoutException( + message = "Socket timeout: ${this.message}", + cause = this + ) + + is ResponseException -> when (this.response.status.value) { + 401 -> NetworkException.AuthenticationException( + message = "Authentication required", + cause = this + ) + + 403 -> NetworkException.AuthorizationException( + message = "Access forbidden", + cause = this + ) + + in 400..499 -> NetworkException.ClientException( + statusCode = this.response.status.value, + message = "Client error: ${this.message}", + cause = this + ) + + in 500..599 -> NetworkException.ServerException( + statusCode = this.response.status.value, + message = "Server error: ${this.message}", + cause = this + ) + + else -> NetworkException.UnknownException( + message = "HTTP error: ${this.message}", + cause = this + ) + } + + is IOException -> NetworkException.ConnectionException( + message = "Network connection failed: ${this.message}", + cause = this + ) + + is NetworkException -> this + else -> NetworkException.UnknownException( + message = "Unexpected error: ${this.message}", + cause = this + ) + } +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/NetworkUtils.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/NetworkUtils.kt new file mode 100644 index 00000000..caae7256 --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/NetworkUtils.kt @@ -0,0 +1,221 @@ +package at.mocode.shared.network + + +import at.mocode.shared.domain.model.ApiError +import kotlinx.coroutines.delay + +// Using platform-agnostic timestamp handling + +/** + * Simple timestamp provider for multiplatform compatibility + */ +expect fun currentTimeMillis(): Long + +/** + * Network utilities for handling retry logic and resilience + */ +object NetworkUtils { + + /** + * Retry configuration for network operations + */ + data class RetryConfig( + val maxAttempts: Int = 3, + val initialDelayMs: Long = 1000L, + val maxDelayMs: Long = 10000L, + val backoffMultiplier: Double = 2.0, + val retryableExceptions: Set = setOf( + "CONNECTION_ERROR", + "TIMEOUT_ERROR", + "SERVER_ERROR" + ) + ) + + /** + * Execute operation with retry logic + */ + suspend fun withRetry( + config: RetryConfig = RetryConfig(), + operation: suspend () -> RepositoryResult + ): RepositoryResult { + var lastError: ApiError? = null + var currentDelay = config.initialDelayMs + + repeat(config.maxAttempts) { attempt -> + try { + val result = operation() + + // Return success immediately + if (result.isSuccess()) { + return result + } + + // Check if the error is retryable + val error = result.getErrorOrNull() + if (error != null && shouldRetry(error, config)) { + lastError = error + + // Don't delay on the last attempt + if (attempt < config.maxAttempts - 1) { + delay(currentDelay) + currentDelay = minOf( + (currentDelay * config.backoffMultiplier).toLong(), + config.maxDelayMs + ) + } + } else { + // Non-retryable error, return immediately + return result + } + } catch (e: Exception) { + val networkException = e.toNetworkException() + lastError = networkException.apiError + + if (shouldRetry(networkException.apiError, config)) { + if (attempt < config.maxAttempts - 1) { + delay(currentDelay) + currentDelay = minOf( + (currentDelay * config.backoffMultiplier).toLong(), + config.maxDelayMs + ) + } + } else { + return RepositoryResult.Error(networkException.apiError) + } + } + } + + // All attempts exhausted, return last error + return RepositoryResult.Error( + lastError ?: ApiError( + code = "MAX_RETRIES_EXCEEDED", + message = "Maximum retry attempts exceeded" + ) + ) + } + + /** + * Check if an error should trigger a retry + */ + private fun shouldRetry(error: ApiError, config: RetryConfig): Boolean { + return config.retryableExceptions.contains(error.code) + } + + /** + * Network connectivity checker (simplified for shared module) + */ + object ConnectivityChecker { + private var isOnline: Boolean = true + private var lastCheckMillis: Long = 0L + + fun setOnlineStatus(online: Boolean) { + isOnline = online + lastCheckMillis = currentTimeMillis() + } + + fun isOnline(): Boolean = isOnline + + fun getLastCheckMillis(): Long = lastCheckMillis + + /** + * Simple connectivity test by attempting a lightweight operation + */ + suspend fun checkConnectivity(testOperation: suspend () -> Boolean): Boolean { + return try { + val result = testOperation() + setOnlineStatus(result) + result + } catch (_: Exception) { + setOnlineStatus(false) + false + } + } + } + + /** + * Circuit breaker pattern for network operations + */ + class CircuitBreaker( + private val failureThreshold: Int = 5, + private val recoveryTimeoutMs: Long = 60000L, + private val successThreshold: Int = 3 + ) { + private enum class State { CLOSED, OPEN, HALF_OPEN } + + private var state = State.CLOSED + private var failureCount = 0 + private var successCount = 0 + private var lastFailureTime = 0L + + suspend fun execute(operation: suspend () -> RepositoryResult): RepositoryResult { + when (state) { + State.OPEN -> { + if (currentTimeMillis() - lastFailureTime >= recoveryTimeoutMs) { + state = State.HALF_OPEN + successCount = 0 + } else { + return RepositoryResult.Error( + ApiError( + code = "CIRCUIT_BREAKER_OPEN", + message = "Circuit breaker is open, requests blocked" + ) + ) + } + } + + State.HALF_OPEN -> { + // Allow limited requests to test recovery + } + + State.CLOSED -> { + // Normal operation + } + } + + return try { + val result = operation() + + if (result.isSuccess()) { + onSuccess() + } else { + onFailure() + } + + result + } catch (e: Exception) { + onFailure() + val networkException = e.toNetworkException() + RepositoryResult.Error(networkException.apiError) + } + } + + private fun onSuccess() { + failureCount = 0 + + when (state) { + State.HALF_OPEN -> { + successCount++ + if (successCount >= successThreshold) { + state = State.CLOSED + } + } + + else -> { + state = State.CLOSED + } + } + } + + private fun onFailure() { + failureCount++ + lastFailureTime = currentTimeMillis() + + if (failureCount >= failureThreshold) { + state = State.OPEN + } + } + + fun getState(): String = state.name + fun getFailureCount(): Int = failureCount + } +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/RepositoryResult.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/RepositoryResult.kt new file mode 100644 index 00000000..901a2859 --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/network/RepositoryResult.kt @@ -0,0 +1,18 @@ +package at.mocode.shared.network + +import at.mocode.shared.domain.model.ApiError + +/** + * Einheitlicher Ergebnis-Typ für Repository-/Netzwerkoperationen. + */ +sealed class RepositoryResult { + data class Success(val value: T) : RepositoryResult() + data class Error(val apiError: ApiError) : RepositoryResult() +} + +fun RepositoryResult.isSuccess(): Boolean = this is RepositoryResult.Success + +fun RepositoryResult.getErrorOrNull(): ApiError? = when (this) { + is RepositoryResult.Success -> null + is RepositoryResult.Error -> this.apiError +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/actions/AppAction.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/actions/AppAction.kt new file mode 100644 index 00000000..b28b3b5e --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/actions/AppAction.kt @@ -0,0 +1,37 @@ +package at.mocode.shared.presentation.actions + +import at.mocode.shared.presentation.state.Notification +import at.mocode.shared.domain.model.User +import at.mocode.shared.domain.model.AuthToken + +sealed class AppAction { + // Auth Actions + sealed class Auth : AppAction() { + data class LoginStart(val username: String, val password: String) : Auth() + data class LoginSuccess(val user: User, val token: AuthToken) : Auth() + data class LoginFailure(val error: String) : Auth() + object Logout : Auth() + data class RefreshToken(val newToken: AuthToken) : Auth() + } + + // Navigation Actions + sealed class Navigation : AppAction() { + data class NavigateTo(val route: String) : Navigation() + object NavigateBack : Navigation() + data class UpdateHistory(val route: String) : Navigation() + } + + // UI Actions + sealed class UI : AppAction() { + object ToggleDarkMode : UI() + data class SetLoading(val isLoading: Boolean) : UI() + data class ShowNotification(val notification: Notification) : UI() + data class DismissNotification(val id: String) : UI() + } + + // Network Actions + sealed class Network : AppAction() { + data class SetOnlineStatus(val isOnline: Boolean) : Network() + data class UpdateLastSync(val timestamp: String) : Network() + } +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/state/AppState.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/state/AppState.kt new file mode 100644 index 00000000..f807c44a --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/state/AppState.kt @@ -0,0 +1,55 @@ +package at.mocode.shared.presentation.state + +import kotlinx.serialization.Serializable +import at.mocode.shared.domain.model.User +import at.mocode.shared.domain.model.AuthToken + +@Serializable +data class AppState( + val auth: AuthState = AuthState(), + val navigation: NavigationState = NavigationState(), + val ui: UiState = UiState(), + val network: NetworkState = NetworkState() +) + +@Serializable +data class AuthState( + val isAuthenticated: Boolean = false, + val user: User? = null, + val token: AuthToken? = null, + val isLoading: Boolean = false, + val error: String? = null +) + +@Serializable +data class NavigationState( + val currentRoute: String = "/", + val history: List = emptyList(), + val canGoBack: Boolean = false +) + +@Serializable +data class UiState( + val isDarkMode: Boolean = false, + val isLoading: Boolean = false, + val notifications: List = emptyList() +) + +@Serializable +data class NetworkState( + val isOnline: Boolean = true, + val lastSync: String? = null +) + +@Serializable +data class Notification( + val id: String, + val title: String, + val message: String, + val type: NotificationType = NotificationType.INFO, + val timestamp: String +) + +enum class NotificationType { + INFO, SUCCESS, WARNING, ERROR +} diff --git a/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/store/AppStore.kt b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/store/AppStore.kt new file mode 100644 index 00000000..27a997a7 --- /dev/null +++ b/frontend/shared/src/commonMain/kotlin/at/mocode/shared/presentation/store/AppStore.kt @@ -0,0 +1,156 @@ +package at.mocode.shared.presentation.store + +import at.mocode.shared.presentation.state.AppState +import at.mocode.shared.presentation.actions.AppAction +import at.mocode.shared.presentation.state.AuthState +import at.mocode.shared.presentation.state.NavigationState +import at.mocode.shared.presentation.state.NetworkState +import at.mocode.shared.presentation.state.UiState +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* + +class AppStore( + private val dispatcher: CoroutineDispatcher = Dispatchers.Main +) { + private val scope = CoroutineScope(SupervisorJob() + dispatcher) + private val _state = MutableStateFlow(AppState()) + + val state: StateFlow = _state.asStateFlow() + + fun dispatch(action: AppAction) { + scope.launch { + val currentState = _state.value + val newState = reduce(currentState, action) + _state.value = newState + + // Handle side effects + handleSideEffect(action, newState) + } + } + + private fun reduce(currentState: AppState, action: AppAction): AppState { + return when (action) { + is AppAction.Auth -> currentState.copy( + auth = reduceAuth(currentState.auth, action) + ) + + is AppAction.Navigation -> currentState.copy( + navigation = reduceNavigation(currentState.navigation, action) + ) + + is AppAction.UI -> currentState.copy( + ui = reduceUI(currentState.ui, action) + ) + + is AppAction.Network -> currentState.copy( + network = reduceNetwork(currentState.network, action) + ) + } + } + + private fun reduceAuth(currentAuth: AuthState, action: AppAction.Auth): AuthState { + return when (action) { + is AppAction.Auth.LoginStart -> currentAuth.copy( + isLoading = true, + error = null + ) + + is AppAction.Auth.LoginSuccess -> currentAuth.copy( + isAuthenticated = true, + user = action.user, + token = action.token, + isLoading = false, + error = null + ) + + is AppAction.Auth.LoginFailure -> currentAuth.copy( + isAuthenticated = false, + user = null, + token = null, + isLoading = false, + error = action.error + ) + + is AppAction.Auth.Logout -> AuthState() + is AppAction.Auth.RefreshToken -> currentAuth.copy( + token = action.newToken + ) + } + } + + private fun reduceNavigation(currentNav: NavigationState, action: AppAction.Navigation): NavigationState { + return when (action) { + is AppAction.Navigation.NavigateTo -> currentNav.copy( + currentRoute = action.route, + history = currentNav.history + currentNav.currentRoute, + canGoBack = true + ) + + is AppAction.Navigation.NavigateBack -> { + val newHistory = currentNav.history.dropLast(1) + currentNav.copy( + currentRoute = newHistory.lastOrNull() ?: "/", + history = newHistory, + canGoBack = newHistory.isNotEmpty() + ) + } + + is AppAction.Navigation.UpdateHistory -> currentNav.copy( + currentRoute = action.route + ) + } + } + + private fun reduceUI(currentUI: UiState, action: AppAction.UI): UiState { + return when (action) { + is AppAction.UI.ToggleDarkMode -> currentUI.copy( + isDarkMode = !currentUI.isDarkMode + ) + + is AppAction.UI.SetLoading -> currentUI.copy( + isLoading = action.isLoading + ) + + is AppAction.UI.ShowNotification -> currentUI.copy( + notifications = currentUI.notifications + action.notification + ) + + is AppAction.UI.DismissNotification -> currentUI.copy( + notifications = currentUI.notifications.filter { it.id != action.id } + ) + } + } + + private fun reduceNetwork(currentNetwork: NetworkState, action: AppAction.Network): NetworkState { + return when (action) { + is AppAction.Network.SetOnlineStatus -> currentNetwork.copy( + isOnline = action.isOnline + ) + + is AppAction.Network.UpdateLastSync -> currentNetwork.copy( + lastSync = action.timestamp + ) + } + } + + private suspend fun handleSideEffect(action: AppAction, newState: AppState) { + when (action) { + is AppAction.Auth.LoginSuccess -> { + // Auto-save token to local storage + // TODO: Implement storage + } + + is AppAction.Auth.Logout -> { + // Clear local storage + // TODO: Implement storage cleanup + } + + else -> { /* No side effects */ + } + } + } + + fun cleanup() { + scope.cancel() + } +} diff --git a/frontend/shared/src/jsMain/kotlin/at/mocode/shared/network/TimeJs.kt b/frontend/shared/src/jsMain/kotlin/at/mocode/shared/network/TimeJs.kt new file mode 100644 index 00000000..f5fd8837 --- /dev/null +++ b/frontend/shared/src/jsMain/kotlin/at/mocode/shared/network/TimeJs.kt @@ -0,0 +1,5 @@ +package at.mocode.shared.network + +import kotlin.js.Date + +actual fun currentTimeMillis(): Long = Date().getTime().toLong() diff --git a/frontend/shared/src/jvmMain/kotlin/at/mocode/shared/network/TimeJvm.kt b/frontend/shared/src/jvmMain/kotlin/at/mocode/shared/network/TimeJvm.kt new file mode 100644 index 00000000..4558d9aa --- /dev/null +++ b/frontend/shared/src/jvmMain/kotlin/at/mocode/shared/network/TimeJvm.kt @@ -0,0 +1,3 @@ +package at.mocode.shared.network + +actual fun currentTimeMillis(): Long = System.currentTimeMillis() diff --git a/frontend/shells/meldestelle-portal/build.gradle.kts b/frontend/shells/meldestelle-portal/build.gradle.kts index 92b80868..c8c49a53 100644 --- a/frontend/shells/meldestelle-portal/build.gradle.kts +++ b/frontend/shells/meldestelle-portal/build.gradle.kts @@ -75,12 +75,13 @@ kotlin { sourceSets { commonMain.dependencies { // Shared modules - implementation(project(":clients:shared")) - implementation(project(":frontend:core:design-system")) - implementation(project(":frontend:core:navigation")) - implementation(project(":frontend:core:network")) - implementation(project(":clients:auth-feature")) - implementation(project(":clients:ping-feature")) + implementation(projects.frontend.shared) + implementation(projects.frontend.core.designSystem) + implementation(projects.frontend.core.navigation) + implementation(projects.frontend.core.network) + implementation(project(":frontend:core:local-db")) + implementation(projects.frontend.features.authFeature) + implementation(projects.frontend.features.pingFeature) // DI (Koin) needed to call initKoin { modules(...) } implementation(libs.koin.core) diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt index a3573b2e..b6b5b1c3 100644 --- a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt @@ -10,7 +10,7 @@ import at.mocode.clients.authfeature.AuthenticatedHttpClient import at.mocode.clients.authfeature.AuthTokenManager import at.mocode.clients.pingfeature.PingScreen import at.mocode.clients.pingfeature.PingViewModel -import at.mocode.clients.shared.core.AppConstants +import at.mocode.shared.core.AppConstants import androidx.compose.material3.OutlinedTextField import androidx.compose.ui.text.input.PasswordVisualTransformation import kotlinx.coroutines.launch diff --git a/frontend/shells/meldestelle-portal/src/jsMain/kotlin/main.kt b/frontend/shells/meldestelle-portal/src/jsMain/kotlin/main.kt index 69f1f198..812057ef 100644 --- a/frontend/shells/meldestelle-portal/src/jsMain/kotlin/main.kt +++ b/frontend/shells/meldestelle-portal/src/jsMain/kotlin/main.kt @@ -2,8 +2,10 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.ComposeViewport import kotlinx.browser.document import org.w3c.dom.HTMLElement -import at.mocode.clients.shared.di.initKoin +import at.mocode.shared.di.initKoin import at.mocode.frontend.core.network.networkModule +import at.mocode.frontend.core.localdb.localDbModule +import at.mocode.frontend.core.localdb.DatabaseProvider import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import org.koin.core.context.GlobalContext @@ -15,10 +17,10 @@ import io.ktor.client.request.get @OptIn(ExperimentalComposeUiApi::class) fun main() { console.log("[WebApp] main() entered") - // Initialize DI (Koin) with shared modules + network module + // Initialize DI (Koin) with shared modules + network + local DB modules try { - initKoin { modules(networkModule) } - console.log("[WebApp] Koin initialized with networkModule") + initKoin { modules(networkModule, localDbModule) } + console.log("[WebApp] Koin initialized with networkModule + localDbModule") } catch (e: dynamic) { console.warn("[WebApp] Koin initialization warning:", e) } @@ -36,6 +38,21 @@ fun main() { } catch (e: dynamic) { console.warn("[WebApp] Unable to resolve apiClient from Koin:", e) } + + // Simple local DB smoke: create DB instance (avoid query calls to keep smoke minimal) + try { + val provider = GlobalContext.get().get() + MainScope().launch { + try { + val db = provider.createDatabase() + console.log("[WebApp] Local DB created:", jsTypeOf(db)) + } catch (e: dynamic) { + console.warn("[WebApp] Local DB smoke failed:", e?.message ?: e) + } + } + } catch (e: dynamic) { + console.warn("[WebApp] Unable to resolve DatabaseProvider from Koin:", e) + } fun startApp() { try { console.log("[WebApp] startApp(): readyState=", document.asDynamic().readyState) diff --git a/frontend/shells/meldestelle-portal/src/jvmMain/kotlin/main.kt b/frontend/shells/meldestelle-portal/src/jvmMain/kotlin/main.kt index eb988734..4bf56af4 100644 --- a/frontend/shells/meldestelle-portal/src/jvmMain/kotlin/main.kt +++ b/frontend/shells/meldestelle-portal/src/jvmMain/kotlin/main.kt @@ -2,7 +2,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.WindowState import androidx.compose.ui.unit.dp -import at.mocode.clients.shared.di.initKoin +import at.mocode.shared.di.initKoin import at.mocode.frontend.core.network.networkModule fun main() = application { diff --git a/frontend/shells/meldestelle-portal/webpack.config.d/sqljs-fix.js b/frontend/shells/meldestelle-portal/webpack.config.d/sqljs-fix.js new file mode 100644 index 00000000..e2197307 --- /dev/null +++ b/frontend/shells/meldestelle-portal/webpack.config.d/sqljs-fix.js @@ -0,0 +1,9 @@ +// Fix für sql.js unter Webpack 5 +config.resolve = config.resolve || {}; +config.resolve.fallback = config.resolve.fallback || {}; +config.resolve.fallback.fs = false; +config.resolve.fallback.path = false; +config.resolve.fallback.crypto = false; +config.resolve.fallback.os = false; +config.resolve.fallback.stream = false; +config.resolve.fallback.buffer = false; diff --git a/frontend/shells/meldestelle-portal/webpack.config.d/webpack.config.js b/frontend/shells/meldestelle-portal/webpack.config.d/webpack.config.js index 5e40aee4..6ac55e93 100644 --- a/frontend/shells/meldestelle-portal/webpack.config.d/webpack.config.js +++ b/frontend/shells/meldestelle-portal/webpack.config.d/webpack.config.js @@ -1,7 +1,7 @@ // HTML template will be handled by Kotlin/JS build system // No need for custom HtmlWebpackPlugin configuration -// Bundle-Analyse für Development (optional, only if package is available) +// Bundle-Analyze für Development (optional, only if package is available) if (process.env.ANALYZE_BUNDLE === 'true') { try { const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; @@ -17,13 +17,13 @@ if (process.env.ANALYZE_BUNDLE === 'true') { } // Hinweis: Wir liefern eine statische index.html aus src/jsMain/resources aus. -// Diese Datei enthält nur einen Script-Tag zu "web-app.js" und wird NICHT +// Diese Datei enthält nur ein Script-Tag zu "web-app.js" und wird NICHT // vom HtmlWebpackPlugin generiert. Zusätzliche Chunks (z. B. vendor/runtime) // würden dann nicht automatisch injiziert und führen dazu, dass die App nicht startet -// (Bildschirm bleibt auf "Loading..."). +// (Bildschirm bleibt auf "Loading ..."). // // Daher überschreiben wir config.optimization NICHT mehr mit splitChunks. -// Wenn später Chunking gewünscht ist, muss die index.html durch die generierte +// Wenn später Chunking gewünscht ist, muss die index.html durch das generierte // HTML ersetzt oder die zusätzlichen Chunks manuell eingebunden werden. // // (Frühere splitChunks-Konfiguration wurde bewusst entfernt.) diff --git a/gradle.properties b/gradle.properties index 76093ea4..a3feea9e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -42,6 +42,12 @@ org.jetbrains.kotlin.wasm.check.wasm.binary.format=false kotlin.native.ignoreDisabledTargets=true idea.project.settings.delegate.build.run.actions.to.gradle=true +# Enable NPM/Yarn lifecycle scripts for Kotlin/JS (required for sql.js & worker setup) +kotlin.js.yarn.ignoreScripts=false +org.jetbrains.kotlin.js.yarn.ignoreScripts=false +kotlin.js.npm.ignoreScripts=false +org.jetbrains.kotlin.js.npm.ignoreScripts=false + # Build Reporting org.gradle.logging.level=lifecycle kotlin.build.report.single_file=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f41178e..a0dbf5cc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,10 +23,10 @@ springDependencyManagement = "1.1.7" springdoc = "2.8.13" # --- Ktor (API Layer & Client) --- -ktor = "3.3.1" +ktor = "3.3.3" # --- DI --- -koin = "4.0.0" +koin = "4.1.0" koinCompose = "4.0.0" # --- Compose UI --- @@ -34,6 +34,12 @@ androidx-lifecycle = "2.9.4" composeHotReload = "1.0.0-rc02" composeMultiplatform = "1.9.0" +# coroutinesVersion = "1.10.2" +# dateTimeVersion = "0.7.1" +# koin = "4.1.0" +# ktor = "3.3.3" +# sqlDelight = "2.1.0" +# material3 = "1.3.2" # --- Database & Persistence --- exposed = "0.61.0" @@ -43,6 +49,7 @@ h2 = "2.3.232" flyway = "11.7.2" redisson = "3.52.0" lettuce = "6.6.0.RELEASE" +sqldelight = "2.1.0" # --- Service Discovery & Monitoring --- micrometer = "1.15.4" @@ -168,6 +175,11 @@ flyway-postgresql = { module = "org.flywaydb:flyway-database-postgresql", versio redisson = { module = "org.redisson:redisson", version.ref = "redisson" } lettuce-core = { module = "io.lettuce:lettuce-core", version.ref = "lettuce" } +# --- SQLDelight --- +sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } +sqldelight-driver-sqlite = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } +sqldelight-driver-webworker = { module = "app.cash.sqldelight:web-worker-driver", version.ref = "sqldelight" } + # --- Service Discovery & Monitoring --- micrometer-prometheus = { module = "io.micrometer:micrometer-registry-prometheus", version.ref = "micrometer" } micrometer-tracing-bridge-brave = { module = "io.micrometer:micrometer-tracing-bridge-brave", version.ref = "micrometerTracing" } @@ -496,6 +508,7 @@ kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", versio kotlinJpa = { id = "org.jetbrains.kotlin.plugin.jpa", version.ref = "kotlin" } kotlinSpring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" } +sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } diff --git a/infrastructure/README-INFRASTRUCTURE.md b/infrastructure/README-INFRASTRUCTURE.md deleted file mode 100644 index 0efae6e4..00000000 --- a/infrastructure/README-INFRASTRUCTURE.md +++ /dev/null @@ -1,582 +0,0 @@ -# Infrastructure Module - -## Überblick - -Das Infrastructure-Modul stellt die technische Grundlage für das gesamte Meldestelle-System bereit. Es implementiert alle querschnittlichen Infrastrukturkomponenten, die von den Geschäftsmodulen (members, horses, events, masterdata) benötigt werden. Das Modul folgt dem Prinzip der Separation of Concerns und bietet wiederverwendbare, skalierbare Infrastrukturdienste. - -## Architektur - -Das Infrastructure-Modul ist in 6 Hauptkomponenten unterteilt: - -``` -infrastructure/ -├── auth/ # Authentifizierung und Autorisierung -│ ├── auth-client/ # Client-seitige Auth-Komponenten -│ └── auth-server/ # Server-seitige Auth-Services -├── cache/ # Caching-Infrastruktur -│ ├── cache-api/ # Cache-Abstraktionen -│ └── redis-cache/ # Redis-basierte Cache-Implementierung -├── event-store/ # Event Sourcing -│ ├── event-store-api/ # Event Store Abstraktionen -│ └── redis-event-store/ # Redis-basierte Event Store Implementierung -├── gateway/ # API Gateway -│ ├── src/ # Gateway-Implementierung -│ ├── docs/ # Gateway-Dokumentation -│ └── build/ # Build-Artefakte -├── messaging/ # Messaging-System -│ ├── messaging-client/ # Messaging-Client -│ └── messaging-config/ # Messaging-Konfiguration -└── monitoring/ # Monitoring und Observability - ├── monitoring-client/ # Monitoring-Client - └── monitoring-server/ # Monitoring-Server -``` - -## Komponenten-Übersicht - -### 1. Authentication & Authorization (auth/) - -Zentrale Authentifizierungs- und Autorisierungskomponente basierend auf OAuth 2.0 und JWT. - -#### Features - -- **JWT Token Management** - Erstellung, Validierung und Refresh von JWT-Tokens -- **OAuth 2.0 Integration** - Unterstützung für OAuth 2.0 Flows -- **Role-Based Access Control (RBAC)** - Rollenbasierte Zugriffskontrolle -- **Keycloak Integration** - Integration mit Keycloak Identity Provider -- **Session Management** - Sichere Session-Verwaltung - -#### Komponenten - -- **auth-client**: Client-seitige Authentifizierungslogik -- **auth-server**: Server-seitige Authentifizierungsdienste - -#### Verwendung - -```kotlin -// JWT Token validieren -val tokenValidator = JwtTokenValidator() -val claims = tokenValidator.validate(token) - -// Benutzer authentifizieren -val authService = AuthenticationService() -val user = authService.authenticate(credentials) -``` - -### 2. Caching (cache/) - -Hochperformante Caching-Lösung für verbesserte Anwendungsleistung. - -#### Features - -- **Redis Integration** - Redis als primärer Cache-Store -- **Multi-Level Caching** - L1 (In-Memory) und L2 (Redis) Cache -- **Cache Invalidation** - Intelligente Cache-Invalidierungsstrategien -- **TTL Management** - Flexible Time-To-Live Konfiguration -- **Cache Statistics** - Monitoring und Metriken - -#### Komponenten - -- **cache-api**: Cache-Abstraktionen und Interfaces -- **redis-cache**: Redis-basierte Cache-Implementierung - -#### Verwendung - -```kotlin -// Cache-Service verwenden -val cacheService = RedisCacheService() -cacheService.put("key", value, Duration.ofMinutes(30)) -val cachedValue = cacheService.get("key") - -// Cache invalidieren -cacheService.invalidate("pattern:*") -``` - -### 3. Event Store (event-store/) - -Event Sourcing Infrastruktur für Domain Events und CQRS-Pattern. - -#### Features - -- **Event Sourcing** - Persistierung von Domain Events -- **Event Replay** - Wiederherstellung von Aggregaten aus Events -- **Snapshots** - Performance-Optimierung durch Snapshots -- **Event Versioning** - Versionierung von Event-Schemas -- **Stream Processing** - Event-Stream-Verarbeitung - -#### Komponenten - -- **event-store-api**: Event Store Abstraktionen -- **redis-event-store**: Redis-basierte Event Store Implementierung - -#### Verwendung - -```kotlin -// Events speichern -val eventStore = RedisEventStore() -eventStore.saveEvents(aggregateId, events, expectedVersion) - -// Events laden -val events = eventStore.getEventsForAggregate(aggregateId) - -// Event-Stream abonnieren -eventStore.subscribeToStream("member-events") { event -> - // Event verarbeiten -} -``` - -### 4. API Gateway (gateway/) - -Zentraler Eingangspoint für alle API-Anfragen mit Routing, Load Balancing und Sicherheit. - -#### Features - -- **Request Routing** - Intelligentes Routing zu Microservices -- **Load Balancing** - Lastverteilung zwischen Service-Instanzen -- **Rate Limiting** - Schutz vor Überlastung -- **API Versioning** - Unterstützung für API-Versionierung -- **Request/Response Transformation** - Datenformat-Transformationen -- **Security** - Authentifizierung und Autorisierung -- **Monitoring** - Request-Tracking und Metriken - -#### Konfiguration - -```yaml -# gateway-config.yml -routes: - - id: members-service - uri: http://members-service:8082 - predicates: - - Path=/api/members/** - filters: - - StripPrefix=2 - - RateLimit=100,1m -``` - -### 5. Messaging (messaging/) - -Asynchrone Kommunikation zwischen Services über Message Queues. - -#### Features - -- **Apache Kafka Integration** - Kafka als Message Broker -- **Event-Driven Architecture** - Unterstützung für Event-driven Patterns -- **Message Serialization** - JSON und Avro Serialisierung -- **Dead Letter Queues** - Fehlerbehandlung für nicht verarbeitbare Nachrichten -- **Consumer Groups** - Skalierbare Message-Verarbeitung - -#### Komponenten - -- **messaging-client**: Kafka-Client-Bibliothek -- **messaging-config**: Messaging-Konfiguration - -#### Verwendung - -```kotlin -// Message Producer -val producer = KafkaMessageProducer() -producer.send("member-events", memberCreatedEvent) - -// Message Consumer -val consumer = KafkaMessageConsumer() -consumer.subscribe("member-events") { message -> - // Message verarbeiten -} -``` - -### 6. Monitoring (monitoring/) - -Umfassende Monitoring- und Observability-Lösung. - -#### Features - -- **Metrics Collection** - Sammlung von Anwendungsmetriken -- **Distributed Tracing** - Zipkin-Integration für Request-Tracing -- **Health Checks** - Service-Gesundheitsprüfungen -- **Alerting** - Automatische Benachrichtigungen bei Problemen -- **Dashboards** - Grafana-Integration für Visualisierung - -#### Komponenten - -- **monitoring-client**: Client-seitige Monitoring-Bibliothek -- **monitoring-server**: Monitoring-Server und Aggregation - -#### Metriken - -```kotlin -// Custom Metrics -val meterRegistry = PrometheusMeterRegistry() -val counter = Counter.builder("member.created") - .register(meterRegistry) - -counter.increment() - -// Timing -Timer.Sample.start(meterRegistry) - .stop(Timer.builder("member.creation.time") - .register(meterRegistry)) -``` - -## Technologie-Stack - -### Datenbanken und Speicher - -- **Redis 7.0** - Caching und Event Store -- **PostgreSQL 16** - Relationale Datenbank (über Domain-Module) - -### Message Broker - -- **Apache Kafka 7.5.0** - Event Streaming und Messaging - -### Monitoring und Observability - -- **Prometheus** - Metriken-Sammlung -- **Grafana** - Dashboards und Visualisierung -- **Zipkin** - Distributed Tracing - -### Security - -- **Keycloak 26.4.2** - Identity und Access Management -- **JWT** - Token-basierte Authentifizierung - -### API Gateway - -- **Spring Cloud Gateway** - API Gateway Implementierung -- **Nginx** - Reverse Proxy und Load Balancer - -## Konfiguration - -### Docker Compose - -```yaml -# docker-compose.yml (Auszug) -services: - redis: - image: redis:7-alpine - ports: - - "6379:6379" - command: redis-server --appendonly yes - - kafka: - image: confluentinc/cp-kafka:7.5.0 - environment: - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 - - keycloak: - image: quay.io/keycloak/keycloak:26.4.2 - environment: - KC_BOOTSTRAP_ADMIN_USERNAME: admin - KC_BOOTSTRAP_ADMIN_PASSWORD: admin - ports: - - "8080:8080" -``` - -### Umgebungsvariablen - -```bash -# Redis Configuration -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD= - -# Kafka Configuration -KAFKA_BOOTSTRAP_SERVERS=localhost:9092 -KAFKA_GROUP_ID=meldestelle-group - -# Keycloak Configuration -KEYCLOAK_URL=http://localhost:8080 -KEYCLOAK_REALM=meldestelle -KEYCLOAK_CLIENT_ID=meldestelle-client - -# Monitoring Configuration -PROMETHEUS_URL=http://localhost:9090 -ZIPKIN_URL=http://localhost:9411 -``` - -## Service Discovery - -### Consul Integration - -```kotlin -// Service Registration -val consulClient = ConsulClient() -val service = NewService().apply { - id = "members-service-1" - name = "members-service" - address = "localhost" - port = 8082 - check = NewService.Check().apply { - http = "http://localhost:8082/actuator/health" - interval = "10s" - } -} -consulClient.agentServiceRegister(service) -``` - -## Sicherheit - -### JWT Token Struktur - -```json -{ - "sub": "user123", - "iss": "meldestelle-auth", - "aud": "meldestelle-api", - "exp": 1640995200, - "iat": 1640991600, - "roles": ["MEMBER", "TRAINER"], - "permissions": ["READ_HORSES", "WRITE_EVENTS"] -} -``` - -### RBAC Rollen - -- **ADMIN** - Vollzugriff auf alle Ressourcen -- **TRAINER** - Zugriff auf Pferde und Veranstaltungen -- **MEMBER** - Zugriff auf eigene Daten -- **GUEST** - Nur Lesezugriff auf öffentliche Daten - -## Performance und Skalierung - -### Caching-Strategien - -1. **Application-Level Caching** - In-Memory Cache für häufig verwendete Daten -2. **Database Query Caching** - Redis-Cache für Datenbankabfragen -3. **HTTP Response Caching** - Gateway-Level Caching für API-Responses -4. **CDN Caching** - Content Delivery Network für statische Inhalte - -### Load Balancing - -```nginx -# nginx.conf -upstream members-service { - server members-service-1:8082; - server members-service-2:8082; - server members-service-3:8082; -} - -location /api/members/ { - proxy_pass http://members-service; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; -} -``` - -## Monitoring und Alerting - -### Prometheus Metriken - -```yaml -# prometheus.yml -global: - scrape_interval: 15s - -scrape_configs: - - job_name: 'meldestelle-services' - static_configs: - - targets: - - 'members-service:8082' - - 'horses-service:8083' - - 'events-service:8084' - - 'gateway:8081' -``` - -### Grafana Dashboards - -- **System Overview** - Gesamtsystem-Metriken -- **Service Health** - Service-spezifische Gesundheitsindikatoren -- **API Performance** - Request-Zeiten und Durchsatz -- **Error Rates** - Fehlerquoten und -trends -- **Infrastructure** - Redis, Kafka, Database Metriken - -### Alerting Rules - -```yaml -# alerting-rules.yml -groups: - - name: meldestelle-alerts - rules: - - alert: HighErrorRate - expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1 - for: 5m - annotations: - summary: "High error rate detected" - - - alert: ServiceDown - expr: up == 0 - for: 1m - annotations: - summary: "Service is down" -``` - -## Deployment - -### Kubernetes - -```yaml -# infrastructure-deployment.yml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: api-gateway -spec: - replicas: 3 - selector: - matchLabels: - app: api-gateway - template: - spec: - containers: - - name: gateway - image: meldestelle/api-gateway:latest - ports: - - containerPort: 8081 - env: - - name: REDIS_HOST - value: "redis-service" - - name: KAFKA_BOOTSTRAP_SERVERS - value: "kafka-service:9092" -``` - -### Helm Charts - -```yaml -# values.yml -redis: - enabled: true - auth: - enabled: false - master: - persistence: - enabled: true - size: 8Gi - -kafka: - enabled: true - replicaCount: 3 - persistence: - enabled: true - size: 10Gi - -monitoring: - prometheus: - enabled: true - grafana: - enabled: true - adminPassword: "admin" -``` - -## Entwicklung - -### Lokale Entwicklung - -```bash -# Infrastructure Services starten -docker-compose up -d redis kafka keycloak prometheus grafana zipkin - -# Gateway starten -./gradlew :infrastructure:gateway:bootRun - -# Tests ausführen -./gradlew :infrastructure:test -``` - -### Integration Tests - -```kotlin -@SpringBootTest -@Testcontainers -class InfrastructureIntegrationTest { - - @Container - val redis = GenericContainer("redis:7-alpine") - .withExposedPorts(6379) - - @Container - val kafka = KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0")) - - @Test - fun `should cache data in Redis`() { - // Test Redis Caching - } - - @Test - fun `should send and receive Kafka messages`() { - // Test Kafka Messaging - } -} -``` - -## Troubleshooting - -### Häufige Probleme - -#### Redis Connection Issues - -```bash -# Redis Verbindung testen -redis-cli -h localhost -p 6379 ping - -# Redis Logs prüfen -docker logs redis-container -``` - -#### Kafka Connection Issues - -```bash -# Kafka Topics auflisten -kafka-topics --bootstrap-server localhost:9092 --list - -# Consumer Group Status -kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group meldestelle-group -``` - -#### Gateway Routing Issues - -```bash -# Gateway Health Check -curl http://localhost:8081/actuator/health - -# Route Configuration prüfen -curl http://localhost:8081/actuator/gateway/routes -``` - -## Best Practices - -### Caching - -1. **Cache Warming** - Wichtige Daten beim Start vorwärmen -2. **Cache Invalidation** - Konsistente Invalidierungsstrategien -3. **TTL Configuration** - Angemessene Time-To-Live Werte -4. **Cache Monitoring** - Hit/Miss Ratios überwachen - -### Messaging - -1. **Idempotenz** - Message-Handler idempotent implementieren -2. **Error Handling** - Retry-Mechanismen und Dead Letter Queues -3. **Schema Evolution** - Backward-kompatible Schema-Änderungen -4. **Monitoring** - Message-Durchsatz und Latenz überwachen - -### Security - -1. **Token Rotation** - Regelmäßige JWT-Token-Rotation -2. **HTTPS Only** - Ausschließlich verschlüsselte Verbindungen -3. **Rate Limiting** - Schutz vor Brute-Force-Angriffen -4. **Audit Logging** - Vollständige Audit-Trails - -## Zukünftige Erweiterungen - -1. **Service Mesh** - Istio/Linkerd Integration -2. **Advanced Monitoring** - OpenTelemetry Integration -3. **Multi-Region Deployment** - Geografische Verteilung -4. **Chaos Engineering** - Resilience Testing -5. **GraphQL Gateway** - GraphQL API-Unterstützung -6. **Event Sourcing Enhancements** - Advanced Event Store Features -7. **AI/ML Integration** - Machine Learning Pipeline Integration -8. **Blockchain Integration** - Distributed Ledger für Audit-Trails - ---- - -**Letzte Aktualisierung**: 25. Juli 2025 - -Für weitere Informationen zur Gesamtarchitektur siehe [README.md](../README.md). diff --git a/infrastructure/cache/README-INFRA-CACHE.md b/infrastructure/cache/README-INFRA-CACHE.md deleted file mode 100644 index 5f8fadab..00000000 --- a/infrastructure/cache/README-INFRA-CACHE.md +++ /dev/null @@ -1,135 +0,0 @@ -# Infrastructure/Cache – Modulbeschreibung und Implementierungsleitfaden - -Letzte Aktualisierung: 03. September 2025 - -## Zweck und Aufgaben des Moduls - -Das Infrastructure/Cache-Modul stellt eine einheitliche, technologie‑neutrale Cache‑Schnittstelle für alle Services bereit und liefert mit einer Redis‑basierten Adapter‑Implementierung die produktionsreife Ausführung. Ziele: - -- Antwortzeiten reduzieren und Primärdatenbanken entlasten. -- Einheitliche API für Lesen/Schreiben, Batch‑Operationen und TTLs. -- Resilienz bei Redis‑Ausfällen durch lokalen Fallback. -- Operative Transparenz durch einfache Metriken, Health‑Informationen und periodische Wartungsaufgaben. - -## Architektur (Port‑Adapter) - -- cache‑api: enthält die öffentlichen Verträge und Basistypen - - DistributedCache: zentrale Port‑Schnittstelle für Cache‑Operationen - - CacheEntry, CacheConfiguration, CacheSerializer - - ConnectionStatusTracker/ConnectionStateListener zur Verbindungsüberwachung -- redis‑cache: Adapter, der die Port‑Schnittstelle mit Spring Data Redis umsetzt - - RedisDistributedCache: konkrete Implementierung inkl. Offline‑Fallback, Dirty‑Sync, Batchs, Key‑Prefixing, TTL‑Handling und einfachen Metriken - - JacksonCacheSerializer: serialisiert Werte und CacheEntry per Jackson - -## Öffentliche API (Auszug) - -DistributedCache - -- get(key, clazz)/set(key, value, ttl?) -- delete(key), exists(key) -- multiGet(keys, clazz), multiSet(map, ttl?) -- multiDelete(keys) -- synchronize(keys?), markDirty(key), getDirtyKeys(), clear() - -Idiomatic Kotlin Extensions - -- cache.get(key) -- cache.multiGet(keys) - -CacheConfiguration (DefaultCacheConfiguration vorhanden) - -- defaultTtl?, localCacheMaxSize?, offlineModeEnabled, synchronizationInterval, offlineEntryMaxAge?, keyPrefix, compressionEnabled, compressionThreshold - -Hinweis: Die Kompression wird aktuell durch den Serializer bereitgestellt; Schwellwerte/Flags sind für zukünftiges Tuning vorgesehen. - -## Implementierungsdetails (RedisDistributedCache) - -- Lokaler Fallback: ConcurrentHashMap als lokaler Cache speichert CacheEntry inkl. expiresAt. Bei Redis‑Ausfall werden Schreibvorgänge lokal gehalten und als „dirty“ markiert. -- Dirty‑Synchronisation: Sobald die Verbindung wieder ONLINE ist, werden geänderte Schlüssel zu Redis synchronisiert (synchronize()). -- Key‑Prefixing: Alle externen Keys werden mittels keyPrefix gekapselt, um Mandanten/Services zu isolieren. -- TTL/Expiration: TTL wird einheitlich über kotlin.time.Duration angegeben und für Redis in java.time.Duration konvertiert. Lokale Einträge enthalten expiresAt und werden periodisch bereinigt. -- Batch‑Operationen: multiGet/multiSet/multiDelete nutzen Redis‑Batching/Pipelining, lokal wird konsistent gespiegelt. -- Größenbegrenzung Local Cache (neu): Wenn localCacheMaxSize gesetzt ist, werden bei Überschreitung die am längsten nicht mehr modifizierten Einträge aus dem lokalen Cache entfernt (LRM – least recently modified). Dadurch bleibt der lokale Fallback speichereffizient. -- Periodische Aufgaben (@Scheduled): - - Verbindungsprüfung: fixedDelayString = "${redis.connection-check-interval:10000}" - - Lokale Bereinigung: fixedDelayString = "${redis.local-cache-cleanup-interval:60000}" - - Dirty‑Sync: fixedDelayString = "${redis.sync-interval:300000}" - - Metriken‑Log: fixedDelayString = "${redis.metrics-log-interval:300000}" - -Wichtige Robustheitsdetails - -- Alle Redis‑Operationen fangen RedisConnectionFailureException ab und schalten den ConnectionState auf DISCONNECTED. Beim nächsten erfolgreichen Zugriff wird CONNECTED gesetzt und eine Synchronisation der dirty keys ausgelöst. -- multiSet setzt TTLs bei Bedarf per Pipeline nach (pExpire); einzelne set‑Operationen nutzen expire via Duration. - -## Verwendung (Beispiele) - -Einbinden: Projekte hängen gegen :infrastructure:cache:redis-cache und injizieren DistributedCache. - -Lesen/Schreiben mit TTL - -```kotlin -val user = cache.get("user:42") -if (user == null) { - val loaded = userRepository.findById("42") ?: return null - cache.set("user:42", loaded, ttl = 1.hours) -} -``` - -Batch‑Lesezugriff - -```kotlin -val ids = listOf("user:1", "user:2", "user:3") -val map = cache.multiGet(ids) -``` - -Bulk‑Schreiben - -```kotlin -cache.multiSet(mapOf( - "cfg:app" to appConfig, - "cfg:features" to features -), ttl = 30.minutes) -``` - -Verbindungsstatus überwachen - -```kotlin -cache.registerConnectionListener(object : ConnectionStateListener { - override fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant) { - logger.info("Cache connection state: $newState at $timestamp") - } -}) -``` - -## Konfiguration - -DefaultCacheConfiguration bietet sinnvolle Defaults. Relevante Properties (optional via Spring @Scheduled Platzhalter): - -- redis.connection-check-interval: ms für Verbindungsprüfung (Default 10000) -- redis.local-cache-cleanup-interval: ms für lokale Bereinigung (Default 60000) -- redis.sync-interval: ms für Synchronisationsläufe (Default 300000) -- redis.metrics-log-interval: ms für periodisches Metriken‑Logging (Default 300000) - -Hinweise - -- keyPrefix sollte pro Service gesetzt werden (z. B. "masterdata"), um Kollisionen zu vermeiden. -- localCacheMaxSize begrenzt die Größe des lokalen Fallback‑Caches. Bei null ist die Größe unbegrenzt. - -## Betrieb & Monitoring - -- Health‑Infos: getHealthStatus() liefert eine einfache Einschätzung basierend auf ConnectionState und Erfolgsrate der Operationen. -- Metriken: getPerformanceMetrics() liefert einfache Kennzahlen (Operations, Success‑Rate, Größe lokaler Cache, Anzahl dirty Keys). Periodisches Logging per @Scheduled möglich. -- Cache Warming: warmCache(keys, loader) und warmCacheBulk(map) helfen, Hot‑Keys/gefragte Konfigurationen beim Start vorzuwärmen. - -## Grenzen & bekannte Punkte - -- Kompression ist im Serializer implementiert; die konfigurierbaren Flags/Schwellenwerte sind derzeit nicht dynamisch an/aus‑geschaltet. -- Offline‑Modus: Die Konfiguration offlineModeEnabled ist vorhanden; die Implementierung betreibt den lokalen Fallback standardmäßig bei Verbindungsproblemen. Eine harte Deaktivierung dieses Verhaltens ist aktuell nicht verdrahtet. - -## Changelog (Kurz) - -- 2025‑09‑03: Fehlerbehebungen für @Scheduled‑Platzhalter, korrektes Logging im Cache‑Warming, lokale Cache‑Größenbegrenzung (LRM‑Eviction) hinzugefügt. Dokumentation aktualisiert (diese Datei). - -## Fazit - -Das Cache‑Modul bietet eine klare, wiederverwendbare Cache‑Schnittstelle mit einer robusten Redis‑Implementierung. Es unterstützt TTLs, Batch‑Operationen, lokalen Fallback bei Ausfällen und liefert einfache, praxistaugliche Betriebsinformationen. Mit keyPrefix und lokalen Limits ist der Einsatz in Multi‑Service‑Umgebungen unkompliziert und stabil. diff --git a/infrastructure/cache/redis-cache/README.md b/infrastructure/cache/redis-cache/README.md deleted file mode 100644 index e3aea97b..00000000 --- a/infrastructure/cache/redis-cache/README.md +++ /dev/null @@ -1,173 +0,0 @@ -# Redis Cache Module - -## Überblick - -Dieses Modul stellt eine konkrete Implementierung der `cache-api` unter Verwendung von Redis als Caching-Backend bereit. - -## Architektur - -Das Modul folgt dem Provider-Pattern: - -- **cache-api**: Provider-agnostische Interfaces (`CacheService`, `DistributedCache`) -- **redis-cache**: Redis-spezifische Implementierung - -## Verwendung - -### Dependency Hinzufügen - -```kotlin -dependencies { - implementation(projects.infrastructure.cache.redisCache) -} -``` - -### Konfiguration - -Das Modul verwendet Spring Boot Auto-Configuration. Konfigurieren Sie Redis über `application.yml`: - -```yaml -redis: - host: localhost - port: 6379 - password: null # Optional - database: 0 # Default cache database - connectionTimeout: 2000 - readTimeout: 2000 - usePooling: true - maxPoolSize: 8 - minPoolSize: 2 -``` - -### Code-Beispiel - -```kotlin -@Service -class MyService(private val cache: DistributedCache) { - - suspend fun getData(key: String): MyData? { - return cache.get(key, MyData::class) - } - - suspend fun saveData(key: String, data: MyData) { - cache.put(key, data, ttl = 1.hours) - } -} -``` - -## Features - -- ✅ TTL-Unterstützung für Cache-Einträge -- ✅ Connection State Tracking -- ✅ Health Monitoring -- ✅ Jackson-basierte Serialisierung -- ✅ Connection Pooling mit Lettuce -- ✅ Kotlin Coroutines Support - -## Beans - -Das Modul registriert folgende Spring Beans: - -- `redisConnectionFactory`: Standard Redis ConnectionFactory -- `redisTemplate`: RedisTemplate für Cache-Operationen -- `cacheSerializer`: Jackson-basierter Serializer (kann überschrieben werden) -- `cacheConfiguration`: Standard Cache-Konfiguration (kann überschrieben werden) - -## Gleichzeitige Verwendung mit redis-event-store - -⚠️ **WICHTIG**: Wenn Sie sowohl `redis-cache` als auch `redis-event-store` im selben Service verwenden: - -### Unterschiedliche Databases - -Die Module verwenden **separate Redis Databases**, um Konflikte zu vermeiden: - -- **redis-cache**: Database 0 (Standard) -- **redis-event-store**: Database 1 (konfigurierbar) - -### Konfigurationsbeispiel - -```yaml -# Redis Cache Konfiguration -redis: - host: localhost - port: 6379 - database: 0 # Cache verwendet Database 0 -``` - -```yaml -# Redis Event Store Konfiguration -redis: - event-store: - host: localhost - port: 6379 - database: 1 # Event Store verwendet Database 1 -``` - -### Bean-Namen - -Die Module verwenden unterschiedliche Bean-Namen: - -| Komponente | redis-cache | redis-event-store | -|------------|-------------|-------------------| -| ConnectionFactory | `redisConnectionFactory` | `eventStoreRedisConnectionFactory` | -| Template | `redisTemplate` | `eventStoreRedisTemplate` | -| Serializer | `cacheSerializer` | `eventSerializer` | - -### Keine Konflikte - -✅ Die Module sind so designed, dass sie **ohne Konflikte** gleichzeitig verwendet werden können: - -- Separate ConnectionFactories mit `@Qualifier` -- Separate Property-Prefixes (`redis` vs `redis.event-store`) -- Unterschiedliche Database-Nummern -- Unterschiedliche Bean-Namen - -## Serialisierung - -Das Modul verwendet Jackson für die Serialisierung: - -- Automatische Kotlin-Modul Integration -- Java 8 Date/Time Support -- Custom Serializer können via `@Bean` überschrieben werden - -## Health Checks - -Das Modul tracked automatisch den Redis-Verbindungsstatus: - -- Connection State (CONNECTED, DISCONNECTED, CONNECTING) -- Connection State Listeners für Benachrichtigungen -- Automatische Reconnect-Versuche - -## Performance - -- **Connection Pooling**: Wiederverwendbare Verbindungen via Lettuce -- **Non-blocking I/O**: Reaktive Operations mit Kotlin Coroutines -- **Optimierte Serialisierung**: Jackson-basiert mit Byte-Array-Caching - -## Troubleshooting - -### Redis Verbindungsfehler - -``` -RedisConnectionFailureException: Unable to connect to Redis -``` - -**Lösung**: Überprüfen Sie Redis-Server und Netzwerk-Konfiguration. - -### Serialisierungsfehler - -``` -SerializationException: Could not serialize object -``` - -**Lösung**: Stellen Sie sicher, dass Ihre Datenklassen mit Jackson serialisierbar sind (data classes, keine private Konstruktoren). - -### Bean-Konflikte mit Event Store - -Wenn Sie Fehler wie "Multiple beans of type RedisConnectionFactory" erhalten: - -**Lösung**: Verwenden Sie `@Qualifier` Annotations oder stellen Sie sicher, dass Sie die neueste Version beider Module verwenden (Bean-Namen-Konflikte sind bereits behoben). - -## Weitere Informationen - -- Siehe auch: [event-store-api README](../../event-store/event-store-api/README.md) -- Siehe auch: [redis-event-store README](../../event-store/redis-event-store/README.md) diff --git a/infrastructure/event-store/README-INFRA-EVENT-STORE.md b/infrastructure/event-store/README-INFRA-EVENT-STORE.md deleted file mode 100644 index 3d105fbc..00000000 --- a/infrastructure/event-store/README-INFRA-EVENT-STORE.md +++ /dev/null @@ -1,624 +0,0 @@ -# Infrastructure/Event-Store Module - -*Letzte Aktualisierung: 15. August 2025* - -## Überblick - -Das **Event-Store-Modul** ist eine kritische Komponente der Infrastruktur, die für die Persistenz und Veröffentlichung von Domänen-Events zuständig ist. Es bildet die technische Grundlage für **Event Sourcing** und eine allgemeine **ereignisgesteuerte Architektur**. Anstatt nur den aktuellen Zustand einer Entität zu speichern, speichert der Event Store die gesamte Kette von Ereignissen, die zu diesem Zustand geführt haben. - -Das Modul bietet eine vollständige, produktionsreife Event-Store-Implementierung mit garantierter Konsistenz, ausfallsicherer Event-Verarbeitung und optimaler Performance für moderne Microservice-Architekturen. - -**Status: ✅ PRODUKTIONSBEREIT & OPTIMIERT** - Vollständig getestet mit 12/12 Tests bestanden, erweiterte Performance-Optimierungen implementiert - -## Inhaltsverzeichnis - -1. [Architektur](#architektur) -2. [Schlüsselfunktionen](#schlüsselfunktionen) -3. [Konfiguration](#konfiguration) -4. [API-Dokumentation](#api-dokumentation) -5. [Verwendung](#verwendung) -6. [Event Consumer](#event-consumer) -7. [Testing-Strategie](#testing-strategie) -8. [Performance & Monitoring](#performance--monitoring) -9. [Troubleshooting](#troubleshooting) -10. [Migration & Deployment](#migration--deployment) - -## Architektur - -### Port-Adapter-Muster - -Das Modul folgt streng dem **Port-Adapter-Muster** (Hexagonal Architecture), um eine maximale Entkopplung von der konkreten Speichertechnologie zu erreichen: - -``` -┌─────────────────────────────────────────┐ -│ Application Services │ -│ (members, horses, events, etc.) │ -└─────────────────┬───────────────────────┘ - │ depends on -┌─────────────────▼───────────────────────┐ -│ event-store-api (Port) │ -│ • EventStore interface │ -│ • EventSerializer interface │ -│ • Subscription interface │ -│ • ConcurrencyException │ -└─────────────────┬───────────────────────┘ - │ implemented by -┌─────────────────▼───────────────────────┐ -│ redis-event-store (Adapter) │ -│ • RedisEventStore │ -│ • RedisEventConsumer │ -│ • JacksonEventSerializer │ -│ • RedisEventStoreConfiguration │ -└─────────────────┬───────────────────────┘ - │ uses -┌─────────────────▼───────────────────────┐ -│ Redis Streams │ -│ • Aggregate streams (event-stream:*) │ -│ • Global stream (all-events) │ -│ • Consumer groups │ -└─────────────────────────────────────────┘ -``` - -### Module Structure - -* **`:infrastructure:event-store:event-store-api`**: Definiert die provider-agnostischen Interfaces (`EventStore`, `EventSerializer`, `Subscription`) gegen die Fach-Services programmieren -* **`:infrastructure:event-store:redis-event-store`**: Konkrete Implementierung mit **Redis Streams** als hoch-performantes, persistentes Event-Log - -## Schlüsselfunktionen - -### 🔒 Garantierte Konsistenz -* **Atomare Transaktionen**: Schreibvorgänge in aggregatspezifische Streams und den globalen "all-events"-Stream werden innerhalb einer **Redis-Transaktion (`MULTI`/`EXEC`)** ausgeführt -* **Optimistische Concurrency Control**: Verhindert Race Conditions durch `expectedVersion`-Prüfung mit `ConcurrencyException` bei Konflikten -* **Eventual Consistency**: Garantiert, dass alle Events sowohl in aggregatspezifischen als auch globalen Streams verfügbar sind - -### 🛡️ Resiliente Event-Verarbeitung -* **Redis Consumer Groups**: Skalierbare und ausfallsichere Event-Verarbeitung mit automatischer Last-Verteilung -* **Pending Message Recovery**: Robuste Logik zum "Claimen" von Nachrichten ausgefallener Consumer -* **Retry-Mechanismen**: Automatische Wiederholung bei temporären Fehlern -* **Graceful Degradation**: Kontinuierliche Funktion auch bei partiellen Ausfällen - -### 📊 Intelligente Serialisierung -* **Metadata Separation**: Event-Metadaten und Nutzlast werden getrennt gespeichert für effiziente Stream-Analyse -* **Type Registry**: Dynamische Event-Type-Registrierung für polymorphe Deserialisierung -* **JSON-basiert**: Verwendung von Jackson für robuste, schema-flexible Serialisierung - -### 🚀 Performance-Optimierung -* **Stream-basierte Speicherung**: Optimale Performance durch Redis Streams -* **Optimierte Batch-Operationen**: Alle Events einer Batch werden in einer einzigen Redis-Transaktion verarbeitet (bis zu 90% Performance-Verbesserung) -* **Intelligente Version-Cache**: Thread-sicherer Cache mit Hit/Miss-Tracking für Stream-Versionen -* **Connection Pooling**: Konfigurierbare Verbindungspools für optimale Resource-Nutzung -* **Asynchrone Verarbeitung**: Non-blocking Event-Processing - -### 📊 Enhanced Monitoring & Performance Tracking (NEW) -* **Real-time Metrics Collection**: Automatisches Tracking aller Event-Store-Operationen mit detaillierten Performance-Metriken -* **Comprehensive Operation Tracking**: Einzelne und Batch-Appends, Read-Operationen, Subscriptions mit Erfolgsraten -* **Cache Performance Monitoring**: Detaillierte Hit/Miss-Ratios für optimale Cache-Tuning -* **Concurrency Conflict Detection**: Spezifisches Tracking von Optimistic-Locking-Konflikten -* **Automated Performance Logging**: Periodische Performance-Reports alle 5 Minuten mit strukturierten Metriken -* **Event Throughput Analytics**: Tracking von Events/Sekunde für Capacity Planning -* **Error Rate Monitoring**: Detaillierte Fehlerklassifizierung und -tracking - -## Konfiguration - -### Basis-Konfiguration (application.yml) - -```yaml -redis: - event-store: - # Redis Connection - host: localhost # Redis Server Host - port: 6379 # Redis Server Port - password: null # Redis Password (optional) - database: 0 # Redis Database Number - - # Connection Pool - use-pooling: true # Enable connection pooling - max-pool-size: 8 # Maximum pool connections - min-pool-size: 2 # Minimum pool connections - connection-timeout: 2000 # Connection timeout (ms) - read-timeout: 2000 # Read timeout (ms) - - # Stream Configuration - stream-prefix: "event-stream:" # Prefix for aggregate streams - all-events-stream: "all-events" # Global events stream name - - # Consumer Configuration - consumer-group: "event-processors" # Consumer group name - consumer-name: "event-consumer" # Consumer instance name - create-consumer-group-if-not-exists: true - - # Processing Configuration - claim-idle-timeout: PT1M # Timeout for claiming idle messages - poll-timeout: PT100MS # Polling timeout - max-batch-size: 100 # Maximum events per batch -``` - -### Production-Konfiguration - -```yaml -redis: - event-store: - # Production Redis Setup - host: redis-cluster.production.local - port: 6379 - password: ${REDIS_PASSWORD} - - # Optimized Pool Settings - use-pooling: true - max-pool-size: 20 - min-pool-size: 5 - connection-timeout: 5000 - read-timeout: 5000 - - # Production Consumer Settings - consumer-group: "${app.name}-processors" - consumer-name: "${app.instance-id}" - claim-idle-timeout: PT2M - poll-timeout: PT500MS - max-batch-size: 50 -``` - -### Umgebungsvariablen - -```bash -# Redis Connection -REDIS_EVENT_STORE_HOST=redis.production.local -REDIS_EVENT_STORE_PORT=6379 -REDIS_EVENT_STORE_PASSWORD=secret123 -REDIS_EVENT_STORE_DATABASE=1 - -# Consumer Configuration -REDIS_EVENT_STORE_CONSUMER_GROUP=prod-processors -REDIS_EVENT_STORE_CONSUMER_NAME=instance-01 -REDIS_EVENT_STORE_MAX_BATCH_SIZE=100 -``` - -## API-Dokumentation - -### EventStore Interface - -```kotlin -interface EventStore { - // Single Event Operations - fun appendToStream(event: DomainEvent, streamId: UUID, expectedVersion: Long): Long - fun readFromStream(streamId: UUID, fromVersion: Long = 0, toVersion: Long? = null): List - fun getStreamVersion(streamId: UUID): Long - - // Batch Operations - fun appendToStream(events: List, streamId: UUID, expectedVersion: Long): Long - - // Global Stream Operations - fun readAllEvents(fromPosition: Long = 0, maxCount: Int? = null): List - - // Subscription Operations - fun subscribeToStream(streamId: UUID, fromVersion: Long = 0, handler: (DomainEvent) -> Unit): Subscription - fun subscribeToAll(fromPosition: Long = 0, handler: (DomainEvent) -> Unit): Subscription -} -``` - -### EventSerializer Interface - -```kotlin -interface EventSerializer { - // Serialization - fun serialize(event: DomainEvent): Map - fun deserialize(data: Map): DomainEvent - - // Type Management - fun getEventType(event: DomainEvent): String - fun getEventType(data: Map): String - fun registerEventType(eventClass: Class, eventType: String) - - // Metadata Extraction - fun getAggregateId(data: Map): UUID - fun getEventId(data: Map): UUID - fun getVersion(data: Map): Long -} -``` - -## Verwendung - -### 1. Dependency Setup - -```kotlin -dependencies { - implementation(projects.infrastructure.eventStore.redisEventStore) -} -``` - -### 2. Event Definition - -```kotlin -@Serializable -data class MemberRegisteredEvent( - @Transient override val aggregateId: AggregateId = AggregateId(UUID.randomUUID()), - @Transient override val version: EventVersion = EventVersion(0), - val memberId: UUID, - val name: String, - val email: String, - val registeredAt: Instant -) : BaseDomainEvent(aggregateId, EventType("MemberRegistered"), version) -``` - -### 3. Service Implementation - -```kotlin -@Service -class MemberApplicationService( - private val eventStore: EventStore, - private val eventSerializer: EventSerializer -) { - @PostConstruct - fun init() { - // Register event types for serialization - eventSerializer.registerEventType(MemberRegisteredEvent::class.java, "MemberRegistered") - eventSerializer.registerEventType(MemberUpdatedEvent::class.java, "MemberUpdated") - } - - fun registerNewMember(command: RegisterMemberCommand): UUID { - val memberId = UUID.randomUUID() - val event = MemberRegisteredEvent( - aggregateId = AggregateId(memberId), - version = EventVersion(1L), - memberId = memberId, - name = command.name, - email = command.email, - registeredAt = Instant.now() - ) - - try { - // Append to stream with expected version 0 (new stream) - val newVersion = eventStore.appendToStream(event, memberId, 0) - logger.info("Member registered: {} at version {}", memberId, newVersion) - return memberId - } catch (ex: ConcurrencyException) { - logger.warn("Concurrency conflict for member: {}", memberId) - throw MemberAlreadyExistsException(memberId) - } - } - - fun updateMember(command: UpdateMemberCommand) { - // 1. Load the current state from the event stream - val events = eventStore.readFromStream(command.memberId) - val currentVersion = eventStore.getStreamVersion(command.memberId) - - // 2. Validate business rules - validateUpdateCommand(command, events) - - // 3. Create and append new event - val event = MemberUpdatedEvent( - aggregateId = AggregateId(command.memberId), - version = EventVersion(currentVersion + 1), - memberId = command.memberId, - updatedFields = command.changes, - updatedAt = Instant.now() - ) - - eventStore.appendToStream(event, command.memberId, currentVersion) - } - - fun getMemberHistory(memberId: UUID): List { - return eventStore.readFromStream(memberId) - } - - fun getMemberHistoryRange(memberId: UUID, fromVersion: Long, toVersion: Long): List { - return eventStore.readFromStream(memberId, fromVersion, toVersion) - } -} -``` - -### 4. Batch Operations - -```kotlin -@Service -class BulkMemberService( - private val eventStore: EventStore -) { - fun registerMultipleMembers(commands: List) { - commands.forEach { command -> - val events = listOf( - MemberRegisteredEvent(/* ... */), - MemberProfileCreatedEvent(/* ... */) - ) - - // Append multiple events atomically - eventStore.appendToStream(events, command.memberId, 0) - } - } -} -``` - -## Event Consumer - -### Consumer Setup - -```kotlin -@Component -class MemberEventHandler( - private val redisEventConsumer: RedisEventConsumer, - private val memberProjectionService: MemberProjectionService -) { - @PostConstruct - fun init() { - // Register handlers for specific event types - redisEventConsumer.registerEventHandler("MemberRegistered") { event -> - val memberEvent = event as MemberRegisteredEvent - memberProjectionService.handleMemberRegistered(memberEvent) - } - - redisEventConsumer.registerEventHandler("MemberUpdated") { event -> - val memberEvent = event as MemberUpdatedEvent - memberProjectionService.handleMemberUpdated(memberEvent) - } - - // Register handler for all events (useful for auditing) - redisEventConsumer.registerAllEventsHandler { event -> - auditService.recordEvent(event) - } - } - - @PreDestroy - fun cleanup() { - // Consumers are automatically cleaned up, but manual cleanup is possible - redisEventConsumer.unregisterEventHandler("MemberRegistered", memberHandler) - } -} -``` - -### Consumer Configuration - -```yaml -redis: - event-store: - # Consumer-specific settings - consumer-group: "member-projections" - consumer-name: "${spring.application.name}-${random.uuid}" - - # Processing optimization - claim-idle-timeout: PT30S # Claim messages idle for 30 seconds - poll-timeout: PT1S # Poll every second - max-batch-size: 25 # Process 25 events per batch -``` - -## Testing-Strategie - -### 1. Integrationstests mit Testcontainers - -```kotlin -@Testcontainers -class RedisEventStoreIntegrationTest { - companion object { - @Container - val redisContainer: GenericContainer<*> = GenericContainer(DockerImageName.parse("redis:7-alpine")) - .withExposedPorts(6379) - } - - @Test - fun `should append and read events correctly`() { - // Test implementation using a real Redis instance - val events = listOf(testEvent1, testEvent2) - val newVersion = eventStore.appendToStream(events, aggregateId, 0) - - val readEvents = eventStore.readFromStream(aggregateId) - assertEquals(2, readEvents.size) - assertEquals(2, newVersion) - } -} -``` - -### 2. Unit-Tests für Business Logic - -```kotlin -@ExtendWith(MockKExtension::class) -class MemberServiceTest { - @MockK private lateinit var eventStore: EventStore - - @Test - fun `should handle concurrency conflicts gracefully`() { - // Given - every { eventStore.appendToStream(any(), any(), any()) } throws ConcurrencyException("Version conflict") - - // When & Then - assertThrows { - memberService.registerMember(command) - } - } -} -``` - -### 3. Consumer Tests - -```kotlin -@Test -fun `consumer should process events reliably`() { - // Arrange - val processedEvents = mutableListOf() - redisEventConsumer.registerEventHandler("TestEvent") { event -> - processedEvents.add(event) - } - - // Act - eventStore.appendToStream(testEvent, aggregateId, 0) - redisEventConsumer.pollEvents() // Manually trigger polling for deterministic tests - - // Assert - assertEquals(1, processedEvents.size) - assertEquals(testEvent.eventId, processedEvents[0].eventId) -} -``` - -### Test-Features - -* **Testcontainers Integration**: Echte Redis-Instanz für Integrationstests -* **Deterministische Tests**: Manueller Polling-Trigger statt Thread.sleep -* **Saubere Test-Daten**: @Transient-Annotation für Event-Klassen -* **Umfassende Szenarien**: Configuration, Error Handling, Stream, Resilience Tests - -## Performance & Monitoring - -### Performance-Charakteristiken - -* **Durchsatz**: >10 000 Events/Sekunde bei optimaler Konfiguration -* **Latenz**: <10ms für Event-Appending, <50ms für Event-Reading -* **Skalierung**: Horizontal skalierbar durch Consumer Groups -* **Speicher**: Effiziente Stream-basierte Speicherung - -### Monitoring-Metriken - -```yaml -# Micrometer/Prometheus Metriken (automatisch aktiviert) -management: - endpoints: - web: - exposure: - include: metrics,health - metrics: - export: - prometheus: - enabled: true - -# Custom Metriken -redis: - event-store: - metrics: - events-appended: counter - events-read: counter - consumer-lag: gauge - stream-length: gauge -``` - -### Health Checks - -```kotlin -@Component -class EventStoreHealthIndicator( - private val redisTemplate: StringRedisTemplate -) : HealthIndicator { - override fun health(): Health { - return try { - redisTemplate.opsForValue().get("health-check") - Health.up() - .withDetail("redis", "connected") - .build() - } catch (ex: Exception) { - Health.down(ex) - .withDetail("redis", "disconnected") - .build() - } - } -} -``` - -## Troubleshooting - -### Häufige Probleme - -#### 1. ConcurrencyException - -```kotlin -// Problem: Race Condition bei parallel Schreibvorgängen -// Lösung: Retry-Logic mit exponential backoff -@Retryable(value = [ConcurrencyException::class], maxAttempts = 3) -fun appendWithRetry(event: DomainEvent, streamId: UUID, expectedVersion: Long) { - eventStore.appendToStream(event, streamId, expectedVersion) -} -``` - -#### 2. Consumer Lag - -```bash -# Redis CLI - Check consumer group info -XINFO GROUPS event-stream:aggregate-id - -# Check pending messages -XPENDING event-stream:aggregate-id event-processors - -# Claim stuck messages manually if needed -XCLAIM event-stream:aggregate-id event-processors consumer-name 60000 message-id -``` - -#### 3. Speicher-Issues - -```yaml -# Redis Memory Optimization -redis: - event-store: - # Reduce batch size if memory constrained - max-batch-size: 25 - - # Shorter claim timeout to free memory faster - claim-idle-timeout: PT30S -``` - -#### 4. Verbindungsprobleme - -```yaml -# Connection troubleshooting -redis: - event-store: - connection-timeout: 10000 # Increase for slow networks - read-timeout: 10000 - max-pool-size: 5 # Reduce if connection limits hit -``` - -### Debugging - -```yaml -# Enable debug logging -logging: - level: - at.mocode.infrastructure.eventstore.redis: DEBUG - org.springframework.data.redis: DEBUG -``` - -### Monitoring Commands - -```bash -# Check Redis Stream info -redis-cli XINFO STREAM event-stream:aggregate-id - -# Monitor real-time commands -redis-cli MONITOR - -# Check memory usage -redis-cli INFO memory -``` - -## Migration & Deployment - -### Deployment Checklist - -* [ ] Redis Cluster verfügbar und erreichbar -* [ ] Konfiguration für Umgebung angepasst -* [ ] Consumer Groups erstellt (automatisch oder manuell) -* [ ] Monitoring und Alerting konfiguriert -* [ ] Health Checks implementiert -* [ ] Backup-Strategie definiert - -### Migration zwischen Versionen - -```kotlin -// Event Schema Evolution -@Serializable -data class MemberRegisteredEventV2( - // Neue Felder optional machen für Backward Compatibility - val additionalInfo: String? = null -) : BaseDomainEvent -``` - -### Backup & Recovery - -```bash -# Redis Stream Backup (RDB) -redis-cli BGSAVE - -# Stream-specific backup -redis-cli --rdb /backup/events.rdb - -# Recovery -redis-server --dbfilename events.rdb --dir /backup/ -``` - ---- - -**Letzte Aktualisierung**: 14. August 2025 diff --git a/infrastructure/event-store/redis-event-store/README.md b/infrastructure/event-store/redis-event-store/README.md deleted file mode 100644 index ce517daa..00000000 --- a/infrastructure/event-store/redis-event-store/README.md +++ /dev/null @@ -1,280 +0,0 @@ -# Redis Event Store Module - -## Überblick - -Dieses Modul stellt eine konkrete Implementierung der `event-store-api` unter Verwendung von Redis Streams als Event-Store-Backend bereit. - -## Architektur - -Das Modul folgt dem Provider-Pattern: - -- **event-store-api**: Provider-agnostische Interfaces (`EventStore`, `EventSerializer`) -- **redis-event-store**: Redis Streams-spezifische Implementierung - -## Verwendung - -### Dependency Hinzufügen - -```kotlin -dependencies { - implementation(projects.infrastructure.eventStore.redisEventStore) -} -``` - -### Konfiguration - -Das Modul verwendet Spring Boot Auto-Configuration. Konfigurieren Sie Redis über `application.yml`: - -```yaml -redis: - event-store: - host: localhost - port: 6379 - password: null # Optional - database: 1 # Separate database for event store (default: 1) - connectionTimeout: 2000 - readTimeout: 2000 - usePooling: true - maxPoolSize: 8 - minPoolSize: 2 - consumerGroup: event-processors - consumerName: event-consumer - streamPrefix: "event-stream:" - allEventsStream: all-events - claimIdleTimeout: 60s - pollTimeout: 100ms - maxBatchSize: 100 - createConsumerGroupIfNotExists: true -``` - -### Code-Beispiel - -```kotlin -@Service -class MyEventService( - private val eventStore: EventStore, - private val eventConsumer: RedisEventConsumer -) { - - // Event speichern - suspend fun saveEvent(aggregateId: Uuid, event: DomainEvent) { - eventStore.appendEvent( - aggregateId = aggregateId, - event = event, - expectedVersion = EventVersion.ANY - ) - } - - // Events abrufen - suspend fun loadEvents(aggregateId: Uuid): List { - return eventStore.loadEvents(aggregateId) - } - - // Events konsumieren - fun startConsuming() { - eventConsumer.consumeEvents { event -> - println("Received event: $event") - } - } -} -``` - -## Features - -- ✅ Event Sourcing mit Redis Streams -- ✅ Optimistic Locking mit Event Versioning -- ✅ Consumer Groups für parallele Event-Verarbeitung -- ✅ Event Replay-Fähigkeit -- ✅ Pub/Sub für Event-Benachrichtigungen -- ✅ Jackson-basierte Serialisierung -- ✅ Connection Pooling mit Lettuce -- ✅ Kotlin Coroutines Support - -## Redis Streams - -Das Modul nutzt Redis Streams für Event Sourcing: - -- **Stream pro Aggregate**: `event-stream:{aggregateId}` -- **All Events Stream**: `event-stream:all-events` -- **Consumer Groups**: Für parallele Verarbeitung -- **Message IDs**: Für Event-Ordering und Replay - -## Beans - -Das Modul registriert folgende Spring Beans: - -- `eventStoreRedisConnectionFactory`: Separate Redis ConnectionFactory für Event Store -- `eventStoreRedisTemplate`: StringRedisTemplate für Event-Operationen -- `eventSerializer`: Jackson-basierter Event-Serializer -- `eventStore`: EventStore Implementierung -- `eventConsumer`: RedisEventConsumer für Event-Verarbeitung - -## Gleichzeitige Verwendung mit redis-cache - -⚠️ **WICHTIG**: Wenn Sie sowohl `redis-cache` als auch `redis-event-store` im selben Service verwenden: - -### Unterschiedliche Databases - -Die Module verwenden **separate Redis Databases**, um Konflikte zu vermeiden: - -- **redis-cache**: Database 0 (Standard) -- **redis-event-store**: Database 1 (Standard, konfigurierbar) - -### Konfigurationsbeispiel - -```yaml -# Beide Module in einer application.yml -redis: - # Cache Konfiguration - host: localhost - port: 6379 - database: 0 # Cache verwendet Database 0 - - # Event Store Konfiguration (nested) - event-store: - host: localhost - port: 6379 - database: 1 # Event Store verwendet Database 1 - consumerGroup: event-processors -``` - -### Bean-Namen - -Die Module verwenden unterschiedliche Bean-Namen zur Vermeidung von Konflikten: - -| Komponente | redis-cache | redis-event-store | -|------------|-------------|-------------------| -| ConnectionFactory | `redisConnectionFactory` | `eventStoreRedisConnectionFactory` | -| Template | `redisTemplate` | `eventStoreRedisTemplate` | -| Serializer | `cacheSerializer` | `eventSerializer` | - -### Keine Konflikte - -✅ Die Module sind so designed, dass sie **ohne Konflikte** gleichzeitig verwendet werden können: - -- **Separate ConnectionFactories** mit `@Qualifier` Annotations -- **Separate Property-Prefixes**: `redis` vs `redis.event-store` -- **Unterschiedliche Database-Nummern**: 0 vs 1 -- **Unterschiedliche Bean-Namen**: Explizite Qualifier verhindern Kollisionen - -## Event Versioning - -Das Modul unterstützt Optimistic Locking: - -```kotlin -// Erwartete Version spezifizieren -eventStore.appendEvent( - aggregateId = aggregateId, - event = myEvent, - expectedVersion = EventVersion.of(5) // Erwartet Version 5 -) - -// Beliebige Version akzeptieren -eventStore.appendEvent( - aggregateId = aggregateId, - event = myEvent, - expectedVersion = EventVersion.ANY -) -``` - -Bei Version-Konflikten wird eine `ConcurrencyException` geworfen. - -## Consumer Groups - -Das Modul unterstützt Consumer Groups für parallele Event-Verarbeitung: - -```kotlin -// Consumer 1 -eventConsumer.consumeEvents( - consumerName = "consumer-1" -) { event -> - // Verarbeite Event - processEvent(event) -} - -// Consumer 2 (in der gleichen Consumer Group) -eventConsumer.consumeEvents( - consumerName = "consumer-2" -) { event -> - // Verarbeite Event parallel - processEvent(event) -} -``` - -Events werden automatisch auf verfügbare Consumer verteilt. - -## Event Replay - -Sie können Events von einem bestimmten Zeitpunkt oder Message-ID replaying: - -```kotlin -// Replay alle Events eines Aggregates -val events = eventStore.loadEvents(aggregateId) - -// Replay Events ab einer bestimmten Version -val eventsFromVersion = eventStore.loadEvents( - aggregateId = aggregateId, - fromVersion = EventVersion.of(10) -) -``` - -## Serialisierung - -Das Modul verwendet Jackson für Event-Serialisierung: - -- Automatische Kotlin-Modul Integration -- Polymorphe Serialisierung für verschiedene Event-Typen -- Custom Serializer können via `@Bean` überschrieben werden - -## Performance - -- **Connection Pooling**: Wiederverwendbare Verbindungen via Lettuce -- **Batch Processing**: Konfigurierbare Batch-Größe für Consumer -- **Non-blocking I/O**: Reaktive Operations mit Kotlin Coroutines -- **Stream-basiert**: Effiziente Event-Speicherung mit Redis Streams - -## Troubleshooting - -### Redis Verbindungsfehler - -``` -RedisConnectionFailureException: Unable to connect to Redis -``` - -**Lösung**: Überprüfen Sie Redis-Server und Netzwerk-Konfiguration. Stellen Sie sicher, dass Redis Streams unterstützt werden (Redis 5.0+). - -### Concurrency Exception - -``` -ConcurrencyException: Expected version X but found Y -``` - -**Lösung**: Dies ist normales Verhalten bei Optimistic Locking. Implementieren Sie Retry-Logik oder verwenden Sie `EventVersion.ANY`. - -### Consumer Group Fehler - -``` -Consumer group already exists -``` - -**Lösung**: Setzen Sie `createConsumerGroupIfNotExists: true` in der Konfiguration oder löschen Sie die Consumer Group manuell. - -### Bean-Konflikte mit Cache - -Wenn Sie Fehler wie "Multiple beans of type RedisConnectionFactory" erhalten: - -**Lösung**: Die Module verwenden bereits unterschiedliche Bean-Namen mit `@Qualifier`. Stellen Sie sicher, dass Sie beide Module korrekt konfiguriert haben (siehe Abschnitt "Gleichzeitige Verwendung"). - -## Best Practices - -1. **Separate Databases**: Verwenden Sie immer separate Redis Databases für Cache und Event Store -2. **Event Versioning**: Verwenden Sie Optimistic Locking für kritische Aggregates -3. **Consumer Groups**: Nutzen Sie Consumer Groups für horizontale Skalierung -4. **Error Handling**: Implementieren Sie Retry-Logik für transiente Fehler -5. **Monitoring**: Überwachen Sie Stream-Größen und Consumer Lag - -## Weitere Informationen - -- Siehe auch: [cache-api README](../../cache/cache-api/README.md) -- Siehe auch: [redis-cache README](../../cache/redis-cache/README.md) -- Redis Streams Dokumentation: diff --git a/infrastructure/messaging/README-INFRA-MESSAGING.md b/infrastructure/messaging/README-INFRA-MESSAGING.md deleted file mode 100644 index 35ebcb7a..00000000 --- a/infrastructure/messaging/README-INFRA-MESSAGING.md +++ /dev/null @@ -1,733 +0,0 @@ -# Infrastructure/Messaging Module - -## Überblick - -Das **Messaging-Modul** stellt die Infrastruktur für die asynchrone, reaktive Kommunikation zwischen den Microservices bereit. Es nutzt **Apache Kafka** als hochperformanten, verteilten Message-Broker und ist entscheidend für die Entkopplung von Services und die Implementierung einer skalierbaren, ereignisgesteuerten Architektur. - -Das Modul implementiert moderne **Domain-Driven Design (DDD)** Prinzipien mit expliziter Fehlerbehandlung über das **Result Pattern** und bietet sowohl suspending Coroutine-APIs als auch reaktive Stream-APIs für maximale Flexibilität. - -### Kernfeatures - -- **🎯 Result Pattern APIs**: Typsichere Fehlerbehandlung ohne Exceptions -- **⚡ Reactive Streams**: Hochperformante, nicht-blockierende I/O-Operationen -- **🔄 Intelligent Retry Logic**: Differenzierte Retry-Strategien basierend auf Fehlertypen -- **📊 Batch Processing**: Optimierte Verarbeitung mehrerer Events mit kontrollierbarer Parallelität -- **🔒 Security Features**: Sichere Deserialisierung mit Trusted-Package-Validierung -- **📈 Observability**: Umfassendes Logging und Monitoring für Production-Ready-Deployment -- **🧪 Comprehensive Testing**: Integration Tests mit Testcontainers und fokussierte Unit Tests - -## Architektur - -Das Modul ist in zwei spezialisierte Komponenten aufgeteilt, um Konfiguration von der Client-Logik zu trennen: - -infrastructure/messaging/ -├── messaging-config/ # Stellt die zentrale Kafka-Konfiguration bereit -└── messaging-client/ # Stellt wiederverwendbare, reaktive Clients bereit - -### `messaging-config` - -Dieses Modul zentralisiert die grundlegende Kafka-Konfiguration für das gesamte Projekt. - -- **Zweck:** Definiert Spring-Beans für die `ProducerFactory` (Basis für Producer) und eine `Map` mit Standard-Konfigurationen für Consumer (z.B. `bootstrap-servers`, `group-id`, Serializer). -- **Vorteil:** Stellt Konsistenz sicher und vereinfacht die Einrichtung neuer Producer oder Consumer in den Services. - -### `messaging-client` - -Dieses Modul baut auf der Konfiguration auf und stellt wiederverwendbare High-Level-Komponenten für die Interaktion mit Kafka bereit. - -#### Kern-Komponenten - -- **`EventPublisher` Interface**: Definiert moderne APIs für das Publizieren von Domain Events - - **Moderne APIs**: `publishEvent()` und `publishEvents()` mit Result Pattern - - **Legacy APIs**: `publishEventReactive()` und `publishEventsReactive()` (deprecated) - -- **`EventConsumer` Interface**: Definiert APIs für das Empfangen von Domain Events - - **Moderne APIs**: `receiveEventsWithResult()` mit Flow> für typsichere Fehlerbehandlung - - **Legacy APIs**: `receiveEvents()` mit Flux (deprecated) - -- **`KafkaEventPublisher`**: Implementierung des EventPublisher mit umfassendem Feature-Set - - Reaktive, nicht-blockierende Kafka-Integration mit `ReactiveKafkaProducerTemplate` - - Intelligente Retry-Logic mit exponential backoff - - Optimierte Batch-Verarbeitung mit kontrollierbarer Parallelität (10 concurrent operations) - - Comprehensive Logging und Progress-Tracking - -- **`KafkaEventConsumer`**: Implementierung des EventConsumer mit erweiterten Funktionen - - Connection-Pooling zur Wiederverwendung von KafkaReceiver-Instanzen - - Sichere Deserialisierung mit Trusted-Package-Validierung - - Manual Acknowledgment Control für bessere Kontrolle über Commit-Verhalten - - Consumer-Cache-Management für Ressourcenoptimierung - -- **`MessagingError` Hierarchie**: Domain-spezifische Fehlertypen für strukturierte Fehlerbehandlung - - `SerializationError`, `DeserializationError`: Serialization-/Deserialization-Probleme - - `ConnectionError`: Netzwerk- und Verbindungsfehler - - `TimeoutError`: Zeitüberschreitungen - - `AuthenticationError`: Authentifizierungs-/Autorisierungsfehler - - `TopicConfigurationError`: Topic-Konfigurationsprobleme - - `UnexpectedError`: Allgemeine unerwartete Fehler - -#### Vorteile - -- **Typsichere Fehlerbehandlung**: Result Pattern eliminiert unerwartete Exceptions -- **Flexible APIs**: Sowohl moderne Coroutine-basierte als auch Legacy reaktive APIs -- **Production-Ready**: Umfassendes Retry-Management, Observability und Ressourcenoptimierung -- **Domain-Driven Design**: Explizite Fehlertypen und saubere Abstraktionen - -## Verwendung - -Ein Microservice, der Nachrichten senden oder empfangen möchte, deklariert eine Abhängigkeit zu `:infrastructure:messaging:messaging-client` und injiziert die entsprechenden Interfaces. - -### Moderne API (Result Pattern + Coroutines) - **Empfohlen** - -**Beispiel für das Senden einer Nachricht mit typsicherer Fehlerbehandlung:** - -```kotlin -@Service -class EventNotificationService( - private val eventPublisher: EventPublisher -) { - suspend fun notifyNewEvent(eventDetails: EventDetails): Result { - val topic = "new-events-topic" - return eventPublisher.publishEvent(topic, eventDetails.id, eventDetails) - .onFailure { error -> - when (error) { - is MessagingError.SerializationError -> logger.error("Serialization failed for event", error) - is MessagingError.ConnectionError -> logger.warn("Connection issue, will retry later", error) - is MessagingError.TimeoutError -> logger.warn("Timeout publishing event", error) - else -> logger.error("Unexpected error publishing event", error) - } - } - } - - suspend fun notifyMultipleEvents(events: List>): Result> { - val topic = "batch-events-topic" - return eventPublisher.publishEvents(topic, events) - .onSuccess { results -> - logger.info("Successfully published {} events", results.size) - } - .onFailure { error -> - logger.error("Failed to publish batch events: {}", error.message) - } - } -} -``` - -**Beispiel für das Empfangen von Nachrichten mit typsicherer Fehlerbehandlung:** - -```kotlin -@Component -class ModernEventListener( - private val eventConsumer: EventConsumer -) { - private val logger = LoggerFactory.getLogger(ModernEventListener::class.java) - - @PostConstruct - fun startListening() { - val topic = "new-events-topic" - - // Moderne Result-basierte API mit Flow> - eventConsumer.receiveEventsWithResult(topic, EventDetails::class.java) - .asFlow() - .collect { result -> - result - .onSuccess { event -> - logger.info("Successfully received event with ID: {}", event.id) - processEvent(event) - } - .onFailure { error -> - when (error) { - is MessagingError.DeserializationError -> { - logger.error("Failed to deserialize event from topic '{}': {}", topic, error.message) - // Deserialization-Fehler sind meist permanent - keine weiteren Versuche - handlePoisonMessage(topic, error) - } - is MessagingError.ConnectionError -> { - logger.warn("Connection issue while consuming from topic '{}': {}", topic, error.message) - // Connection-Fehler sind oft temporär - Consumer wird automatisch retries - } - is MessagingError.TimeoutError -> { - logger.warn("Timeout while consuming from topic '{}': {}", topic, error.message) - // Timeout-Fehler können retries bekommen - } - else -> { - logger.error("Unexpected error consuming from topic '{}': {}", topic, error.message, error) - handleUnexpectedError(topic, error) - } - } - } - } - } - - private suspend fun processEvent(event: EventDetails) { - // Geschäftslogik zur Verarbeitung des Events - logger.debug("Processing event: {}", event) - } - - private suspend fun handlePoisonMessage(topic: String, error: MessagingError.DeserializationError) { - // Poison Messages in separates Topic oder Dead Letter Queue verschieben - logger.warn("Moving poison message from topic '{}' to dead letter queue", topic) - } - - private suspend fun handleUnexpectedError(topic: String, error: MessagingError) { - // Monitoring/Alerting für unerwartete Fehler - logger.error("Alerting monitoring system for unexpected error in topic '{}'", topic) - } -} -``` - -**Beispiel für Consumer mit Coroutines und strukturierter Parallelität:** - -```kotlin -@Service -class BatchEventProcessor( - private val eventConsumer: EventConsumer -) { - private val logger = LoggerFactory.getLogger(BatchEventProcessor::class.java) - - suspend fun processBatchEvents(topic: String): Result = withContext(Dispatchers.IO) { - try { - var processedCount = 0 - var errorCount = 0 - - eventConsumer.receiveEventsWithResult(topic, EventDetails::class.java) - .asFlow() - .take(100) // Verarbeite maximal 100 Events pro Batch - .collect { result -> - result - .onSuccess { event -> - processedCount++ - logger.debug("Processed event {}/{}", processedCount, 100) - } - .onFailure { error -> - errorCount++ - logger.warn("Error processing event: {}", error.message) - } - } - - logger.info("Batch processing completed: {} processed, {} errors", processedCount, errorCount) - Result.success(processedCount) - } catch (exception: Exception) { - logger.error("Batch processing failed", exception) - Result.failure(MessagingError.UnexpectedError("Batch processing failed: ${exception.message}", exception)) - } - } -} -``` - -### Legacy Reactive API - **Wird depreciert** - -**Beispiel für das Senden einer Nachricht (reaktiv, nicht-blockierend):** - -```kotlin -@Service -class LegacyEventNotificationService( - private val eventPublisher: EventPublisher -) { - @Deprecated("Use suspending publishEvent with Result instead") - fun notifyNewEventReactive(eventDetails: EventDetails) { - val topic = "new-events-topic" - eventPublisher.publishEventReactive(topic, eventDetails.id, eventDetails) - .subscribe( - { /* onNext: Unit received */ }, - { error -> logger.error("Failed to send message to topic '{}'", topic, error) }, - { /* onComplete: Nichts zu tun */ } - ) - // Die Methode kehrt sofort zurück, ohne auf die Bestätigung von Kafka zu warten. - } -} -``` - -**Beispiel für das Empfangen von Nachrichten (reaktiv):** - -```kotlin -@Component -class EventListener( - private val eventConsumer: EventConsumer -) { - @PostConstruct - fun listenForEvents() { - val topic = "new-events-topic" - eventConsumer.receiveEvents(topic) - .subscribe { event -> - logger.info("Received new event with ID: {}", event.id) - // Geschäftslogik zur Verarbeitung des Events... - } - } -} -``` - -## Konfiguration - -Das Messaging-Modul bietet umfassende Konfigurationsmöglichkeiten über die `KafkaConfig`-Klasse mit automatischer Validierung und optimierten Standardwerten für Production-Ready-Deployments. - -### Basis-Konfiguration - -```kotlin -@Configuration -class MessagingConfiguration { - - @Bean - fun kafkaConfig(): KafkaConfig { - return KafkaConfig().apply { - // Kafka-Cluster-Verbindung - bootstrapServers = "kafka-cluster:9092" // oder "localhost:9092" für lokale Entwicklung - - // Consumer-Gruppierung - defaultGroupIdPrefix = "myapp-messaging" - - // Sicherheitseinstellungen - trustedPackages = "com.mycompany.*,at.mocode.*" - enableSecurityFeatures = true - - // Performance-Tuning - connectionPoolSize = 20 // Für hochfrequente Anwendungen - } - } -} -``` - -### Konfigurationsoptionen - -| Parameter | Typ | Standard | Beschreibung | -|-----------|-----|----------|--------------| -| `bootstrapServers` | String | "localhost:9092" | Kafka-Cluster-Endpunkte. Unterstützt `host:port` und `PROTOCOL://host:port` Formate | -| `defaultGroupIdPrefix` | String | "messaging-client" | Präfix für automatisch generierte Consumer-Gruppen | -| `trustedPackages` | String | "at.mocode.*" | Comma-separated List von Packages für sichere JSON-Deserialisierung | -| `enableSecurityFeatures` | Boolean | true | Aktiviert erweiterte Sicherheitsfeatures für Production | -| `connectionPoolSize` | Int | 10 | Anzahl der gleichzeitigen Kafka-Verbindungen im Pool | - -### Production-Konfiguration - -Für Production-Umgebungen empfohlene Konfiguration: - -```kotlin -@Configuration -@Profile("production") -class ProductionMessagingConfiguration { - - @Bean - fun kafkaConfig(): KafkaConfig { - return KafkaConfig().apply { - // Hochverfügbares Kafka-Cluster - bootstrapServers = "kafka-01.prod:9092,kafka-02.prod:9092,kafka-03.prod:9092" - - // Environment-spezifische Gruppierung - defaultGroupIdPrefix = "${System.getenv("APP_NAME")}-${System.getenv("ENVIRONMENT")}" - - // Restriktive Sicherheitseinstellungen - trustedPackages = "com.mycompany.events.*,com.mycompany.domain.*" - enableSecurityFeatures = true - - // Optimiert für hohe Parallelität - connectionPoolSize = 50 - } - } -} -``` - -### Umgebungsvariablen - -Das Modul unterstützt Konfiguration über Umgebungsvariablen für Container-Deployments: - -```bash -# Docker/Kubernetes Environment Variables -KAFKA_BOOTSTRAP_SERVERS=kafka-cluster:9092 -KAFKA_GROUP_ID_PREFIX=myapp-prod -KAFKA_TRUSTED_PACKAGES=com.mycompany.* -KAFKA_CONNECTION_POOL_SIZE=25 -KAFKA_ENABLE_SECURITY=true -``` - -### Erweiterte Producer-Konfiguration - -Die `KafkaConfig` stellt optimierte Producer-Eigenschaften bereit: - -```kotlin -// Automatisch konfigurierte Producer-Eigenschaften: -// - Batch-Verarbeitung (32KB Batches, 5ms Linger) -// - Snappy-Komprimierung für bessere Performance -// - Idempotenz für Exactly-Once-Semantics -// - Intelligente Retry-Logik (3 Versuche, 1s Backoff) -// - 30s Delivery-Timeout mit 10s Request-Timeout -``` - -### Erweiterte Consumer-Konfiguration - -Consumer werden automatisch mit optimierten Einstellungen konfiguriert: - -```kotlin -// Automatisch konfigurierte Consumer-Eigenschaften: -// - Manual Commit für bessere Kontrolle -// - Optimierte Fetch-Größen (1KB min, 1MB max) -// - 500ms Max-Wait für Fetch-Operationen -// - Session-Timeout: 30s, Heartbeat: 3s -// - Automatic Offset Reset: earliest -// - Max 500 Records pro Poll -``` - -### Monitoring und Observability - -```kotlin -@Component -class MessagingHealthIndicator( - private val kafkaConfig: KafkaConfig -) : HealthIndicator { - - override fun health(): Health { - return try { - // Kafka-Cluster-Konnektivität prüfen - val adminClient = AdminClient.create(kafkaConfig.producerConfigs()) - val clusterMetadata = adminClient.describeCluster() - val nodeCount = clusterMetadata.nodes().get(5, TimeUnit.SECONDS).size - - Health.up() - .withDetail("kafka.cluster.nodes", nodeCount) - .withDetail("kafka.bootstrap.servers", kafkaConfig.bootstrapServers) - .withDetail("kafka.connection.pool.size", kafkaConfig.connectionPoolSize) - .build() - } catch (exception: Exception) { - Health.down() - .withDetail("kafka.error", exception.message) - .withException(exception) - .build() - } - } -} -``` - -## Dependency Management - -### Gradle-Konfiguration - -Das Messaging-Modul nutzt eine saubere Modularisierung über Gradle Composite Builds: - -```kotlin -// In einem Service-Modul -dependencies { - // Hauptabhängigkeit für messaging functionality - implementation(projects.infrastructure.messaging.messagingClient) - - // Die messaging-config wird transitiv eingebunden - // Alle benötigten Kafka-, Spring- und Reactive-Dependencies sind enthalten -} -``` - -### Verfügbare Module - -| Modul | Zweck | Transitive Dependencies | -|--------|--------|------------------------| -| `messaging-config` | Zentrale Kafka-Konfiguration | Spring Kafka, Jackson, Kafka Clients | -| `messaging-client` | High-Level Publisher/Consumer APIs | Reactor Kafka, Kotlinx Coroutines, messaging-config | - -### Version-Management - -```kotlin -// platform/platform-bom/build.gradle.kts - Zentrale Versionsverwaltung -dependencies { - constraints { - api("org.springframework.kafka:spring-kafka:3.1.4") - api("io.projectreactor.kafka:reactor-kafka:1.3.22") - api("org.apache.kafka:kafka-clients:3.6.1") - } -} -``` - -## Testing-Strategie - -Die Zuverlässigkeit des Moduls wird durch eine mehrstufige Teststrategie sichergestellt, die sowohl Unit- als auch Integrationstests umfasst: - -### Integrationstests (Goldstandard) -- **Testcontainers**: Der `KafkaIntegrationTest` startet einen echten Apache Kafka Docker-Container, um die Funktionalität unter realen Bedingungen zu validieren -- **Reaktives Testen**: Nutzt Project Reactor's `StepVerifier` für deterministische Tests der reaktiven Streams ohne unzuverlässige Thread.sleep-Aufrufe -- **Lifecycle Management**: Saubere Ressourcenverwaltung über @BeforeEach und @AfterEach für korrekte Freigabe von Producer-Threads -- **End-to-End Validierung**: Vollständige Publish-Subscribe-Zyklen mit echtem Kafka-Cluster - -### Unit Tests -- **`KafkaEventPublisherErrorTest`**: Fokussierte Tests für Fehlerbehandlung mit MockK für isolierte Testszenarien -- **Fehlerszenarien**: Systematische Tests für Serialization-, Authentication-, Connection- und Timeout-Fehler -- **Batch-Verarbeitung**: Validierung von Batch-Operationen und Empty-Batch-Handling -- **Retry-Logic**: Tests für intelligente Retry-Mechanismen und Retry-Exhaustion - -### Sicherheits- und Konfigurationstests -- **`KafkaSecurityTest`**: Validierung der Sicherheitskonfigurationen und Trusted-Package-Verwaltung -- **`KafkaEventConsumerCacheTest`**: Tests für Consumer-Caching und Ressourcenoptimierung -- **Konfigurationsvalidierung**: Automatische Validierung aller Konfigurationsparameter - -## Neue Features und Optimierungen (2025) - -### Domain-Driven Design (DDD) Integration -- **Result Pattern APIs**: Neue suspending Coroutine-basierte APIs mit typsicherer Fehlerbehandlung über das Result Pattern -- **Domain-spezifische Fehlertypen**: Umfassende `MessagingError` Hierarchie (SerializationError, ConnectionError, TimeoutError, AuthenticationError, etc.) -- **Explizite Fehlerbehandlung**: Eliminiert unerwartete Exceptions durch strukturierte Fehler-Typen -- **Backward Compatibility**: Legacy-reactive APIs bleiben verfügbar, sind aber als deprecated markiert - -### Erweiterte Konfigurationsvalidierung -- **Automatische Validierung**: Alle Konfigurationsparameter werden automatisch bei der Zuweisung validiert -- **Bootstrap-Server-Format**: Unterstützt sowohl einfache (`host:port`) als auch protokoll-präfixierte Formate (`PLAINTEXT://host:port`) -- **Sicherheitsfeatures**: Konfigurierbare Sicherheitsfunktionen für Produktionsumgebungen -- **Connection-Pool-Management**: Konfigurierbare Verbindungspool-Größe für bessere Ressourcenverwaltung - -### Verbesserte Observability -- **Strukturierte Logs**: Erweiterte Logging-Informationen mit GroupID, Timestamps und Event-Kontext -- **Fehlerkontext**: Detaillierte Fehlerinformationen mit Retry-Status und Event-Type-Details -- **Performance-Tracking**: Bessere Nachvollziehbarkeit von Batch-Operationen und Retry-Versuchen -- **Batch-Progress-Logging**: Automatisches Progress-Logging bei großen Batch-Operationen (alle 100 Events) - -### Robustheit-Verbesserungen -- **Intelligente Retry-Logik**: Differenzierte Retry-Strategien basierend auf Fehlertypen (keine Retries für Serialization/Auth-Fehler) -- **Exponential Backoff**: Konfigurierbare Retry-Delays mit exponential backoff (1s initial, max 10s backoff) -- **Controlled Batch Concurrency**: Optimierte Batch-Verarbeitung mit konfigurierbarer Parallelität (Standard: 10 concurrent operations) -- **Testcontainer-Kompatibilität**: Vollständige Kompatibilität mit Docker-basierten Tests -- **Enhanced Error Handling**: Verbesserte Fehlerbehandlung mit strukturierten Kontext-Informationen - -### Test-Suite Optimierung -- **Fokussierte Unit Tests**: Bereinigte Test-Suite mit Fokus auf essentielle Funktionalität -- **MockK Integration**: Moderne Mocking-Frameworks für isolierte Unit Tests -- **StepVerifier Korrekturen**: Korrigierte reaktive Test-Assertions für `Mono` Rückgabetypen -- **Reduced Test Complexity**: Entfernung unnötiger Performance- und Logging-Tests zugunsten fokussierter Funktionstests - -## Troubleshooting - -### Häufige Probleme und Lösungen - -#### 1. Connection-Fehler zu Kafka - -**Problem**: `MessagingError.ConnectionError` beim Senden oder Empfangen von Nachrichten - -**Mögliche Ursachen und Lösungen**: - -1. **Kafka-Cluster-Erreichbarkeit prüfen**: - -```bash -# Teste Verbindung zu Kafka-Cluster -telnet kafka-cluster 9092 - -# Oder mit nc (netcat) -nc -zv kafka-cluster 9092 -``` - -2. **Bootstrap-Server-Konfiguration validieren**: - -```kotlin -// Multiple Broker für High Availability -kafkaConfig.bootstrapServers = "kafka-01:9092,kafka-02:9092,kafka-03:9092" -``` - -3. **Netzwerk-Timeouts erhöhen für langsame Verbindungen**: - -```kotlin -// Producer-Konfiguration erweitern -override fun producerConfigs(): Map = super.producerConfigs() + mapOf( - ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG to 30000, // 30 Sekunden - ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG to 60000 // 1 Minute -) -``` - -#### 2. Deserialization-Fehler - -**Problem**: `MessagingError.DeserializationError` beim Empfangen von Nachrichten - -**Lösungsansätze**: - -```kotlin -// 1. Trusted Packages erweitern -kafkaConfig.trustedPackages = "at.mocode.*,com.mycompany.*,java.util.*" - -// 2. Event-Schema-Kompatibilität prüfen -@JsonIgnoreProperties(ignoreUnknown = true) -data class EventDetails( - val id: String, - val version: Int = 1 // Schema-Versionierung -) - -// 3. Dead Letter Queue für Poison Messages implementieren -private suspend fun handlePoisonMessage(topic: String, error: MessagingError.DeserializationError) { - val dlqTopic = "${topic}.dlq" - eventPublisher.publishEvent(dlqTopic, "error", error.message) -} -``` - -#### 3. Performance-Probleme - -**Problem**: Langsame Message-Verarbeitung oder hohe Latenz - -**Optimierungsstrategien**: - -```kotlin -// 1. Connection Pool vergrößern -kafkaConfig.connectionPoolSize = 50 - -// 2. Batch-Verarbeitung nutzen -suspend fun processEventsBatch(events: List) { - val batchSize = 100 - events.chunked(batchSize).forEach { batch -> - // Parallele Verarbeitung pro Batch - batch.map { event -> - async { processEvent(event) } - }.awaitAll() - } -} - -// 3. Consumer-Parallelität erhöhen -// Mehrere Consumer-Instanzen mit unterschiedlichen Group-IDs -``` - -#### 4. Memory-Leaks bei Consumers - -**Problem**: Speicherverbrauch steigt kontinuierlich - -**Lösungen**: - -```kotlin -// 1. Consumer-Cache korrekt verwalten -@PreDestroy -fun cleanup() { - eventConsumer.cleanup() // Cached receivers freigeben -} - -// 2. Flow-Streams korrekt beenden -eventConsumer.receiveEventsWithResult(topic, EventDetails::class.java) - .asFlow() - .take(1000) // Streams begrenzen - .catch { exception -> - logger.error("Stream error", exception) - } - .collect { /* process */ } - -// 3. Subscription Management -val subscription = eventConsumer.receiveEvents(topic) - .take(Duration.ofMinutes(5)) // Auto-Timeout nach 5 Minuten - .subscribe() -``` - -### Best Practices - -#### 1. Error Handling - -```kotlin -// Strukturierte Fehlerbehandlung mit spezifischen Aktionen -suspend fun handleMessagingError(error: MessagingError, topic: String) { - when (error) { - is MessagingError.SerializationError, - is MessagingError.DeserializationError -> { - // Keine Retries - permanente Fehler - alertMonitoring("Schema compatibility issue", error) - } - is MessagingError.ConnectionError, - is MessagingError.TimeoutError -> { - // Retries möglich - temporäre Fehler - scheduleRetry(error, topic) - } - is MessagingError.AuthenticationError -> { - // Security-Issue - sofortige Attention erforderlich - alertSecurity("Authentication failed", error) - } - else -> { - // Unbekannte Fehler - Investigation erforderlich - alertDevelopment("Unknown messaging error", error) - } - } -} -``` - -#### 2. Monitoring und Alerting - -```kotlin -// Umfassendes Monitoring einrichten -@Component -class MessagingMetrics( private val meterRegistry: MeterRegistry ) { - private val publishedEvents = Counter.builder("messaging.events.published") - .register(meterRegistry) - - private val consumedEvents = Counter.builder("messaging.events.consumed") - .register(meterRegistry) - - private val errorCounter = Counter.builder("messaging.errors") - .tag("type", "unknown") - .register(meterRegistry) - - fun recordPublishedEvent(topic: String) { - publishedEvents.increment(Tags.of("topic", topic)) - } - - fun recordError(error: MessagingError, topic: String) { - errorCounter.increment( - Tags.of( - "error.type", error.javaClass.simpleName, - "topic", topic - ) - ) - } -} -``` - -#### 3. Testing von Messaging-Code - -```kotlin -// Integration Test mit Testcontainers -@TestMethodOrder(OrderAnnotation::class) -class MessagingIntegrationTest { - - companion object { - @Container - val kafka = KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:latest")) - } - - @Test - @Order(1) - fun `should publish and consume events successfully`() = runTest { - // Given - val topic = "test-topic" - val event = EventDetails("test-id", "test-data") - - // When - val publishResult = eventPublisher.publishEvent(topic, event.id, event) - val consumedEvents = mutableListOf>() - - eventConsumer.receiveEventsWithResult(topic, EventDetails::class.java) - .asFlow() - .take(1) - .collect { result -> consumedEvents.add(result) } - - // Then - publishResult.shouldBeSuccess() - consumedEvents.shouldHaveSize(1) - consumedEvents.first().getOrNull()?.id shouldBe event.id - } -} -``` - -### Häufig gestellte Fragen (FAQ) - -**Q: Wie unterscheidet sich die moderne API von der Legacy-API?** - -A: Die moderne API nutzt das Result Pattern für explizite Fehlerbehandlung und Kotlin Coroutines für bessere Performance. Legacy APIs verwenden reaktive Streams mit Exception-basierter Fehlerbehandlung. - -**Q: Wann sollte ich Batch-Verarbeitung verwenden?** - -A: Batch-Verarbeitung ist empfohlen bei: - -- Mehr als 10 Events pro Sekunde -- Hoher Netzwerk-Latenz zum Kafka-Cluster -- Events, die zusammen verarbeitet werden können - -**Q: Wie handle ich Backpressure bei hohem Event-Durchsatz?** - -A: Nutzen Sie die eingebauten Flow-Operatoren: - -```kotlin -eventConsumer.receiveEventsWithResult(topic, EventType::class.java) - .asFlow() - .buffer(1000) // Puffering für Backpressure-Handling - .flowOn(Dispatchers.IO) // Separater Dispatcher - .collect { /* process */ } -``` - ---- - -**Letzte Aktualisierung**: 15. August 2025 - -## Aktualisierungen (September 2025) - -- ReactiveKafkaConfig: Der Bean kafkaConfig() ist jetzt mit @ConditionalOnMissingBean annotiert. Dadurch wird kein zweiter KafkaConfig-Bean erzeugt, wenn bereits extern einer bereitgestellt wird. Dies verhindert Bean-Kollisionen und erleichtert Überschreibungen in Services/Tests. -- Legacy Consumer API: Die reifizierte Extension receiveEvents(topic) wirft bei Fehlern nicht mehr, sondern filtert Fehl-Results heraus und protokolliert sie. Das hält den Flux lebendig und ist robuster. Die moderne, empfohlene Methode bleibt receiveEventsWithResult(topic): Flow>. -- Dokumentation: Diese Hinweise wurden ergänzt. Module bleiben ansonsten unverändert und production-ready. diff --git a/infrastructure/monitoring/README-INFRA-MONITORING.md b/infrastructure/monitoring/README-INFRA-MONITORING.md deleted file mode 100644 index 0d89741d..00000000 --- a/infrastructure/monitoring/README-INFRA-MONITORING.md +++ /dev/null @@ -1,112 +0,0 @@ -# Infrastructure/Monitoring Modul – Aktuelle Dokumentation (Stand: September 2025) - -## Überblick - -Das Monitoring-Modul stellt die zentrale Observability-Infrastruktur für alle Services bereit. Es deckt zwei der drei Observability-Säulen ab: Metriken und Distributed Tracing. Ziel ist es, Betriebsdaten konsistent zu erfassen, zu visualisieren und Probleme schnell zu erkennen. - -- Metriken: Quantitative Leistungsdaten wie Antwortzeiten, Fehlerraten, JVM- und Systemmetriken. -- Distributed Tracing: Ende-zu-Ende-Verfolgung einer Anfrage über Service-Grenzen hinweg (Trace/Span). - -## Aufgabe des Moduls - -- Bereitstellung einer einheitlichen Monitoring-Client-Bibliothek für alle Microservices. -- Zentrale Bereitstellung eines Zipkin-Servers zur Sammlung und Visualisierung von Traces. -- Sicherstellung eines konsistenten Prometheus-/Micrometer-Setups über alle Services hinweg. -- Bereitstellung konservativer Default-Properties, die pro Service überschrieben werden können. - -## Architektur - -Das Modul ist in eine wiederverwendbare Client-Bibliothek und einen zentralen Server aufgeteilt: - -``` -infrastructure/monitoring/ -├── monitoring-client/ # Bibliothek, die jeder Service einbindet -└── monitoring-server/ # Eigenständiger Service, der den Zipkin-Server hostet -``` - -### monitoring-client - -Dies ist eine wiederverwendbare Bibliothek, die von jedem Microservice (z. B. gateway, members, masterdata) eingebunden wird. - -- Zweck: Automatische Instrumentierung der Anwendung, Aktivierung von Actuator-/Tracing-Funktionen und Export von Metriken. -- Technologien: - - Spring Boot Actuator: Stellt u. a. den Endpunkt `/actuator/prometheus` bereit. - - Micrometer (Core, Prometheus): Einheitliches Metrik-API, Export in Prometheus-Format. - - Micrometer Tracing (Brave Bridge) + Zipkin Reporter: Erzeugt und sendet Traces (Spans) an Zipkin. -- Vorteile: Keine individuelle Konfiguration in jedem Service nötig; sinnvolle Defaults per AutoConfiguration. - -#### AutoConfiguration und Defaults - -Die Klasse MonitoringClientAutoConfiguration ist als `@AutoConfiguration` deklariert und aktiviert nur, wenn Actuator/Micrometer am Classpath sind (`@ConditionalOnClass`). Sie lädt `monitoring-defaults.properties` mit niedriger Priorität. Wichtige Defaults: - -``` -management.endpoints.web.exposure.include=health,info,prometheus -management.tracing.enabled=true -management.tracing.sampling.probability=${TRACING_SAMPLING_PROBABILITY:1.0} -management.observations.http.server.requests.enabled=true -management.info.env.enabled=true -management.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans -``` - -Hinweise: - -- Sampling-Rate: In Entwicklung 1.0 (100%). In Produktion sollte dies reduziert werden (z. B. 0.1). -- Endpunkte: Der Prometheus-Scrape-Pfad ist einheitlich `/actuator/prometheus`. -- Überschreibung: Jede Anwendung kann diese Werte über application.yml/-properties anpassen. - -### monitoring-server - -Eigenständiger Spring-Boot-Service, der den Zipkin-Server hostet. Durch die Zipkin-Server-Abhängigkeit erfolgt die Auto-Konfiguration; eine explizite `@EnableZipkinServer`-Annotation ist nicht erforderlich. - -- Zweck: Empfang und UI-Visualisierung von Traces aus allen Services. -- Metriken: Der Server exportiert eigene Metriken (Prometheus-Registry eingebunden), sodass er ebenfalls von Prometheus gescraped werden kann. - -## Zusammenspiel im System - -1. Jeder Microservice bindet `:infrastructure:monitoring:monitoring-client` ein und exponiert `/actuator/prometheus`; Traces werden an Zipkin gesendet. -2. Der `:infrastructure:monitoring:monitoring-server` empfängt Traces und stellt die Zipkin UI bereit. -3. Prometheus (docker-compose) scraped periodisch alle `/actuator/prometheus`-Endpunkte und speichert Metriken. -4. Grafana (docker-compose) visualisiert Metriken/Dashboards. - -Diese Kombination aus Micrometer, Prometheus, Zipkin und Grafana bildet einen gängigen Production-Stack. - -## Verwendung in Services - -- Abhängigkeit hinzufügen: `implementation(projects.infrastructure.monitoring.monitoringClient)` (über build.gradle.kts der Services). -- Optional: Eigene Tags setzen (z. B. `management.metrics.tags.application`, `environment`). -- Optional: Sampling-Rate via Umgebungsvariable `TRACING_SAMPLING_PROBABILITY` anpassen. - -Beispiel (application.yml): - -```yaml -management: - metrics: - tags: - application: ${spring.application.name} - environment: ${SPRING_PROFILES_ACTIVE:dev} - tracing: - sampling: - probability: 0.2 -``` - -## Testing-Strategie (Tracer-Bullet-Zyklus) - -- Monitoring-Server: Ein grundlegender Smoke-Test prüft erfolgreichen Start der Anwendung. -- Monitoring-Client: Keine dedizierten Unit-Tests; Validierung erfolgt End-to-End durch integrierte Services (Prometheus scrape, Zipkin-Empfang). - -## Aktualitäts-Check (Repo-Stand September 2025) - -- monitoring-client: AutoConfiguration (Deutsch kommentiert) und Defaults vorhanden; Endpunkt `management.zipkin.tracing.endpoint` verweist auf `zipkin:9411` und entspricht docker-compose. -- monitoring-server: Kotlin-Hauptklasse startet Zipkin-Server; Build-Datei bindet Actuator, Zipkin-Server und Prometheus-Registry ein. -- Gateway application.yml exponiert Actuator-Endpunkte inkl. prometheus; passt zum Monitoring-Ansatz. - -## Optimierungen (September 2025) - -- Dokumentation präzisiert und vollständig auf Deutsch gebracht; Zweck und Umsetzung klar herausgestellt. -- Kleine Konsistenz-Anpassung: Kommentare im Wurzel-Build-Skript des Monitoring-Moduls ins Deutsche übertragen. -- Empfehlung (optional, nicht zwingend umgesetzt): - - Standard-Tags global setzen (application, environment, instance) via `management.metrics.tags.*`. - - Falls Services eigene MeterRegistry konfigurieren: `@ConditionalOnMissingBean` beachten, um Kollisionen zu vermeiden. - ---- -Letzte Aktualisierung: 4. September 2025 diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index d80395ee..5fc796ba 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@cashapp/sqldelight-sqljs-worker@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@cashapp/sqldelight-sqljs-worker/-/sqldelight-sqljs-worker-2.1.0.tgz#4ab898698aca9487f47fc9a42107c606c3ce81c5" + integrity sha512-odvBljb1rUOCk3UUZgjdiAChEohYI4Fy6Tj3NUy3l6u3WV/we+tjDTJ/kC25CJKD4pv0ZlH5AL1sKsZ5clKCew== + "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -2648,6 +2653,11 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" +sql.js@^1.8.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/sql.js/-/sql.js-1.13.0.tgz#f73cba7eaba0bc881f466c9149e00cf598fb01e0" + integrity sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" diff --git a/logs/gateway.log b/logs/gateway.log deleted file mode 100644 index 25bef396..00000000 --- a/logs/gateway.log +++ /dev/null @@ -1,82 +0,0 @@ -2025-11-27 19:01:19.988 [background-preinit] INFO [] o.h.validator.internal.util.Version - HV000001: Hibernate Validator 8.0.3.Final -2025-11-27 19:01:20.022 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - Starting GatewayApplicationKt using Java 21.0.9 with PID 56550 (/home/stefan-mo/WsMeldestelle/Meldestelle/infrastructure/gateway/build/classes/kotlin/main started by stefan-mo in /home/stefan-mo/WsMeldestelle/Meldestelle) -2025-11-27 19:01:20.022 [main] DEBUG [] a.m.i.gateway.GatewayApplicationKt - Running with Spring Boot v3.5.6, Spring v6.2.11 -2025-11-27 19:01:20.022 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - The following 1 profile is active: "dev" -2025-11-27 19:01:21.201 [main] INFO [] o.s.cloud.context.scope.GenericScope - BeanFactory id=4eb90187-1826-32ce-9dc7-fa80cb000915 -2025-11-27 19:01:23.095 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [After] -2025-11-27 19:01:23.095 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Before] -2025-11-27 19:01:23.095 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Between] -2025-11-27 19:01:23.095 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Cookie] -2025-11-27 19:01:23.095 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Header] -2025-11-27 19:01:23.095 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Host] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Method] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Path] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Query] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [ReadBody] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [RemoteAddr] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [XForwardedRemoteAddr] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Weight] -2025-11-27 19:01:23.096 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [CloudFoundryRouteService] -2025-11-27 19:01:23.674 [main] INFO [] o.s.b.a.e.web.EndpointLinksResolver - Exposing 6 endpoints beneath base path '/actuator' -2025-11-27 19:01:24.297 [main] WARN [] o.s.c.l.c.LoadBalancerCacheAutoConfiguration$LoadBalancerCaffeineWarnLogger - Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath. -2025-11-27 19:01:24.430 [main] INFO [] o.s.b.w.e.netty.NettyWebServer - Netty started on port 8080 (http) -2025-11-27 19:01:24.434 [main] INFO [] o.s.c.c.s.ConsulServiceRegistry - Registering service with consul: NewService{id='meldestelle-8080-74499425-14ca-46e0-9d2c-2a31faa28455', name='meldestelle', tags=[], address='10.0.0.18', meta={secure=false}, port=8080, enableTagOverride=null, check=Check{script='null', dockerContainerID='null', shell='null', interval='10s', ttl='null', http='http://10.0.0.18:8080/actuator/health', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null', grpc='null', grpcUseTLS=null}, checks=null} -2025-11-27 19:01:24.559 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - Started GatewayApplicationKt in 4.894 seconds (process running for 5.462) -2025-11-27 19:03:18.798 [SpringApplicationShutdownHook] INFO [] o.s.b.w.e.netty.GracefulShutdown - Commencing graceful shutdown. Waiting for active requests to complete -2025-11-27 19:03:18.799 [netty-shutdown] INFO [] o.s.b.w.e.netty.GracefulShutdown - Graceful shutdown complete -2025-11-27 19:03:20.810 [SpringApplicationShutdownHook] INFO [] o.s.c.c.s.ConsulServiceRegistry - Deregistering service with consul: meldestelle-8080-74499425-14ca-46e0-9d2c-2a31faa28455 -2025-11-27 19:06:42.612 [background-preinit] INFO [] o.h.validator.internal.util.Version - HV000001: Hibernate Validator 8.0.3.Final -2025-11-27 19:06:42.637 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - Starting GatewayApplicationKt using Java 21.0.9 with PID 61912 (/home/stefan-mo/WsMeldestelle/Meldestelle/infrastructure/gateway/build/classes/kotlin/main started by stefan-mo in /home/stefan-mo/WsMeldestelle/Meldestelle) -2025-11-27 19:06:42.638 [main] DEBUG [] a.m.i.gateway.GatewayApplicationKt - Running with Spring Boot v3.5.6, Spring v6.2.11 -2025-11-27 19:06:42.638 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - The following 1 profile is active: "dev" -2025-11-27 19:06:43.726 [main] INFO [] o.s.cloud.context.scope.GenericScope - BeanFactory id=4eb90187-1826-32ce-9dc7-fa80cb000915 -2025-11-27 19:06:44.156 [main] WARN [] o.s.b.w.r.c.AnnotationConfigReactiveWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'gatewayHealthIndicator' defined in file [/home/stefan-mo/WsMeldestelle/Meldestelle/infrastructure/gateway/build/classes/kotlin/main/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'compositeDiscoveryClient' defined in class path resource [org/springframework/cloud/client/discovery/composite/CompositeDiscoveryClientAutoConfiguration.class]: Unsatisfied dependency expressed through method 'compositeDiscoveryClient' parameter 0: Error creating bean with name 'consulDiscoveryClient' defined in class path resource [org/springframework/cloud/consul/discovery/ConsulDiscoveryClientConfiguration.class]: Unsatisfied dependency expressed through method 'consulDiscoveryClient' parameter 0: Error creating bean with name 'consulClient' defined in class path resource [org/springframework/cloud/consul/ConsulAutoConfiguration.class]: Unsatisfied dependency expressed through method 'consulClient' parameter 0: Error creating bean with name 'consulProperties': Could not bind properties to 'ConsulProperties' : prefix=spring.cloud.consul, ignoreInvalidFields=false, ignoreUnknownFields=true -2025-11-27 19:06:44.169 [main] INFO [] o.s.b.a.l.ConditionEvaluationReportLogger - - -Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. -2025-11-27 19:06:44.181 [main] ERROR [] o.s.b.d.LoggingFailureAnalysisReporter - - -*************************** -APPLICATION FAILED TO START -*************************** - -Description: - -Failed to bind properties under 'spring.cloud.consul.port' to int: - - Property: spring.cloud.consul.port - Value: "${CONSUL_PORT:8500}" - Origin: class path resource [application.yml] - 22:13 - Reason: failed to convert java.lang.String to @jakarta.validation.constraints.NotNull int (caused by java.lang.NumberFormatException: For input string: "8500:8500") - -Action: - -Update your application's configuration - -2025-11-27 19:07:39.516 [background-preinit] INFO [] o.h.validator.internal.util.Version - HV000001: Hibernate Validator 8.0.3.Final -2025-11-27 19:07:39.548 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - Starting GatewayApplicationKt using Java 21.0.9 with PID 62913 (/home/stefan-mo/WsMeldestelle/Meldestelle/infrastructure/gateway/build/classes/kotlin/main started by stefan-mo in /home/stefan-mo/WsMeldestelle/Meldestelle) -2025-11-27 19:07:39.548 [main] DEBUG [] a.m.i.gateway.GatewayApplicationKt - Running with Spring Boot v3.5.6, Spring v6.2.11 -2025-11-27 19:07:39.549 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - The following 1 profile is active: "dev" -2025-11-27 19:07:40.666 [main] INFO [] o.s.cloud.context.scope.GenericScope - BeanFactory id=4eb90187-1826-32ce-9dc7-fa80cb000915 -2025-11-27 19:07:42.677 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [After] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Before] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Between] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Cookie] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Header] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Host] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Method] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Path] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Query] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [ReadBody] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [RemoteAddr] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [XForwardedRemoteAddr] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [Weight] -2025-11-27 19:07:42.678 [main] INFO [] o.s.c.g.r.RouteDefinitionRouteLocator - Loaded RoutePredicateFactory [CloudFoundryRouteService] -2025-11-27 19:07:43.362 [main] INFO [] o.s.b.a.e.web.EndpointLinksResolver - Exposing 6 endpoints beneath base path '/actuator' -2025-11-27 19:07:43.989 [main] WARN [] o.s.c.l.c.LoadBalancerCacheAutoConfiguration$LoadBalancerCaffeineWarnLogger - Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath. -2025-11-27 19:07:44.087 [main] INFO [] o.s.b.w.e.netty.NettyWebServer - Netty started on port 8080 (http) -2025-11-27 19:07:44.090 [main] INFO [] o.s.c.c.s.ConsulServiceRegistry - Registering service with consul: NewService{id='meldestelle-8080-54f57945-6e65-4dd4-bb1d-ddec4966a68a', name='meldestelle', tags=[], address='10.0.0.18', meta={secure=false}, port=8080, enableTagOverride=null, check=Check{script='null', dockerContainerID='null', shell='null', interval='10s', ttl='null', http='http://10.0.0.18:8080/actuator/health', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null', grpc='null', grpcUseTLS=null}, checks=null} -2025-11-27 19:07:44.196 [main] INFO [] a.m.i.gateway.GatewayApplicationKt - Started GatewayApplicationKt in 4.979 seconds (process running for 5.524) -2025-11-27 19:07:48.007 [SpringApplicationShutdownHook] INFO [] o.s.b.w.e.netty.GracefulShutdown - Commencing graceful shutdown. Waiting for active requests to complete -2025-11-27 19:07:48.009 [netty-shutdown] INFO [] o.s.b.w.e.netty.GracefulShutdown - Graceful shutdown complete -2025-11-27 19:07:50.020 [SpringApplicationShutdownHook] INFO [] o.s.c.c.s.ConsulServiceRegistry - Deregistering service with consul: meldestelle-8080-54f57945-6e65-4dd4-bb1d-ddec4966a68a diff --git a/logs/troubleshooting/compose-config.txt b/logs/troubleshooting/compose-config.txt deleted file mode 100644 index 87a5bac3..00000000 --- a/logs/troubleshooting/compose-config.txt +++ /dev/null @@ -1,371 +0,0 @@ -# docker compose config output -name: meldestelle -services: - api-gateway: - build: - context: /home/stefan-mo/WsMeldestelle/Meldestelle - dockerfile: dockerfiles/infrastructure/gateway/Dockerfile - args: - BUILD_DATE: unknown - GRADLE_VERSION: 9.0.0 - JAVA_VERSION: "21" - SPRING_PROFILES_ACTIVE: default - VERSION: 1.0.0 - container_name: meldestelle-api-gateway - depends_on: - consul: - condition: service_healthy - required: true - keycloak: - condition: service_healthy - required: true - postgres: - condition: service_healthy - required: true - redis: - condition: service_healthy - required: true - environment: - CONSUL_ENABLED: "true" - CONSUL_HOST: consul - CONSUL_PORT: "8500" - GATEWAY_PORT: "8081" - GATEWAY_SECURITY_KEYCLOAK_ENABLED: "false" - KEYCLOAK_CLIENT_ID: api-gateway - KEYCLOAK_ISSUER_URI: http://keycloak:8080/realms/meldestelle - KEYCLOAK_JWK_SET_URI: http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs - KEYCLOAK_REALM: meldestelle - KEYCLOAK_SERVER_URL: http://keycloak:8080 - SPRING_PROFILES_ACTIVE: dev,keycloak - healthcheck: - test: - - CMD - - curl - - --fail - - http://localhost:8081/actuator/health - timeout: 5s - interval: 15s - retries: 3 - start_period: 30s - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 8081 - published: "8081" - protocol: tcp - restart: unless-stopped - volumes: - - type: volume - source: api-gateway-gradle-cache - target: /home/gradle/.gradle - volume: {} - consul: - command: - - agent - - -server - - -ui - - -node=server-1 - - -bootstrap-expect=1 - - -client=0.0.0.0 - container_name: meldestelle-consul - healthcheck: - test: - - CMD - - curl - - -f - - http://localhost:8500/v1/status/leader - timeout: 5s - interval: 10s - retries: 3 - start_period: 20s - image: hashicorp/consul:1.15 - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 8500 - published: "8500" - protocol: tcp - restart: unless-stopped - grafana: - container_name: meldestelle-grafana - depends_on: - prometheus: - condition: service_started - required: true - environment: - GF_INSTALL_PLUGINS: grafana-piechart-panel - GF_SECURITY_ADMIN_PASSWORD: admin - GF_SECURITY_ADMIN_USER: admin - GF_USERS_ALLOW_SIGN_UP: "false" - healthcheck: - test: - - CMD - - curl - - --fail - - http://localhost:3000/api/health - timeout: 5s - interval: 10s - retries: 3 - start_period: 20s - image: grafana/grafana:11.3.0 - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 3000 - published: "3000" - protocol: tcp - restart: unless-stopped - volumes: - - type: volume - source: grafana-data - target: /var/lib/grafana - volume: {} - - type: bind - source: /home/stefan-mo/WsMeldestelle/Meldestelle/docker/monitoring/grafana - target: /etc/grafana/provisioning - read_only: true - bind: - create_host_path: true - kafka: - container_name: meldestelle-kafka - depends_on: - zookeeper: - condition: service_healthy - required: true - environment: - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 - KAFKA_BROKER_ID: "1" - KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - healthcheck: - test: - - CMD - - kafka-broker-api-versions - - --bootstrap-server - - localhost:9092 - timeout: 5s - interval: 10s - retries: 3 - start_period: 20s - image: confluentinc/cp-kafka:7.4.0 - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 9092 - published: "9092" - protocol: tcp - restart: unless-stopped - keycloak: - command: - - start-dev - - --import-realm - container_name: meldestelle-keycloak - depends_on: - postgres: - condition: service_healthy - required: true - environment: - JAVA_OPTS_APPEND: -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+DisableExplicitGC -Djava.net.preferIPv4Stack=true -Duser.timezone=Europe/Vienna - KC_CACHE: ispn - KC_DB: postgres - KC_DB_PASSWORD: meldestelle - KC_DB_POOL_INITIAL_SIZE: "5" - KC_DB_POOL_MAX_SIZE: "20" - KC_DB_POOL_MIN_SIZE: "5" - KC_DB_SCHEMA: keycloak - KC_DB_URL: jdbc:postgresql://postgres:5432/meldestelle - KC_DB_USERNAME: meldestelle - KC_HEALTH_ENABLED: "true" - KC_HOSTNAME_STRICT: "false" - KC_HTTP_ENABLED: "true" - KC_HTTP_PORT: "8080" - KC_LOG_CONSOLE_COLOR: "false" - KC_LOG_CONSOLE_FORMAT: plain - KC_LOG_LEVEL: info - KC_METRICS_ENABLED: "true" - KC_PROXY_HEADERS: xforwarded - KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: admin - healthcheck: - test: - - CMD-SHELL - - 'if command -v curl >/dev/null 2>&1; then curl -fsS http://localhost:8080/health/ready | grep -q "\"status\":\"UP\""; elif command -v wget >/dev/null 2>&1; then wget -q -O - http://localhost:8080/health/ready | grep -q "\"status\":\"UP\""; else echo "Healthcheck: curl/wget not found, using bash /dev/tcp fallback" >&2; timeout 25 bash -lc "exec 3<>/dev/tcp/127.0.0.1/8080 && printf \"GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\" >&3 && head -n 1 <&3 | grep -q \"200 OK\""; fi' - timeout: 30s - interval: 15s - retries: 10 - start_period: 3m0s - image: quay.io/keycloak/keycloak:26.4.0 - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 8080 - published: "8180" - protocol: tcp - restart: unless-stopped - volumes: - - type: bind - source: /home/stefan-mo/WsMeldestelle/Meldestelle/docker/services/keycloak - target: /opt/keycloak/data/import - bind: - create_host_path: true - - type: volume - source: keycloak-data - target: /opt/keycloak/data - volume: {} - postgres: - container_name: meldestelle-postgres - environment: - POSTGRES_DB: meldestelle - POSTGRES_PASSWORD: meldestelle - POSTGRES_USER: meldestelle - healthcheck: - test: - - CMD-SHELL - - pg_isready -U meldestelle -d meldestelle - timeout: 5s - interval: 10s - retries: 3 - start_period: 20s - image: postgres:16-alpine - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 5432 - published: "5432" - protocol: tcp - restart: unless-stopped - volumes: - - type: volume - source: postgres-data - target: /var/lib/postgresql/data - volume: {} - - type: bind - source: /home/stefan-mo/WsMeldestelle/Meldestelle/docker/services/postgres - target: /docker-entrypoint-initdb.d - bind: - create_host_path: true - prometheus: - command: - - --config.file=/etc/prometheus/prometheus.yml - - --storage.tsdb.path=/prometheus - - --web.console.libraries=/etc/prometheus/console_libraries - - --web.console.templates=/etc/prometheus/consoles - - --storage.tsdb.retention.time=200h - - --web.enable-lifecycle - container_name: meldestelle-prometheus - healthcheck: - test: - - CMD - - wget - - --no-verbose - - --tries=1 - - --spider - - http://localhost:9090/-/healthy - timeout: 5s - interval: 10s - retries: 3 - start_period: 20s - image: prom/prometheus:v2.54.1 - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 9090 - published: "9090" - protocol: tcp - restart: unless-stopped - volumes: - - type: volume - source: prometheus-data - target: /prometheus - volume: {} - - type: bind - source: /home/stefan-mo/WsMeldestelle/Meldestelle/docker/monitoring/prometheus - target: /etc/prometheus - read_only: true - bind: - create_host_path: true - redis: - command: - - redis-server - - --appendonly - - "yes" - container_name: meldestelle-redis - healthcheck: - test: - - CMD - - redis-cli - - ping - timeout: 5s - interval: 10s - retries: 3 - start_period: 20s - image: redis:7-alpine - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 6379 - published: "6379" - protocol: tcp - restart: unless-stopped - volumes: - - type: volume - source: redis-data - target: /data - volume: {} - zookeeper: - container_name: meldestelle-zookeeper - environment: - ZOOKEEPER_CLIENT_PORT: "2181" - ZOOKEEPER_TICK_TIME: "2000" - healthcheck: - test: - - CMD - - bash - - -c - - echo 'ruok' | nc localhost 2181 - timeout: 5s - interval: 10s - retries: 3 - start_period: 20s - image: confluentinc/cp-zookeeper:7.4.0 - networks: - meldestelle-network: null - ports: - - mode: ingress - target: 2181 - published: "2181" - protocol: tcp - restart: unless-stopped -networks: - meldestelle-network: - name: meldestelle_meldestelle-network - driver: bridge -volumes: - api-gateway-gradle-cache: - name: meldestelle_api-gateway-gradle-cache - driver: local - grafana-data: - name: meldestelle_grafana-data - driver: local - keycloak-data: - name: meldestelle_keycloak-data - driver: local - postgres-data: - name: meldestelle_postgres-data - driver: local - prometheus-data: - name: meldestelle_prometheus-data - driver: local - redis-data: - name: meldestelle_redis-data - driver: local diff --git a/logs/troubleshooting/compose-ps.txt b/logs/troubleshooting/compose-ps.txt deleted file mode 100644 index 34fa3c21..00000000 --- a/logs/troubleshooting/compose-ps.txt +++ /dev/null @@ -1,3 +0,0 @@ -NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS -meldestelle-keycloak quay.io/keycloak/keycloak:26.4.0 "/opt/keycloak/bin/k…" keycloak 1 second ago Up Less than a second (health: starting) 8443/tcp, 9000/tcp, 0.0.0.0:8180->8080/tcp, [::]:8180->8080/tcp -meldestelle-postgres postgres:16-alpine "docker-entrypoint.s…" postgres 8 seconds ago Up 7 seconds (healthy) 0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp diff --git a/logs/troubleshooting/keycloak.log b/logs/troubleshooting/keycloak.log deleted file mode 100644 index 21b207b5..00000000 --- a/logs/troubleshooting/keycloak.log +++ /dev/null @@ -1 +0,0 @@ -Appending additional Java properties to JAVA_OPTS diff --git a/logs/troubleshooting/postgres.log b/logs/troubleshooting/postgres.log deleted file mode 100644 index c9801f02..00000000 --- a/logs/troubleshooting/postgres.log +++ /dev/null @@ -1,74 +0,0 @@ -The files belonging to this database system will be owned by user "postgres". -This user must also own the server process. - -The database cluster will be initialized with locale "en_US.utf8". -The default database encoding has accordingly been set to "UTF8". -The default text search configuration will be set to "english". - -Data page checksums are disabled. - -fixing permissions on existing directory /var/lib/postgresql/data ... ok -creating subdirectories ... ok -selecting dynamic shared memory implementation ... posix -selecting default max_connections ... 100 -selecting default shared_buffers ... 128MB -selecting default time zone ... UTC -creating configuration files ... ok -running bootstrap script ... ok -sh: locale: not found -2025-10-05 16:36:24.554 UTC [35] WARNING: no usable system locales were found -performing post-bootstrap initialization ... ok -initdb: warning: enabling "trust" authentication for local connections -initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb. -syncing data to disk ... ok - - -Success. You can now start the database server using: - - pg_ctl -D /var/lib/postgresql/data -l logfile start - -waiting for server to start....2025-10-05 16:36:26.874 UTC [41] LOG: starting PostgreSQL 16.10 on x86_64-pc-linux-musl, compiled by gcc (Alpine 14.2.0) 14.2.0, 64-bit -2025-10-05 16:36:26.877 UTC [41] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" -2025-10-05 16:36:26.888 UTC [44] LOG: database system was shut down at 2025-10-05 16:36:24 UTC -2025-10-05 16:36:26.895 UTC [41] LOG: database system is ready to accept connections - done -server started -CREATE DATABASE - - -/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/01-init-keycloak-schema.sql -CREATE SCHEMA -GRANT -GRANT -ALTER DEFAULT PRIVILEGES -ALTER DEFAULT PRIVILEGES -ALTER DEFAULT PRIVILEGES -psql:/docker-entrypoint-initdb.d/01-init-keycloak-schema.sql:31: NOTICE: Keycloak schema created successfully -psql:/docker-entrypoint-initdb.d/01-init-keycloak-schema.sql:31: NOTICE: Schema: keycloak -psql:/docker-entrypoint-initdb.d/01-init-keycloak-schema.sql:31: NOTICE: Owner: meldestelle -DO - - -/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/02-init-keycloak-schema.sql -psql:/docker-entrypoint-initdb.d/02-init-keycloak-schema.sql:11: NOTICE: 02-init-keycloak-schema.sql is a no-op (handled by 01-init-keycloak-schema.sql) -DO - - -waiting for server to shut down....2025-10-05 16:36:27.075 UTC [41] LOG: received fast shutdown request -2025-10-05 16:36:27.078 UTC [41] LOG: aborting any active transactions -2025-10-05 16:36:27.081 UTC [41] LOG: background worker "logical replication launcher" (PID 47) exited with exit code 1 -2025-10-05 16:36:27.081 UTC [42] LOG: shutting down -2025-10-05 16:36:27.084 UTC [42] LOG: checkpoint starting: shutdown immediate -2025-10-05 16:36:27.671 UTC [42] LOG: checkpoint complete: wrote 929 buffers (5.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.022 s, sync=0.550 s, total=0.590 s; sync files=301, longest=0.003 s, average=0.002 s; distance=4275 kB, estimate=4275 kB; lsn=0/191F358, redo lsn=0/191F358 -2025-10-05 16:36:27.682 UTC [41] LOG: database system is shut down - done -server stopped - -PostgreSQL init process complete; ready for start up. - -2025-10-05 16:36:27.805 UTC [1] LOG: starting PostgreSQL 16.10 on x86_64-pc-linux-musl, compiled by gcc (Alpine 14.2.0) 14.2.0, 64-bit -2025-10-05 16:36:27.805 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 -2025-10-05 16:36:27.805 UTC [1] LOG: listening on IPv6 address "::", port 5432 -2025-10-05 16:36:27.811 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" -2025-10-05 16:36:27.818 UTC [61] LOG: database system was shut down at 2025-10-05 16:36:27 UTC -2025-10-05 16:36:27.826 UTC [1] LOG: database system is ready to accept connections diff --git a/scripts/config-sync.sh b/scripts/config-sync.sh deleted file mode 100755 index b4bd832e..00000000 --- a/scripts/config-sync.sh +++ /dev/null @@ -1,614 +0,0 @@ -#!/bin/bash - -# =================================================================== -# Configuration Synchronization Utility -# Syncs config/central.toml to all dependent configuration files -# Eliminates redundancy across 38+ port definitions and 72+ Spring profiles -# =================================================================== - -set -euo pipefail - -# Script directory and project root -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -CENTRAL_CONFIG="$PROJECT_ROOT/config/central.toml" - -# Load common utilities -# shellcheck source=utils/common.sh -source "$SCRIPT_DIR/utils/common.sh" || { - echo "Error: Could not load common utilities" - exit 1 -} - -# =================================================================== -# TOML Parser Functions -# =================================================================== - -# Function to extract value from TOML file -get_config_value() { - local section=$1 - local key=$2 - local config_file=${3:-$CENTRAL_CONFIG} - - # Handle nested sections like [ports] or [spring-profiles.defaults] - if [[ "$section" == *.* ]]; then - # Split nested section - local main_section="${section%%.*}" - local subsection="${section#*.}" - - # Extract from nested section - awk -v main="$main_section" -v subsec="$subsection" -v key="$key" ' - BEGIN { in_main = 0; in_subsec = 0 } - /^\[/ && !/^\['"$main_section"'/ && !/^\['"$main_section"'\./ { in_main = 0; in_subsec = 0 } - $0 ~ "^\\[" main "\\]$" { in_main = 1; in_subsec = 0; next } - $0 ~ "^\\[" main "\\." subsec "\\]$" { in_main = 1; in_subsec = 1; next } - in_subsec && $0 ~ "^" key " *= *" { - gsub(/^[^=]*= *"?/, ""); gsub(/"$/, ""); print; exit - } - ' "$config_file" - else - # Extract from simple section - awk -v section="$section" -v key="$key" ' - BEGIN { in_section = 0 } - /^\[/ { in_section = 0 } - $0 ~ "^\\[" section "\\]$" { in_section = 1; next } - in_section && $0 ~ "^" key " *= *" { - gsub(/^[^=]*= *"?/, ""); gsub(/"$/, ""); print; exit - } - ' "$config_file" - fi -} - -# Function to get all keys from a TOML section -get_section_keys() { - local section=$1 - local config_file=${2:-$CENTRAL_CONFIG} - - awk -v section="$section" ' - BEGIN { in_section = 0 } - /^\[/ { in_section = 0 } - $0 ~ "^\\[" section "\\]$" { in_section = 1; next } - in_section && /^[a-zA-Z0-9_-]+ *= *.*$/ { - match($0, /^[a-zA-Z0-9_-]+/); print substr($0, RSTART, RLENGTH) - } - ' "$config_file" -} - -# =================================================================== -# Synchronization Functions -# =================================================================== - -# Function to sync gradle.properties -sync_gradle_properties() { - log_section "Syncing gradle.properties" - - local gradle_file="$PROJECT_ROOT/gradle.properties" - local backup_file="${gradle_file}.bak.$(date +%Y%m%d_%H%M%S)" - - # Create backup - cp "$gradle_file" "$backup_file" - log_info "Created backup: $(basename "$backup_file")" - - # Extract port values from central config - local gateway_port=$(get_config_value "ports" "api-gateway") - local consul_port=$(get_config_value "ports" "consul") - local ping_port=$(get_config_value "ports" "ping-service") - local members_port=$(get_config_value "ports" "members-service") - local horses_port=$(get_config_value "ports" "horses-service") - local events_port=$(get_config_value "ports" "events-service") - - # Update gradle.properties with centralized values - sed -i.tmp \ - -e "s/^infrastructure\.gateway\.port=.*/infrastructure.gateway.port=${gateway_port}/" \ - -e "s/^infrastructure\.consul\.port=.*/infrastructure.consul.port=${consul_port}/" \ - -e "s/^services\.port\.start=.*/services.port.start=${ping_port}/" \ - -e "s/^services\.port\.ping=.*/services.port.ping=${ping_port}/" \ - -e "s/^services\.port\.members=.*/services.port.members=${members_port}/" \ - -e "s/^services\.port\.horses=.*/services.port.horses=${horses_port}/" \ - -e "s/^services\.port\.events=.*/services.port.events=${events_port}/" \ - "$gradle_file" - - rm -f "${gradle_file}.tmp" - log_success "Updated gradle.properties with centralized ports" -} - -# Function to sync Docker Compose files -sync_docker_compose_files() { - log_section "Syncing Docker Compose files" - - local compose_files=( - "$PROJECT_ROOT/docker-compose.yml" - "$PROJECT_ROOT/docker-compose.services.yml" - "$PROJECT_ROOT/docker-compose.clients.yml" - ) - - # Extract values from central config - local gateway_port=$(get_config_value "ports" "api-gateway") - local ping_port=$(get_config_value "ports" "ping-service") - local members_port=$(get_config_value "ports" "members-service") - local horses_port=$(get_config_value "ports" "horses-service") - local events_port=$(get_config_value "ports" "events-service") - local masterdata_port=$(get_config_value "ports" "masterdata-service") - local auth_port=$(get_config_value "ports" "auth-server") - local consul_port=$(get_config_value "ports" "consul") - local redis_port=$(get_config_value "ports" "redis") - local postgres_port=$(get_config_value "ports" "postgres") - local prometheus_port=$(get_config_value "ports" "prometheus") - local grafana_port=$(get_config_value "ports" "grafana") - local keycloak_port=$(get_config_value "ports" "keycloak") - local web_app_port=$(get_config_value "ports" "web-app") - - # Extract Spring profiles - local infrastructure_profile=$(get_config_value "spring-profiles.defaults" "infrastructure") - local services_profile=$(get_config_value "spring-profiles.defaults" "services") - local clients_profile=$(get_config_value "spring-profiles.defaults" "clients") - - for compose_file in "${compose_files[@]}"; do - if [[ -f "$compose_file" ]]; then - local backup_file="${compose_file}.bak.$(date +%Y%m%d_%H%M%S)" - cp "$compose_file" "$backup_file" - log_info "Created backup: $(basename "$backup_file")" - - # Update port references - sed -i.tmp \ - -e "s/\${GATEWAY_PORT:-[0-9]*}/\${GATEWAY_PORT:-${gateway_port}}/g" \ - -e "s/\${PING_SERVICE_PORT:-[0-9]*}/\${PING_SERVICE_PORT:-${ping_port}}/g" \ - -e "s/\${MEMBERS_SERVICE_PORT:-[0-9]*}/\${MEMBERS_SERVICE_PORT:-${members_port}}/g" \ - -e "s/\${HORSES_SERVICE_PORT:-[0-9]*}/\${HORSES_SERVICE_PORT:-${horses_port}}/g" \ - -e "s/\${EVENTS_SERVICE_PORT:-[0-9]*}/\${EVENTS_SERVICE_PORT:-${events_port}}/g" \ - -e "s/\${MASTERDATA_SERVICE_PORT:-[0-9]*}/\${MASTERDATA_SERVICE_PORT:-${masterdata_port}}/g" \ - -e "s/\${AUTH_SERVICE_PORT:-[0-9]*}/\${AUTH_SERVICE_PORT:-${auth_port}}/g" \ - -e "s/\${CONSUL_PORT:-[0-9]*}/\${CONSUL_PORT:-${consul_port}}/g" \ - -e "s/\${REDIS_PORT:-[0-9]*}/\${REDIS_PORT:-${redis_port}}/g" \ - -e "s/\${PROMETHEUS_PORT:-[0-9]*}/\${PROMETHEUS_PORT:-${prometheus_port}}/g" \ - -e "s/\${GRAFANA_PORT:-[0-9]*}/\${GRAFANA_PORT:-${grafana_port}}/g" \ - -e "s/:[0-9]*\":${postgres_port}/:${postgres_port}:${postgres_port}/g" \ - -e "s/:[0-9]*\":${redis_port}/:${redis_port}:${redis_port}/g" \ - -e "s/\${DOCKER_SPRING_PROFILES_DEFAULT:-[^}]*}/\${DOCKER_SPRING_PROFILES_DEFAULT:-${infrastructure_profile}}/g" \ - -e "s/\${DOCKER_SPRING_PROFILES_DOCKER:-[^}]*}/\${DOCKER_SPRING_PROFILES_DOCKER:-${services_profile}}/g" \ - "$compose_file" - - rm -f "${compose_file}.tmp" - log_success "Updated $(basename "$compose_file")" - else - log_warning "File not found: $(basename "$compose_file")" - fi - done -} - -# Function to sync environment files -sync_environment_files() { - log_section "Syncing Environment Files" - - local env_template="$PROJECT_ROOT/config/.env.template" - - if [[ -f "$env_template" ]]; then - local backup_file="${env_template}.bak.$(date +%Y%m%d_%H%M%S)" - cp "$env_template" "$backup_file" - log_info "Created backup: $(basename "$backup_file")" - - # Extract all port values - local gateway_port=$(get_config_value "ports" "api-gateway") - local ping_port=$(get_config_value "ports" "ping-service") - local members_port=$(get_config_value "ports" "members-service") - local horses_port=$(get_config_value "ports" "horses-service") - local events_port=$(get_config_value "ports" "events-service") - local masterdata_port=$(get_config_value "ports" "masterdata-service") - local auth_port=$(get_config_value "ports" "auth-server") - local consul_port=$(get_config_value "ports" "consul") - local redis_port=$(get_config_value "ports" "redis") - local postgres_port=$(get_config_value "ports" "postgres") - local prometheus_port=$(get_config_value "ports" "prometheus") - local grafana_port=$(get_config_value "ports" "grafana") - - # Update .env.template with centralized values - sed -i.tmp \ - -e "s/^GATEWAY_PORT=.*/GATEWAY_PORT=${gateway_port}/" \ - -e "s/^PING_SERVICE_PORT=.*/PING_SERVICE_PORT=${ping_port}/" \ - -e "s/^MEMBERS_SERVICE_PORT=.*/MEMBERS_SERVICE_PORT=${members_port}/" \ - -e "s/^HORSES_SERVICE_PORT=.*/HORSES_SERVICE_PORT=${horses_port}/" \ - -e "s/^EVENTS_SERVICE_PORT=.*/EVENTS_SERVICE_PORT=${events_port}/" \ - -e "s/^MASTERDATA_SERVICE_PORT=.*/MASTERDATA_SERVICE_PORT=${masterdata_port}/" \ - -e "s/^AUTH_SERVICE_PORT=.*/AUTH_SERVICE_PORT=${auth_port}/" \ - -e "s/^CONSUL_PORT=.*/CONSUL_PORT=${consul_port}/" \ - -e "s/^REDIS_PORT=.*/REDIS_PORT=${redis_port}/" \ - -e "s/^DB_PORT=.*/DB_PORT=${postgres_port}/" \ - -e "s/^PROMETHEUS_PORT=.*/PROMETHEUS_PORT=${prometheus_port}/" \ - -e "s/^GRAFANA_PORT=.*/GRAFANA_PORT=${grafana_port}/" \ - "$env_template" - - rm -f "${env_template}.tmp" - log_success "Updated .env.template" - else - log_warning ".env.template not found" - fi -} - -# Function to sync Docker build arguments -sync_docker_build_args() { - log_section "Syncing Docker Build Arguments" - - local build_args_dir="$PROJECT_ROOT/docker/build-args" - - # Extract Spring profiles from central config - local infrastructure_profile=$(get_config_value "spring-profiles.defaults" "infrastructure") - local services_profile=$(get_config_value "spring-profiles.defaults" "services") - local clients_profile=$(get_config_value "spring-profiles.defaults" "clients") - - # Update services.env - local services_env="$build_args_dir/services.env" - if [[ -f "$services_env" ]]; then - local backup_file="${services_env}.bak.$(date +%Y%m%d_%H%M%S)" - cp "$services_env" "$backup_file" - - # Extract port values - local ping_port=$(get_config_value "ports" "ping-service") - local members_port=$(get_config_value "ports" "members-service") - local horses_port=$(get_config_value "ports" "horses-service") - local events_port=$(get_config_value "ports" "events-service") - local masterdata_port=$(get_config_value "ports" "masterdata-service") - - sed -i.tmp \ - -e "s/^SPRING_PROFILES_ACTIVE=.*/SPRING_PROFILES_ACTIVE=${services_profile}/" \ - -e "s/^PING_SERVICE_PORT=.*/PING_SERVICE_PORT=${ping_port}/" \ - -e "s/^MEMBERS_SERVICE_PORT=.*/MEMBERS_SERVICE_PORT=${members_port}/" \ - -e "s/^HORSES_SERVICE_PORT=.*/HORSES_SERVICE_PORT=${horses_port}/" \ - -e "s/^EVENTS_SERVICE_PORT=.*/EVENTS_SERVICE_PORT=${events_port}/" \ - -e "s/^MASTERDATA_SERVICE_PORT=.*/MASTERDATA_SERVICE_PORT=${masterdata_port}/" \ - "$services_env" - - rm -f "${services_env}.tmp" - log_success "Updated services.env" - fi - - # Update infrastructure.env - local infrastructure_env="$build_args_dir/infrastructure.env" - if [[ -f "$infrastructure_env" ]]; then - local backup_file="${infrastructure_env}.bak.$(date +%Y%m%d_%H%M%S)" - cp "$infrastructure_env" "$backup_file" - - # Extract port values - local gateway_port=$(get_config_value "ports" "api-gateway") - local auth_port=$(get_config_value "ports" "auth-server") - local monitoring_port=$(get_config_value "ports" "monitoring-server") - local consul_port=$(get_config_value "ports" "consul") - - sed -i.tmp \ - -e "s/^SPRING_PROFILES_ACTIVE=.*/SPRING_PROFILES_ACTIVE=${infrastructure_profile}/" \ - -e "s/^GATEWAY_PORT=.*/GATEWAY_PORT=${gateway_port}/" \ - -e "s/^AUTH_SERVER_PORT=.*/AUTH_SERVER_PORT=${auth_port}/" \ - -e "s/^MONITORING_SERVER_PORT=.*/MONITORING_SERVER_PORT=${monitoring_port}/" \ - -e "s/^CONSUL_PORT=.*/CONSUL_PORT=${consul_port}/" \ - "$infrastructure_env" - - rm -f "${infrastructure_env}.tmp" - log_success "Updated infrastructure.env" - fi - - # Update clients.env - local clients_env="$build_args_dir/clients.env" - if [[ -f "$clients_env" ]]; then - local backup_file="${clients_env}.bak.$(date +%Y%m%d_%H%M%S)" - cp "$clients_env" "$backup_file" - - # Extract port values - local web_app_port=$(get_config_value "ports" "web-app") - local vnc_port=$(get_config_value "ports" "desktop-app-vnc") - local novnc_port=$(get_config_value "ports" "desktop-app-novnc") - - sed -i.tmp \ - -e "s/^WEB_APP_PORT=.*/WEB_APP_PORT=${web_app_port}/" \ - -e "s/^DESKTOP_APP_VNC_PORT=.*/DESKTOP_APP_VNC_PORT=${vnc_port}/" \ - -e "s/^DESKTOP_APP_NOVNC_PORT=.*/DESKTOP_APP_NOVNC_PORT=${novnc_port}/" \ - "$clients_env" - - rm -f "${clients_env}.tmp" - log_success "Updated clients.env" - fi -} - -# Function to sync monitoring configuration -sync_monitoring_config() { - log_section "Syncing Monitoring Configuration" - - local prometheus_config="$PROJECT_ROOT/config/monitoring/prometheus.dev.yml" - - if [[ -f "$prometheus_config" ]]; then - local backup_file="${prometheus_config}.bak.$(date +%Y%m%d_%H%M%S)" - cp "$prometheus_config" "$backup_file" - log_info "Created backup: $(basename "$backup_file")" - - # Extract service ports - local ping_port=$(get_config_value "ports" "ping-service") - local members_port=$(get_config_value "ports" "members-service") - local horses_port=$(get_config_value "ports" "horses-service") - local events_port=$(get_config_value "ports" "events-service") - local masterdata_port=$(get_config_value "ports" "masterdata-service") - local gateway_port=$(get_config_value "ports" "api-gateway") - - # Update Prometheus targets with centralized ports - sed -i.tmp \ - -e "s/ping-service:[0-9]*/ping-service:${ping_port}/g" \ - -e "s/members-service:[0-9]*/members-service:${members_port}/g" \ - -e "s/horses-service:[0-9]*/horses-service:${horses_port}/g" \ - -e "s/events-service:[0-9]*/events-service:${events_port}/g" \ - -e "s/masterdata-service:[0-9]*/masterdata-service:${masterdata_port}/g" \ - -e "s/api-gateway:[0-9]*/api-gateway:${gateway_port}/g" \ - "$prometheus_config" - - rm -f "${prometheus_config}.tmp" - log_success "Updated Prometheus configuration" - else - log_warning "Prometheus config not found: $prometheus_config" - fi -} - -# Function to sync test scripts -sync_test_scripts() { - log_section "Syncing Test Scripts" - - local test_scripts=( - "$PROJECT_ROOT/scripts/test/integration-test.sh" - "$PROJECT_ROOT/scripts/test/test_gateway.sh" - "$PROJECT_ROOT/scripts/test/test-monitoring.sh" - ) - - # Extract port values - local ping_port=$(get_config_value "ports" "ping-service") - local gateway_port=$(get_config_value "ports" "api-gateway") - local consul_port=$(get_config_value "ports" "consul") - local prometheus_port=$(get_config_value "ports" "prometheus") - local grafana_port=$(get_config_value "ports" "grafana") - - for script_file in "${test_scripts[@]}"; do - if [[ -f "$script_file" ]]; then - local backup_file="${script_file}.bak.$(date +%Y%m%d_%H%M%S)" - cp "$script_file" "$backup_file" - - # Update port references in test scripts - sed -i.tmp \ - -e "s/:${ping_port}[^0-9]/:${ping_port}/g" \ - -e "s/localhost:[0-9]*\/actuator/localhost:${ping_port}\/actuator/g" \ - -e "s/ping-service:[0-9]*/ping-service:${ping_port}/g" \ - -e "s/api-gateway:[0-9]*/api-gateway:${gateway_port}/g" \ - -e "s/consul:[0-9]*/consul:${consul_port}/g" \ - -e "s/prometheus:[0-9]*/prometheus:${prometheus_port}/g" \ - -e "s/grafana:[0-9]*/grafana:${grafana_port}/g" \ - "$script_file" - - rm -f "${script_file}.tmp" - log_success "Updated $(basename "$script_file")" - else - log_warning "Test script not found: $(basename "$script_file")" - fi - done -} - -# =================================================================== -# Validation Functions -# =================================================================== - -# Function to validate central configuration -validate_central_config() { - log_section "Validating Central Configuration" - - if [[ ! -f "$CENTRAL_CONFIG" ]]; then - log_error "Central configuration file not found: $CENTRAL_CONFIG" - return 1 - fi - - log_info "Validating TOML syntax..." - - # Basic TOML validation (check for common syntax errors) - local validation_errors=0 - - # Check for unclosed brackets - if ! awk '/^\[.*[^]]$/ { print "Unclosed bracket on line " NR ": " $0; exit 1 }' "$CENTRAL_CONFIG"; then - ((validation_errors++)) - fi - - # Check for duplicate sections - local duplicate_sections=$(awk '/^\[.*\]$/ { - section = $0 - count[section]++ - } - END { - for (s in count) { - if (count[s] > 1) - print s - } - }' "$CENTRAL_CONFIG") - if [[ -n "$duplicate_sections" ]]; then - log_warning "Duplicate sections found: $duplicate_sections" - ((validation_errors++)) - fi - - if [[ $validation_errors -eq 0 ]]; then - log_success "Central configuration is valid" - return 0 - else - log_error "Central configuration has $validation_errors validation errors" - return 1 - fi -} - -# Function to show current configuration status -show_config_status() { - log_section "Configuration Status Report" - - log_info "Current port assignments from central config:" - local services=("ping-service" "members-service" "horses-service" "events-service" "masterdata-service" "api-gateway" "auth-server") - - for service in "${services[@]}"; do - local port=$(get_config_value "ports" "$service") - echo " ${service}: ${port}" - done - - log_info "Current Spring profile defaults:" - local infrastructure_profile=$(get_config_value "spring-profiles.defaults" "infrastructure") - local services_profile=$(get_config_value "spring-profiles.defaults" "services") - local clients_profile=$(get_config_value "spring-profiles.defaults" "clients") - - echo " Infrastructure: ${infrastructure_profile}" - echo " Services: ${services_profile}" - echo " Clients: ${clients_profile}" -} - -# =================================================================== -# Main Functions -# =================================================================== - -# Function to perform full synchronization -sync_all() { - log_section "Full Configuration Synchronization" - log_info "Syncing all configuration files from central.toml..." - - # Validate central configuration first - validate_central_config || return 1 - - # Perform all synchronizations - sync_gradle_properties || return 1 - sync_docker_compose_files || return 1 - sync_environment_files || return 1 - sync_docker_build_args || return 1 - sync_monitoring_config || return 1 - sync_test_scripts || return 1 - - log_success "All configuration files synchronized successfully!" - show_config_status -} - -# Function to show help -show_help() { - cat << EOF -Configuration Synchronization Utility - -USAGE: - $0 [COMMAND] [OPTIONS] - -COMMANDS: - sync Synchronize all configuration files - validate Validate central configuration file - status Show current configuration status - gradle Sync gradle.properties only - compose Sync Docker Compose files only - env Sync environment files only - docker-args Sync Docker build arguments only - monitoring Sync monitoring configuration only - tests Sync test scripts only - -OPTIONS: - -h, --help Show this help message - -v, --verbose Enable verbose output - --dry-run Show what would be changed without making changes - -EXAMPLES: - $0 sync # Sync all configuration files - $0 validate # Validate central.toml syntax - $0 status # Show current port and profile assignments - $0 gradle # Sync gradle.properties only - -This script reads from config/central.toml and updates all dependent -configuration files to eliminate redundancy across 38+ port definitions -and 72+ Spring profile configurations. - -Configuration files that will be synchronized: - - gradle.properties - - docker-compose*.yml files - - config/.env.template - - docker/build-args/*.env files - - config/monitoring/*.yml files - - scripts/test/*.sh files - -All original files are backed up before modification. -EOF -} - -# Main execution function -main() { - local command="${1:-sync}" - local verbose=false - local dry_run=false - - # Parse options - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - show_help - exit 0 - ;; - -v|--verbose) - verbose=true - shift - ;; - --dry-run) - dry_run=true - shift - ;; - -*) - log_error "Unknown option: $1" - show_help - exit 1 - ;; - *) - command="$1" - shift - ;; - esac - done - - # Set verbose mode - if [[ "$verbose" == "true" ]]; then - set -x - fi - - # Handle dry run - if [[ "$dry_run" == "true" ]]; then - log_warning "DRY RUN MODE - No files will be modified" - # In a real implementation, you would add dry-run logic here - fi - - # Change to project root - cd "$PROJECT_ROOT" - - # Execute command - case "$command" in - "sync"|"all") - sync_all - ;; - "validate") - validate_central_config - ;; - "status") - show_config_status - ;; - "gradle") - validate_central_config && sync_gradle_properties - ;; - "compose") - validate_central_config && sync_docker_compose_files - ;; - "env") - validate_central_config && sync_environment_files - ;; - "docker-args") - validate_central_config && sync_docker_build_args - ;; - "monitoring") - validate_central_config && sync_monitoring_config - ;; - "tests") - validate_central_config && sync_test_scripts - ;; - *) - log_error "Unknown command: $command" - show_help - exit 1 - ;; - esac - - log_success "Configuration synchronization completed!" -} - -# Run main function with all arguments -main "$@" diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh deleted file mode 100755 index a152a87c..00000000 --- a/scripts/docker-build.sh +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/bash -# =================================================================== -# Docker Build Script with Centralized Version Management -# Supports two modes: -# - compat (default): load docker/build-args/*.env (current behavior) -# - envless: parse docker/versions.toml directly and export DOCKER_* vars -# =================================================================== - -set -e - -# Script directory and project root -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -DOCKER_DIR="$PROJECT_ROOT/docker" -BUILD_ARGS_DIR="$DOCKER_DIR/build-args" -VERSIONS_TOML="$DOCKER_DIR/versions.toml" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to print colored output -print_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -print_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# --- Helpers to read versions.toml directly (POSIX-friendly) --- -get_version() { - local key=$1 - awk -v k="$key" ' - /^\[versions\]/ { in_section=1; next } - /^\[/ { if (in_section) exit; in_section=0 } - in_section && $1 == k && $2 == "=" { v=$3; gsub(/"/ ,"", v); print v; exit } - ' "$VERSIONS_TOML" || true -} - -get_env_mappings() { - awk '/^\[environment-mapping\]/,/^\[/ { if (/^[a-zA-Z].*= /) { key=$1; val=$3; gsub(/"/,"",val); print key":"val } }' "$VERSIONS_TOML" || true -} - -# Function to load from versions.toml (env-less mode) -load_from_versions() { - if [[ ! -f "$VERSIONS_TOML" ]]; then - print_error "versions.toml not found at $VERSIONS_TOML" - exit 1 - fi - - print_info "Loading centralized versions directly from versions.toml (env-less mode)..." - - # Export BUILD_DATE if not already set - export BUILD_DATE=${BUILD_DATE:-$(date -u +'%Y-%m-%dT%H:%M:%SZ')} - - # Map all environment-mapping keys to DOCKER_* variables using [versions] values - while IFS=: read -r toml_key env_var; do - [[ -z "$toml_key" || -z "$env_var" ]] && continue - val=$(get_version "$toml_key") - if [[ -n "$val" ]]; then - export "$env_var"="$val" - fi - done < <(get_env_mappings) - - # Additional convenience exports used by compose build args - export DOCKER_GRADLE_VERSION="${DOCKER_GRADLE_VERSION:-$(get_version gradle)}" - export DOCKER_JAVA_VERSION="${DOCKER_JAVA_VERSION:-$(get_version java)}" - export DOCKER_NODE_VERSION="${DOCKER_NODE_VERSION:-$(get_version node)}" - export DOCKER_NGINX_VERSION="${DOCKER_NGINX_VERSION:-$(get_version nginx)}" - - # Ensure DOCKER_APP_VERSION is derived from app-version - local app_ver - app_ver=$(get_version "app-version") - if [[ -n "$app_ver" ]]; then - export DOCKER_APP_VERSION="$app_ver" - fi - - # Backwards compatibility for scripts expecting plain names - export VERSION="${VERSION:-$app_ver}" - - print_success "versions.toml loaded; DOCKER_* variables exported." -} - -# Function to load environment files (compat mode) -load_env_files() { - print_info "Loading centralized Docker version environment files (compat mode)..." - - # Load global environment variables - if [[ -f "$BUILD_ARGS_DIR/global.env" ]]; then - # shellcheck disable=SC2046 - export $(grep -v '^#' "$BUILD_ARGS_DIR/global.env" | xargs) - print_info "✓ Loaded global.env" - else - print_error "Global environment file not found: $BUILD_ARGS_DIR/global.env" - exit 1 - fi - - # Load category-specific environment variables - for env_file in services.env clients.env infrastructure.env; do - if [[ -f "$BUILD_ARGS_DIR/$env_file" ]]; then - # shellcheck disable=SC2046 - export $(grep -v '^#' "$BUILD_ARGS_DIR/$env_file" | xargs) - print_info "✓ Loaded $env_file" - else - print_warning "Optional environment file not found: $BUILD_ARGS_DIR/$env_file" - fi - done - - # Set BUILD_DATE if not already set - export BUILD_DATE=${BUILD_DATE:-$(date -u +'%Y-%m-%dT%H:%M:%SZ')} - - # Map to Docker Compose environment variables - export DOCKER_GRADLE_VERSION="${GRADLE_VERSION}" - export DOCKER_JAVA_VERSION="${JAVA_VERSION}" - export DOCKER_NODE_VERSION="${NODE_VERSION}" - export DOCKER_NGINX_VERSION="${NGINX_VERSION}" - export DOCKER_APP_VERSION="${VERSION}" - export DOCKER_SPRING_PROFILES_DEFAULT="${SPRING_PROFILES_ACTIVE:-default}" - export DOCKER_SPRING_PROFILES_DOCKER="docker" - - print_success "All environment files loaded successfully!" -} - -# Function to show current versions -show_versions() { - print_info "Current centralized Docker versions:" - echo " Gradle Version: ${DOCKER_GRADLE_VERSION:-not set}" - echo " Java Version: ${DOCKER_JAVA_VERSION:-not set}" - echo " Node Version: ${DOCKER_NODE_VERSION:-not set}" - echo " Nginx Version: ${DOCKER_NGINX_VERSION:-not set}" - echo " App Version: ${DOCKER_APP_VERSION:-not set}" - echo " Build Date: ${BUILD_DATE:-not set}" - echo " Spring Profile (Default): ${DOCKER_SPRING_PROFILES_DEFAULT:-not set}" - echo " Spring Profile (Docker): ${DOCKER_SPRING_PROFILES_DOCKER:-not set}" -} - -# Function to build specific category -build_category() { - local category=$1 - local compose_file="" - - case $category in - "infrastructure") - compose_file="docker-compose.yml" - ;; - "services") - compose_file="docker-compose.yml -f docker-compose.services.yml" - ;; - "clients") - compose_file="docker-compose.yml -f docker-compose.clients.yml" - ;; - "all") - compose_file="docker-compose.yml -f docker-compose.services.yml -f docker-compose.clients.yml" - ;; - *) - print_error "Invalid category: $category" - print_info "Valid categories: infrastructure, services, clients, all" - exit 1 - ;; - esac - - print_info "Building $category with centralized versions..." - cd "$PROJECT_ROOT" - - if docker-compose -f $compose_file build; then - print_success "$category built successfully!" - else - print_error "Failed to build $category" - exit 1 - fi -} - -# Help function -show_help() { - echo "Docker Build Script with Centralized Version Management" - echo "" - echo "Usage: $0 [OPTIONS] [CATEGORY]" - echo "" - echo "Categories:" - echo " infrastructure Build infrastructure services (API Gateway)" - echo " services Build application services (ping-service, etc.)" - echo " clients Build client applications (web-app, desktop-app)" - echo " all Build everything" - echo "" - echo "Options:" - echo " -v, --versions Show current versions" - echo " -h, --help Show this help message" - echo "" - echo "Environment:" - echo " DOCKER_SSOT_MODE=envless|compat Default: compat" - echo "" - echo "Examples:" - echo " $0 services # Build all services" - echo " $0 clients # Build client applications" - echo " $0 all # Build everything" - echo " $0 --versions # Show current versions" - echo " DOCKER_SSOT_MODE=envless $0 --versions # Use versions.toml directly" -} - -# Main execution -main() { - local MODE="${DOCKER_SSOT_MODE:-compat}" - - # Parse command line arguments - case $1 in - -h|--help) - show_help - exit 0 - ;; - -v|--versions) - if [[ "$MODE" == "envless" ]]; then - load_from_versions - else - load_env_files - fi - show_versions - exit 0 - ;; - "") - print_error "No category specified" - show_help - exit 1 - ;; - *) - # Load environment and build - if [[ "$MODE" == "envless" ]]; then - load_from_versions - else - load_env_files - fi - show_versions - echo "" - build_category "$1" - ;; - esac -} - -# Run main function with all arguments -main "$@" diff --git a/scripts/docker-versions-update.sh b/scripts/docker-versions-update.sh deleted file mode 100755 index df0216f8..00000000 --- a/scripts/docker-versions-update.sh +++ /dev/null @@ -1,298 +0,0 @@ -#!/bin/bash -# =================================================================== -# Docker Versions Update Utility -# Updates central docker/versions.toml and syncs to environment files -# =================================================================== - -set -e - -# Script directory and project root -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -DOCKER_DIR="$PROJECT_ROOT/docker" -VERSIONS_TOML="$DOCKER_DIR/versions.toml" -BUILD_ARGS_DIR="$DOCKER_DIR/build-args" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to print colored output -print_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -print_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# Function to extract version from TOML file -get_version() { - local key=$1 - grep "^$key = " "$VERSIONS_TOML" | sed 's/.*= "\(.*\)"/\1/' || echo "" -} - -# Function to update version in TOML file -update_version() { - local key=$1 - local new_value=$2 - - if grep -q "^$key = " "$VERSIONS_TOML"; then - # Update existing key - sed -i.bak "s/^$key = .*/$key = \"$new_value\"/" "$VERSIONS_TOML" - print_success "Updated $key to $new_value" - else - print_error "Key $key not found in $VERSIONS_TOML" - return 1 - fi -} - -# Function to sync TOML to environment files -sync_to_env_files() { - print_info "Syncing versions.toml to environment files..." - - # Get current versions from TOML - # shellcheck disable=SC2155 - local gradle_version=$(get_version "gradle") - # shellcheck disable=SC2155 - local java_version=$(get_version "java") - # shellcheck disable=SC2155 - local node_version=$(get_version "node") - # shellcheck disable=SC2155 - local nginx_version=$(get_version "nginx") - # shellcheck disable=SC2155 - local app_version=$(get_version "app-version") - # shellcheck disable=SC2155 - local spring_default=$(get_version "spring-profiles-default") - # shellcheck disable=SC2155 - local spring_docker=$(get_version "spring-profiles-docker") - local prometheus_version=$(get_version "prometheus") - local grafana_version=$(get_version "grafana") - local keycloak_version=$(get_version "keycloak") - local postgres_version=$(get_version "postgres") - local redis_version=$(get_version "redis") - local consul_version=$(get_version "consul") - local zookeeper_version=$(get_version "zookeeper") - local kafka_version=$(get_version "kafka") - - # Update global.env (strictly build-time versions/tags) - cat > "$BUILD_ARGS_DIR/global.env" << EOF -# =================================================================== -# Global Docker Build Arguments - Used by all categories -# Source: docker/versions.toml -# Last updated: $(date -u +'%Y-%m-%d %H:%M:%S UTC') -# =================================================================== - -# --- Build Tools --- -GRADLE_VERSION=$gradle_version -JAVA_VERSION=$java_version - -# --- Build Metadata --- -VERSION=$app_version - -# --- Monitoring & Infrastructure Services (image tags) --- -PROMETHEUS_IMAGE_TAG=$prometheus_version -GRAFANA_IMAGE_TAG=$grafana_version -KEYCLOAK_IMAGE_TAG=$keycloak_version - -# --- Datastore Images (image tags) --- -POSTGRES_IMAGE_TAG=$postgres_version -REDIS_IMAGE_TAG=$redis_version - -# --- Additional Infrastructure Images (image tags) --- -CONSUL_IMAGE_TAG=$consul_version -ZOOKEEPER_IMAGE_TAG=$zookeeper_version -KAFKA_IMAGE_TAG=$kafka_version -EOF - print_success "Updated global.env" - - # Update clients.env (strictly build-time values; no runtime/dev vars) - cat > "$BUILD_ARGS_DIR/clients.env" << EOF -# =================================================================== -# Clients Docker Build Arguments - dockerfiles/clients/* -# Source: docker/versions.toml [categories.clients] -# Last updated: $(date -u +'%Y-%m-%d %H:%M:%S UTC') -# =================================================================== - -# --- Include Global Arguments --- -# Source global.env for GRADLE_VERSION, JAVA_VERSION, VERSION - -# --- Client-Specific Build Tools --- -NODE_VERSION=$node_version -NGINX_VERSION=$nginx_version - -# --- Client Build Configuration --- -CLIENT_PATH=client -CLIENT_MODULE=client -CLIENT_NAME=meldestelle-client -# Note: Runtime/Dev values moved to config/env/.env -# Keep this file strictly for build-time values only. -EOF - print_success "Updated clients.env" - - # Update services.env (strictly build-time values; no runtime vars) - cat > "$BUILD_ARGS_DIR/services.env" << EOF -# =================================================================== -# Services Docker Build Arguments - dockerfiles/services/* -# Source: docker/versions.toml [categories.services] -# Last updated: $(date -u +'%Y-%m-%d %H:%M:%S UTC') -# =================================================================== - -# --- Include Global Arguments --- -# Source global.env for GRADLE_VERSION, JAVA_VERSION, VERSION - -# --- Service-Specific Arguments --- -SERVICE_PATH=. -SERVICE_NAME=spring-boot-service -# Note: Runtime profiles/ports moved to config/env/.env -EOF - print_success "Updated services.env" - - # Update infrastructure.env (strictly build-time values; no runtime vars) - cat > "$BUILD_ARGS_DIR/infrastructure.env" << EOF -# =================================================================== -# Infrastructure Docker Build Arguments - dockerfiles/infrastructure/* -# Source: docker/versions.toml [categories.infrastructure] -# Last updated: $(date -u +'%Y-%m-%d %H:%M:%S UTC') -# =================================================================== - -# --- Include Global Arguments --- -# Source global.env for GRADLE_VERSION, JAVA_VERSION, VERSION - -# --- API Gateway Specific --- -GATEWAY_SERVICE_PATH=infrastructure/gateway -GATEWAY_SERVICE_NAME=api-gateway - -# --- Auth Server Specific --- -AUTH_SERVER_PATH=infrastructure/auth/auth-server -AUTH_SERVER_SERVICE_NAME=auth-server - -# --- Monitoring Server Specific --- -MONITORING_SERVER_PATH=infrastructure/monitoring/monitoring-server -MONITORING_SERVER_SERVICE_NAME=monitoring-server - -# Note: Runtime profiles/ports/dependencies moved to config/env/.env -EOF - print_success "Updated infrastructure.env" - - # --- Post-generation cleanup to enforce SSoT policies --- - # 1) Remove any accidental bare DOCKER_* placeholders from non-global envs - sed -i "/^DOCKER_[A-Z0-9_]\+$/d" "$BUILD_ARGS_DIR/services.env" || true - sed -i "/^DOCKER_[A-Z0-9_]\+$/d" "$BUILD_ARGS_DIR/infrastructure.env" || true - sed -i "/^DOCKER_[A-Z0-9_]\+$/d" "$BUILD_ARGS_DIR/clients.env" || true - - # 2) Remove forbidden DOCKER_APP_VERSION from all build-args envs (it is mapped at runtime) - sed -i "/^DOCKER_APP_VERSION\(=.*\)\?$/d" "$BUILD_ARGS_DIR/global.env" || true - sed -i "/^DOCKER_APP_VERSION\(=.*\)\?$/d" "$BUILD_ARGS_DIR/clients.env" || true - sed -i "/^DOCKER_APP_VERSION\(=.*\)\?$/d" "$BUILD_ARGS_DIR/services.env" || true - sed -i "/^DOCKER_APP_VERSION\(=.*\)\?$/d" "$BUILD_ARGS_DIR/infrastructure.env" || true - - # 3) Purge stray numeric service-port assignments that must not live in global.env - # e.g., lines like: prometheus = 9090 - sed -i -E "/^[a-z_]+ = [0-9]+$/d" "$BUILD_ARGS_DIR/global.env" || true - - print_success "All environment files synced successfully!" -} - -# Function to show current versions -show_current_versions() { - print_info "Current Docker versions:" - echo " Gradle: $(get_version "gradle")" - echo " Java: $(get_version "java")" - echo " Node.js: $(get_version "node")" - echo " Nginx: $(get_version "nginx")" - echo " Alpine: $(get_version "alpine")" - echo " Prometheus: $(get_version "prometheus")" - echo " Grafana: $(get_version "grafana")" - echo " Keycloak: $(get_version "keycloak")" - echo " App Version: $(get_version "app-version")" - echo " Spring Profile (Default): $(get_version "spring-profiles-default")" - echo " Spring Profile (Docker): $(get_version "spring-profiles-docker")" -} - -# Function to show help -show_help() { - echo "Docker Versions Update Utility" - echo "" - echo "Usage: $0 [COMMAND] [OPTIONS]" - echo "" - echo "Commands:" - echo " show Show current versions" - echo " sync Sync versions.toml to environment files" - echo " update Update specific version" - echo "" - echo "Available keys for update:" - echo " gradle Gradle version" - echo " java Java version" - echo " node Node.js version" - echo " nginx Nginx version" - echo " alpine Alpine Linux version" - echo " prometheus Prometheus version" - echo " grafana Grafana version" - echo " keycloak Keycloak version" - echo " app-version Application version" - echo " spring-profiles-default Default Spring profile" - echo " spring-profiles-docker Docker Spring profile" - echo "" - echo "Examples:" - echo " $0 show # Show current versions" - echo " $0 update gradle 9.1.0 # Update Gradle to 9.1.0" - echo " $0 update java 22 # Update Java to version 22" - echo " $0 sync # Sync versions to environment files" - echo "" - echo "After updating versions, run 'sync' to update environment files" - echo "or use scripts/docker-build.sh to build with new versions." -} - -# Main execution -main() { - # Check if versions.toml exists - if [[ ! -f "$VERSIONS_TOML" ]]; then - print_error "Versions file not found: $VERSIONS_TOML" - exit 1 - fi - - case $1 in - "show") - show_current_versions - ;; - "sync") - sync_to_env_files - ;; - "update") - if [[ $# -lt 3 ]]; then - print_error "Usage: $0 update " - exit 1 - fi - update_version "$2" "$3" - sync_to_env_files - ;; - "-h"|"--help"|"help") - show_help - ;; - "") - print_error "No command specified" - show_help - exit 1 - ;; - *) - print_error "Unknown command: $1" - show_help - exit 1 - ;; - esac -} - -# Run main function with all arguments -main "$@" diff --git a/scripts/generate-build-env.sh b/scripts/generate-build-env.sh deleted file mode 100644 index 219b02d8..00000000 --- a/scripts/generate-build-env.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Minimal generator: creates docker/build-args/global.env from docker/versions.toml -# Usage: scripts/generate-build-env.sh [OUTPUT_FILE] - -ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) -TOML="$ROOT_DIR/docker/versions.toml" -OUT="${1:-$ROOT_DIR/docker/build-args/global.env}" - -if [[ ! -f "$TOML" ]]; then - echo "Error: versions file not found: $TOML" >&2 - exit 1 -fi - -get_ver() { - # reads [versions] table key - local key="$1" - awk -F'=' -v k="$key" ' - $0 ~ /^\[versions\]/ { inver=1; next } - $0 ~ /^\[/ { if(inver) exit } - inver && $1 ~ "^"k"$" { gsub(/[ "\t]/, "", $2); print $2; exit } - ' "$TOML" -} - -GRADLE_VERSION=$(get_ver gradle) -JAVA_VERSION=$(get_ver java) -APP_VERSION=$(get_ver app-version) -PROMETHEUS=$(get_ver prometheus) -GRAFANA=$(get_ver grafana) -KEYCLOAK=$(get_ver keycloak) -POSTGRES=$(get_ver postgres) -REDIS=$(get_ver redis) -CONSUL=$(get_ver consul) -KAFKA=$(get_ver kafka) -ZOOKEEPER=$(get_ver zookeeper) - -mkdir -p "$(dirname "$OUT")" -cat > "$OUT" < PING_SERVICE) - local service_upper=$(echo "$service" | tr '[:lower:]' '[:upper:]' | tr '-' '_') - - # Map to actual Dockerfile path when service directory name differs from service key - local dockerfile_service="$service" - if [[ "$category" == "infrastructure" && "$service" == "api-gateway" ]]; then - dockerfile_service="gateway" - fi - - cat << EOF - $service: - build: - context: . - dockerfile: dockerfiles/$category/$dockerfile_service/Dockerfile -$(generate_build_args_section $category) - container_name: meldestelle-$service -$(generate_environment_vars_for_service $service $environment) - ports: - - "\${${service_upper}_PORT:-$service_port}:$service_port" -EOF - - # Add debug port if enabled - if [[ "$debug_port" != "false" && "$debug_port" != "" ]]; then - echo " - \"${debug_port}:${debug_port}\" # Debug-Port" - fi - - cat << EOF - networks: - - meldestelle-network -$(generate_health_check $service $environment) - restart: unless-stopped -EOF -} - -# Function to generate main infrastructure compose file -generate_infrastructure_compose() { - local environment=${1:-development} - - print_info "Generating docker-compose.yml (Infrastructure)..." - - cat > "$OUTPUT_DIR/docker-compose.yml" << EOF -# =================================================================== -# Docker Compose - Infrastructure Services -# Generated from docker/versions.toml -# Environment: $environment -# Generated: $(date -u +'%Y-%m-%d %H:%M:%S UTC') -# =================================================================== - -services: - # =================================================================== - # Database - # =================================================================== - postgres: - image: postgres:\${DOCKER_POSTGRES_VERSION:-$(get_version "postgres")} - container_name: meldestelle-postgres - environment: - POSTGRES_USER: \${POSTGRES_USER:-meldestelle} - POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-meldestelle} - POSTGRES_DB: \${POSTGRES_DB:-meldestelle} - ports: - - "$(get_port postgres):$(get_port postgres)" - volumes: - - postgres-data:/var/lib/postgresql/data - - ./docker/services/postgres:/docker-entrypoint-initdb.d - networks: - - meldestelle-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U meldestelle -d meldestelle"] - interval: $(get_env_config $environment "health-check-interval") - timeout: $(get_env_config $environment "health-check-timeout") - retries: $(get_env_config $environment "health-check-retries") - start_period: $(get_env_config $environment "health-check-start-period") - restart: unless-stopped - - # =================================================================== - # Cache - # =================================================================== - redis: - image: redis:\${DOCKER_REDIS_VERSION:-$(get_version "redis")} - container_name: meldestelle-redis - ports: - - "\${REDIS_PORT:-$(get_port redis)}:$(get_port redis)" - volumes: - - redis-data:/data - command: redis-server --appendonly yes - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: $(get_env_config $environment "health-check-interval") - timeout: $(get_env_config $environment "health-check-timeout") - retries: $(get_env_config $environment "health-check-retries") - start_period: $(get_env_config $environment "health-check-start-period") - restart: unless-stopped - - # =================================================================== - # Authentication - # =================================================================== - keycloak: - image: quay.io/keycloak/keycloak:\${DOCKER_KEYCLOAK_VERSION:-$(get_version "keycloak")} - container_name: meldestelle-keycloak - environment: - KEYCLOAK_ADMIN: \${KEYCLOAK_ADMIN:-admin} - KEYCLOAK_ADMIN_PASSWORD: \${KEYCLOAK_ADMIN_PASSWORD:-admin} - KC_DB: postgres - KC_DB_URL: jdbc:postgresql://postgres:$(get_port postgres)/\${POSTGRES_DB:-meldestelle} - KC_DB_USERNAME: \${POSTGRES_USER:-meldestelle} - KC_DB_PASSWORD: \${POSTGRES_PASSWORD:-meldestelle} - ports: - - "$(get_port keycloak):8080" - depends_on: - postgres: - condition: service_healthy - volumes: - - ./docker/services/keycloak:/opt/keycloak/data/import - command: start-dev --import-realm - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/"] - interval: $(get_env_config $environment "health-check-interval") - timeout: $(get_env_config $environment "health-check-timeout") - retries: $(get_env_config $environment "health-check-retries") - start_period: $(get_env_config $environment "health-check-start-period") - restart: unless-stopped - - # =================================================================== - # Monitoring - # =================================================================== - prometheus: - image: prom/prometheus:\${DOCKER_PROMETHEUS_VERSION:-$(get_version "prometheus")} - container_name: meldestelle-prometheus - ports: - - "\${PROMETHEUS_PORT:-$(get_port prometheus)}:$(get_port prometheus)" - volumes: - - prometheus-data:/prometheus - - ./docker/monitoring/prometheus:/etc/prometheus:ro - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--web.console.libraries=/etc/prometheus/console_libraries' - - '--web.console.templates=/etc/prometheus/consoles' - - '--storage.tsdb.retention.time=200h' - - '--web.enable-lifecycle' - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:$(get_port prometheus)/-/healthy"] - interval: $(get_env_config $environment "health-check-interval") - timeout: $(get_env_config $environment "health-check-timeout") - retries: $(get_env_config $environment "health-check-retries") - start_period: $(get_env_config $environment "health-check-start-period") - restart: unless-stopped - - grafana: - image: grafana/grafana:\${DOCKER_GRAFANA_VERSION:-$(get_version "grafana")} - container_name: meldestelle-grafana - environment: - GF_SECURITY_ADMIN_USER: \${GF_SECURITY_ADMIN_USER:-admin} - GF_SECURITY_ADMIN_PASSWORD: \${GF_SECURITY_ADMIN_PASSWORD:-admin} - GF_USERS_ALLOW_SIGN_UP: \${GF_USERS_ALLOW_SIGN_UP:-false} - GF_INSTALL_PLUGINS: grafana-piechart-panel - ports: - - "\${GRAFANA_PORT:-$(get_port grafana)}:$(get_port grafana)" - volumes: - - grafana-data:/var/lib/grafana - - ./docker/monitoring/grafana:/etc/grafana/provisioning:ro - depends_on: - - prometheus - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:$(get_port grafana)/api/health"] - interval: $(get_env_config $environment "health-check-interval") - timeout: $(get_env_config $environment "health-check-timeout") - retries: $(get_env_config $environment "health-check-retries") - start_period: $(get_env_config $environment "health-check-start-period") - restart: unless-stopped - -# =================================================================== -# Volumes -# =================================================================== -volumes: - postgres-data: - driver: local - redis-data: - driver: local - prometheus-data: - driver: local - grafana-data: - driver: local - -# =================================================================== -# Networks -# =================================================================== -networks: - meldestelle-network: - driver: bridge -EOF - - print_success "Generated docker-compose.yml" -} - -# Function to generate services compose file -generate_services_compose() { - local environment=${1:-development} - - print_info "Generating docker-compose.services.yml..." - - cat > "$OUTPUT_DIR/docker-compose.services.yml" << EOF -# =================================================================== -# Docker Compose - Application Services -# Generated from docker/versions.toml -# Environment: $environment -# Generated: $(date -u +'%Y-%m-%d %H:%M:%S UTC') -# =================================================================== - -services: -$(generate_service_definition "ping-service" "services" $environment) - -$(generate_service_definition "api-gateway" "infrastructure" $environment) - -# =================================================================== -# Networks (shared network from main compose file) -# =================================================================== -networks: - meldestelle-network: - driver: bridge -EOF - - print_success "Generated docker-compose.services.yml" -} - -# Function to generate clients compose file -generate_clients_compose() { - local environment=${1:-development} - - print_info "Generating docker-compose.clients.yml..." - - cat > "$OUTPUT_DIR/docker-compose.clients.yml" << EOF -# =================================================================== -# Docker Compose - Client Applications -# Generated from docker/versions.toml -# Environment: $environment -# Generated: $(date -u +'%Y-%m-%d %H:%M:%S UTC') -# =================================================================== - -services: - # =================================================================== - # Web Application (Compose for Web) - # =================================================================== - web-app: - build: - context: . - dockerfile: dockerfiles/clients/web-app/Dockerfile -$(generate_build_args_section "clients") - # Application-specific arguments - CLIENT_PATH: client - CLIENT_MODULE: client - CLIENT_NAME: meldestelle-web-app - container_name: meldestelle-web-app - environment: - NODE_ENV: \${NODE_ENV:-$(get_env_config $environment "spring-profiles")} - API_BASE_URL: http://api-gateway:\${GATEWAY_PORT:-$(get_port "api-gateway")} - WS_URL: ws://api-gateway:\${GATEWAY_PORT:-$(get_port "api-gateway")}/ws - APP_TITLE: \${APP_NAME:-Meldestelle} - APP_VERSION: \${APP_VERSION:-$(get_version "app-version")} - ports: - - "$(get_port "web-app"):$(get_port "web-app")" - networks: - - meldestelle-network - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:$(get_port "web-app")/health"] - interval: $(get_env_config $environment "health-check-interval") - timeout: $(get_env_config $environment "health-check-timeout") - retries: $(get_env_config $environment "health-check-retries") - start_period: $(get_env_config $environment "health-check-start-period") - restart: unless-stopped - -# =================================================================== -# Networks (shared network from main compose file) -# =================================================================== -networks: - meldestelle-network: - driver: bridge -EOF - - print_success "Generated docker-compose.clients.yml" -} - -# Function to show help -show_help() { - echo "Docker Compose Template Generator" - echo "" - echo "Usage: $0 [COMMAND] [ENVIRONMENT]" - echo "" - echo "Commands:" - echo " all Generate all compose files" - echo " infrastructure Generate docker-compose.yml (infrastructure)" - echo " services Generate docker-compose.services.yml" - echo " clients Generate docker-compose.clients.yml" - echo "" - echo "Environments:" - echo " development Development environment (default)" - echo " production Production environment" - echo " testing Testing environment" - echo "" - echo "Examples:" - echo " $0 all # Generate all files for development" - echo " $0 all production # Generate all files for production" - echo " $0 infrastructure development # Generate infrastructure compose for dev" - echo " $0 services production # Generate services compose for prod" -} - -# Main execution -main() { - # Check if versions.toml exists - if [[ ! -f "$VERSIONS_TOML" ]]; then - print_error "Versions file not found: $VERSIONS_TOML" - exit 1 - fi - - local command=${1:-all} - local environment=${2:-development} - - # Validate environment - if [[ ! "$environment" =~ ^(development|production|testing)$ ]]; then - print_error "Invalid environment: $environment" - print_error "Valid environments: development, production, testing" - exit 1 - fi - - print_info "Generating Docker Compose files for environment: $environment" - - case $command in - "all") - generate_infrastructure_compose $environment - generate_services_compose $environment - generate_clients_compose $environment - print_success "All compose files generated successfully!" - ;; - "infrastructure") - generate_infrastructure_compose $environment - ;; - "services") - generate_services_compose $environment - ;; - "clients") - generate_clients_compose $environment - ;; - "-h"|"--help"|"help") - show_help - ;; - *) - print_error "Unknown command: $command" - show_help - exit 1 - ;; - esac -} - -# Run main function with all arguments -main "$@" diff --git a/scripts/git-hooks/pre-commit-ssot b/scripts/git-hooks/pre-commit-ssot deleted file mode 100644 index 6a2b6096..00000000 --- a/scripts/git-hooks/pre-commit-ssot +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -# =================================================================== -# Git pre-commit hook: Enforce Docker SSoT (Single Source of Truth) -# Runs lightweight sync/generate/validate and drift check before commit -# Install: make hooks-install (copies this file into .git/hooks/pre-commit) -# =================================================================== - -set -euo pipefail - -ROOT_DIR=$(git rev-parse --show-toplevel) -cd "$ROOT_DIR" - -# Colors -RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' - -info() { echo -e "${YELLOW}[pre-commit][INFO]${NC} $*"; } -success() { echo -e "${GREEN}[pre-commit][OK]${NC} $*"; } -error() { echo -e "${RED}[pre-commit][ERROR]${NC} $*"; } - -MODE="${DOCKER_SSOT_MODE:-compat}" - -# Only run if Docker-related files are in the index or if explicitly requested -if ! git diff --cached --name-only | grep -E '^(docker/|dockerfiles/|docker-compose.*\.yml(\.optimized)?|scripts/(generate-compose-files\.sh|docker-versions-update\.sh|validate-docker-consistency\.sh))' >/dev/null; then - info "No Docker-related changes staged; skipping SSoT checks." - exit 0 -fi - -if [ "$MODE" = "envless" ]; then - info "Env-less mode active → skipping env file sync and using versions.toml directly" -else - info "Synchronizing versions to env files..." - bash scripts/docker-versions-update.sh sync >/dev/null -fi - -info "Regenerating docker-compose files (development)..." -bash scripts/generate-compose-files.sh all development >/dev/null - -info "Validating Docker SSoT consistency (mode: $MODE)..." -if ! DOCKER_SSOT_MODE="$MODE" bash scripts/validate-docker-consistency.sh all >/dev/null; then - error "SSoT validation failed. See details by running: DOCKER_SSOT_MODE=$MODE bash scripts/validate-docker-consistency.sh all" - exit 1 -fi - -info "Checking for drift (ignoring timestamps/comments)..." -CHANGED=$(git diff --name-only || true) -if [ -n "$CHANGED" ]; then - fail=0 - for f in $CHANGED; do - DIFF_FILTERED=$(git diff --unified=0 -- "$f" \ - | awk 'BEGIN{show=0} { \ - if ($0 ~ /^\+\+\+|^---|^@@/) { next } \ - if ($0 ~ /^[+-]# (Generated:|Last updated:)/) { next } \ - if ($0 ~ /^[+-]#\s*Generated from docker\/versions.toml/) { next } \ - if ($0 ~ /^[+-]#\s*Environment:/) { next } \ - if ($0 ~ /^[+-]#\s*Source:/) { next } \ - if ($0 ~ /^[+-]$/) { next } \ - if ($0 ~ /^[+-]/) { print $0 } \ - }') - if [ -n "$DIFF_FILTERED" ]; then - error "SSoT drift detected in $f (beyond timestamps):" - echo "$DIFF_FILTERED" - fail=1 - fi - done - if [ $fail -ne 0 ]; then - if [ "$MODE" = "envless" ]; then - error "Generated artifacts differ from repository. Run:\n DOCKER_SSOT_MODE=envless bash scripts/generate-compose-files.sh all\nThen add and commit the changes." - else - error "Generated artifacts differ from repository. Run:\n bash scripts/docker-versions-update.sh sync\n bash scripts/generate-compose-files.sh all\nThen add and commit the changes." - fi - exit 1 - fi -fi - -success "Docker SSoT checks passed." -exit 0 diff --git a/scripts/maintenance/prune-docs.sh b/scripts/maintenance/prune-docs.sh deleted file mode 100755 index 6d0dbbac..00000000 --- a/scripts/maintenance/prune-docs.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Hard prune docs to a strict whitelist. Everything else under docs/ is removed. -# Keep directories required by validator, and the minimal doc set we agreed on. - -ROOT_DIR=$(cd "$(dirname "$0")/../.." && pwd) -cd "$ROOT_DIR" - -WHITELIST=( - "docs/index.md" - "docs/overview/system-overview.md" - "docs/how-to/start-local.md" - "docs/how-to/deploy-proxmox-nginx.md" - "docs/api/README.md" - "docs/api/members-api.md" # kept to satisfy link in API README - "docs/api/generated" # keep generated OpenAPI specs - "docs/reference/ports-and-urls.md" - "docs/now/current.md" - "docs/now/TEMPLATE.md" - "docs/now/README.md" - "docs/proxmox-nginx/meldestelle.conf" - "docs/architecture/c4" # keep all C4 diagrams - "docs/architecture/adr" # keep all ADRs - # Note: Do NOT include generic directories like 'docs' or 'docs/api' here, - # because prefix matching would whitelist everything under them. We'll recreate - # required empty directories after pruning. -) - -is_whitelisted() { - local path="$1" - for keep in "${WHITELIST[@]}"; do - # exact match - if [[ "$path" == "$keep" ]]; then - return 0 - fi - # directory prefix match - if [[ -d "$keep" && "$path" == $keep/* ]]; then - return 0 - fi - done - return 1 -} - -# Remove all tracked files under docs/ that are not whitelisted -mapfile -t FILES < <(git ls-files docs) - -REMOVED=0 -for f in "${FILES[@]}"; do - if ! is_whitelisted "$f"; then - git rm -r -q "$f" - ((REMOVED++)) || true - fi -done - -echo "Removed $REMOVED files not in whitelist." - -# Ensure required directories exist (even if empty) for validator -mkdir -p docs docs/api docs/architecture docs/development docs/how-to docs/overview docs/now docs/proxmox-nginx docs/reference - -# Optionally remove empty directories that are not needed anymore (except required ones) -# Find empty directories under docs and remove them, but skip the required ones above -find docs -type d -empty \ - ! -path "docs" \ - ! -path "docs/api" \ - ! -path "docs/architecture" \ - ! -path "docs/development" \ - ! -path "docs/how-to" \ - ! -path "docs/overview" \ - ! -path "docs/now" \ - ! -path "docs/proxmox-nginx" \ - ! -path "docs/reference" \ - -print -delete || true - -# Stage only docs directory updates created by find -git add -A docs - -echo "Docs prune completed. Review changes and commit." diff --git a/scripts/setup-keycloak.sh b/scripts/setup-keycloak.sh deleted file mode 100755 index cb29519b..00000000 --- a/scripts/setup-keycloak.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# =================================================================== -# Keycloak Setup Script für Meldestelle Projekt -# =================================================================== - -set -e - -# Konfiguration -KEYCLOAK_URL=${KEYCLOAK_URL:-"http://localhost:8180"} -# Support both new KC_BOOTSTRAP_* (Keycloak 26+) and legacy KEYCLOAK_* env vars -ADMIN_USER=${KC_BOOTSTRAP_ADMIN_USERNAME:-${KEYCLOAK_ADMIN:-"admin"}} -ADMIN_PASSWORD=${KC_BOOTSTRAP_ADMIN_PASSWORD:-${KEYCLOAK_ADMIN_PASSWORD:-"admin"}} -REALM_NAME="meldestelle" - -echo "🚀 Starting Keycloak setup for Meldestelle..." - -# Warte auf Keycloak -echo "⏳ Waiting for Keycloak to be ready..." -timeout=60 -counter=0 -while ! curl -f "$KEYCLOAK_URL/health/ready" >/dev/null 2>&1; do - if [ $counter -eq $timeout ]; then - echo "❌ Keycloak is not ready after $timeout seconds" - exit 1 - fi - echo " Waiting... ($counter/$timeout)" - sleep 1 - counter=$((counter + 1)) -done - -echo "✅ Keycloak is ready!" - -# Obtain admin token -echo "🔐 Obtaining admin token..." -ADMIN_TOKEN=$(curl -s \ - -d "client_id=admin-cli" \ - -d "username=$ADMIN_USER" \ - -d "password=$ADMIN_PASSWORD" \ - -d "grant_type=password" \ - "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | \ - jq -r '.access_token') - -if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then - echo "❌ Failed to obtain admin token" - exit 1 -fi - -echo "✅ Admin token obtained" - -# Check if realm exists -echo "🔍 Checking if realm '$REALM_NAME' exists..." -REALM_EXISTS=$(curl -s \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - "$KEYCLOAK_URL/admin/realms/$REALM_NAME" \ - -w "%{http_code}" -o /dev/null) - -if [ "$REALM_EXISTS" = "200" ]; then - echo "✅ Realm '$REALM_NAME' already exists" -else - echo "❌ Realm '$REALM_NAME' not found (HTTP $REALM_EXISTS)" - echo "💡 Please import the realm configuration manually or check the import volume" -fi - -# Verify API Gateway client -echo "🔍 Checking API Gateway client..." -CLIENT_EXISTS=$(curl -s \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - "$KEYCLOAK_URL/admin/realms/$REALM_NAME/clients?clientId=api-gateway" | \ - jq '. | length') - -if [ "$CLIENT_EXISTS" -gt "0" ]; then - echo "✅ API Gateway client exists" -else - echo "⚠️ API Gateway client not found" -fi - -# Test realm endpoints -echo "🧪 Testing realm endpoints..." -curl -s "$KEYCLOAK_URL/realms/$REALM_NAME/.well-known/openid_configuration" > /dev/null && \ - echo "✅ OpenID configuration accessible" || \ - echo "❌ OpenID configuration not accessible" - -curl -s "$KEYCLOAK_URL/realms/$REALM_NAME/protocol/openid-connect/certs" > /dev/null && \ - echo "✅ JWK Set accessible" || \ - echo "❌ JWK Set not accessible" - -echo "" -echo "🎉 Keycloak setup check completed!" -echo "📝 Summary:" -echo " - Keycloak URL: $KEYCLOAK_URL" -echo " - Realm: $REALM_NAME" -echo " - Admin Console: $KEYCLOAK_URL/admin/" -echo "" -echo "🔗 Next steps:" -echo " 1. Visit the admin console: $KEYCLOAK_URL/admin/" -echo " 2. Login with: $ADMIN_USER / $ADMIN_PASSWORD" -echo " 3. Verify realm configuration" -echo " 4. Test authentication flow" diff --git a/scripts/smoke/prometheus_smoke.sh b/scripts/smoke/prometheus_smoke.sh deleted file mode 100644 index 2c2faa40..00000000 --- a/scripts/smoke/prometheus_smoke.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -PING_SERVICE_URL=${PING_SERVICE_URL:-http://localhost:8082} -GATEWAY_URL=${GATEWAY_URL:-http://localhost:8081} - -check_metrics() { - local url="$1" - echo "[Smoke] Checking Prometheus metrics at $url ..." - local body - body=$(curl -sf "$url/actuator/prometheus") || return 1 - echo "$body" | grep -E 'http_server_requests|jvm_memory_used_bytes' -q -} - -if check_metrics "$PING_SERVICE_URL"; then - echo "[Smoke][OK] ping-service exposes Prometheus metrics" -else - echo "[Smoke][FAIL] ping-service Prometheus endpoint not available" >&2 - exit 1 -fi - -if check_metrics "$GATEWAY_URL"; then - echo "[Smoke][OK] api-gateway exposes Prometheus metrics" -else - echo "[Smoke][FAIL] api-gateway Prometheus endpoint not available" >&2 - exit 1 -fi diff --git a/scripts/smoke/zipkin_smoke.sh b/scripts/smoke/zipkin_smoke.sh deleted file mode 100644 index 4226c3e5..00000000 --- a/scripts/smoke/zipkin_smoke.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -GATEWAY_URL=${GATEWAY_URL:-http://localhost:8081} -ZIPKIN_URL=${ZIPKIN_URL:-http://localhost:9411} - -echo "[Smoke] Triggering ping via Gateway..." -curl -sf "$GATEWAY_URL/api/ping/ping" > /dev/null || { - echo "[Smoke][FAIL] Gateway ping failed" >&2 - exit 1 -} - -# Give Zipkin a moment to receive spans -sleep 1 - -echo "[Smoke] Checking for recent traces in Zipkin..." -TRACES_JSON=$(curl -sf "$ZIPKIN_URL/api/v2/traces?limit=5") || { - echo "[Smoke][FAIL] Zipkin API not reachable" >&2 - exit 1 -} - -# Very lightweight check: ensure at least one trace contains api-gateway or ping-service -if echo "$TRACES_JSON" | grep -E 'api-gateway|ping-service' -q; then - echo "[Smoke][OK] Traces found for api-gateway/ping-service" - exit 0 -else - echo "[Smoke][WARN] No traces for api-gateway/ping-service in the last results" >&2 - # Not a hard failure; Zipkin may be delayed. Exit non-zero to be strict in CI - exit 2 -fi diff --git a/scripts/test/integration-test.sh b/scripts/test/integration-test.sh deleted file mode 100755 index f810b552..00000000 --- a/scripts/test/integration-test.sh +++ /dev/null @@ -1,432 +0,0 @@ -#!/bin/bash - -# ============================================================================= -# Full System Integration Test Script -# ============================================================================= -# Comprehensive testing of all Meldestelle services including infrastructure, -# application services, client applications, and inter-service connectivity. -# ============================================================================= - -# Load common utilities -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=../utils/common.sh -source "$SCRIPT_DIR/../utils/common.sh" || { - echo "Error: Could not load common utilities" - exit 1 -} - -# ============================================================================= -# Configuration -# ============================================================================= - -readonly COMPOSE_FILES="-f docker-compose.yml -f docker-compose.services.yml -f docker-compose.clients.yml" -# shellcheck disable=SC2034 -readonly TIMEOUT_SECONDS=300 -# shellcheck disable=SC2034 -readonly HEALTH_CHECK_INTERVAL=10 -readonly MAX_RETRIES=30 - -# Project root and Docker configuration -# shellcheck disable=SC2155 -readonly PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -readonly DOCKER_DIR="$PROJECT_ROOT/docker" -readonly BUILD_ARGS_DIR="$DOCKER_DIR/build-args" - -# Service endpoints (from common configuration) -# shellcheck disable=SC2034 -readonly SERVICES_CONFIG=( - "postgres:5432:PostgreSQL:pg_isready -U meldestelle" - "redis:6379:Redis:redis-cli ping" - "consul:8500:Consul:http://localhost:8500/v1/status/leader" - "api-gateway:8081:API Gateway:http://localhost:8082actuator/health" - "ping-service:8082ing Service:http://localhost:8082ctuator/health" -) - -# Integration with central Docker version management -load_docker_versions() { - if [[ -f "$BUILD_ARGS_DIR/global.env" ]]; then - source "$BUILD_ARGS_DIR/global.env" - log_info "Loaded centralized Docker versions" - else - log_warning "Centralized Docker versions not found, using defaults" - fi -} - -# Function to wait for service health check using common utilities -wait_for_service_with_retry() { - local service_name=$1 - local health_check=$2 - local max_attempts=${3:-$MAX_RETRIES} - - log_info "Waiting for $service_name to become healthy..." - - if retry_with_backoff "$max_attempts" "$health_check" "Waiting for $service_name"; then - log_success "$service_name is healthy" - return 0 - else - log_error "$service_name failed to become healthy after $max_attempts attempts" - return 1 - fi -} - -# HTTP health check function -http_health_check() { - local url=$1 - curl -f -s -L --max-time 5 "$url" > /dev/null 2>&1 -} - -# PostgreSQL health check function -postgres_health_check() { - docker exec meldestelle-postgres pg_isready -U meldestelle -d meldestelle > /dev/null 2>&1 -} - -# Redis health check function -redis_health_check() { - docker exec meldestelle-redis redis-cli ping > /dev/null 2>&1 -} - -# Function to check service logs for errors -check_service_logs() { - local service_name=$1 - local container_name=$2 - - log_info "Checking $service_name logs for errors..." - - # Get last 50 lines of logs - # shellcheck disable=SC2155 - local logs=$(docker logs --tail 50 "$container_name" 2>&1 || echo "") - - # Check for common error patterns - if echo "$logs" | grep -qi "error\|exception\|failed\|fatal"; then - log_warning "$service_name has error messages in logs:" - echo "$logs" | grep -i "error\|exception\|failed\|fatal" | tail -5 - else - log_success "$service_name logs look clean" - fi -} - -# ============================================================================= -# Enhanced Test Categories and Selective Execution -# ============================================================================= - -# Function to test infrastructure services only -test_infrastructure_services() { - log_section "Testing Infrastructure Services" - - # Load Docker versions - load_docker_versions - - # Start infrastructure services only - log_info "Starting infrastructure services..." - # shellcheck disable=SC2164 - cd "$PROJECT_ROOT" - docker compose -f docker-compose.yml up -d - - # Wait for initialization - log_info "Waiting 30 seconds for infrastructure services to initialize..." - sleep 30 - - # Test PostgreSQL - log_info "Testing PostgreSQL connection..." - wait_for_service_with_retry "PostgreSQL" postgres_health_check || return 1 - - # Test Redis - log_info "Testing Redis connection..." - wait_for_service_with_retry "Redis" redis_health_check || return 1 - - # Test Consul - log_info "Testing Consul..." - wait_for_service_with_retry "Consul" "http_health_check http://localhost:8500/v1/status/leader" || return 1 - - # Test Prometheus - log_info "Testing Prometheus..." - wait_for_service_with_retry "Prometheus" "http_health_check http://localhost:9090/-/healthy" || return 1 - - # Test Grafana - log_info "Testing Grafana..." - wait_for_service_with_retry "Grafana" "http_health_check http://localhost:3000/api/health" || return 1 - - # Test Keycloak - log_info "Testing Keycloak..." - wait_for_service_with_retry "Keycloak" "http_health_check http://localhost:8180/" || return 1 - - log_success "All infrastructure services are healthy!" -} - -# Function to test application services -test_application_services() { - log_section "Testing Application Services" - - # Start application services - log_info "Starting application services..." - # shellcheck disable=SC2164 - cd "$PROJECT_ROOT" - docker compose $COMPOSE_FILES up -d - - # Wait for initialization - log_info "Waiting 45 seconds for application services to initialize..." - sleep 45 - - # Test API Gateway - log_info "Testing API Gateway..." - wait_for_service_with_retry "API Gateway" "http_health_check http://localhost:8082actuator/health" || return 1 - - # Test Ping Service - log_info "Testing Ping Service..." - wait_for_service_with_retry "Ping Service" "http_health_check http://localhost:8082ctuator/health" || return 1 - - log_success "All application services are healthy!" -} - -# Function to test client applications -test_client_applications() { - log_section "Testing Client Applications" - - # Start client applications - log_info "Starting client applications..." - # shellcheck disable=SC2164 - cd "$PROJECT_ROOT" - docker compose -f docker-compose.yml -f docker-compose.clients.yml up -d - - # Wait for initialization - log_info "Waiting 60 seconds for client applications to initialize..." - sleep 60 - - # Test Web Application - log_info "Testing Web Application..." - wait_for_service_with_retry "Web App" "http_health_check http://localhost:4000/health" || return 1 - - # Test Desktop Application (VNC interface) - log_info "Testing Desktop Application VNC interface..." - wait_for_service_with_retry "Desktop App" "http_health_check http://localhost:6080/" || return 1 - - log_success "All client applications are healthy!" -} - -# Function to test network connectivity -test_network_connectivity() { - log_section "Testing Network Connectivity" - - # Test service-to-service connectivity - log_info "Testing service-to-service connectivity..." - - # Test API Gateway can reach backend services - if docker exec meldestelle-api-gateway curl -f -s --max-time 5 http://ping-service:8082ctuator/health > /dev/null 2>&1; then - log_success "API Gateway can reach Ping Service" - else - log_error "API Gateway cannot reach Ping Service" - return 1 - fi - - # Test application service can reach infrastructure - if docker exec meldestelle-ping-service curl -f -s --max-time 5 http://consul:8500/v1/status/leader > /dev/null 2>&1; then - log_success "Application services can reach Consul" - else - log_error "Application services cannot reach Consul" - return 1 - fi - - log_success "Network connectivity tests passed!" -} - -# ============================================================================= -# Enhanced Reporting and Monitoring -# ============================================================================= - -# Function to generate integration report -generate_integration_report() { - log_section "Integration Test Report" - - # Service status matrix - log_info "Service Status Matrix:" - docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" --filter "name=meldestelle" - - # Performance metrics - log_info "Performance Metrics:" - # shellcheck disable=SC2046 - docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" $(docker ps -q --filter "name=meldestelle") 2>/dev/null || true - - # Resource usage summary - # shellcheck disable=SC2155 - local containers=$(docker ps --filter "name=meldestelle" --format "{{.Names}}" | wc -l) - log_info "Total running containers: $containers" - - # Test summary - print_test_summary -} - -# Enhanced cleanup function using common utilities -cleanup() { - log_section "Cleaning up test environment" - - log_info "Stopping and removing all test containers..." - # shellcheck disable=SC2164 - cd "$PROJECT_ROOT" - - # Use the same files to tear down the environment - docker compose "$COMPOSE_FILES" down --remove-orphans -v 2>/dev/null || true - - # Remove network if it exists - docker network rm meldestelle-network >/dev/null 2>&1 || true - - log_success "Cleanup completed" -} - -# Function to run full system integration test -run_full_integration_test() { - log_section "Full System Integration Test" - - # Load Docker versions - load_docker_versions - - # Start ALL services using all compose files - log_info "Starting full environment with all services..." - # shellcheck disable=SC2164 - cd "$PROJECT_ROOT" - docker compose "$COMPOSE_FILES" up -d - - # Give services time to initialize - log_info "Waiting 60 seconds for all services to initialize..." - sleep 60 - - # Run comprehensive tests - test_infrastructure_services || return 1 - test_application_services || return 1 - test_client_applications || return 1 - test_network_connectivity || return 1 - - # Generate comprehensive report - generate_integration_report - - log_success "Full system integration test completed successfully!" -} - -# ============================================================================= -# Command Line Interface and Help System -# ============================================================================= - -# Function to show help -show_help() { - cat << EOF -Full System Integration Test Script - -USAGE: - $0 [OPTIONS] [CATEGORY] - -CATEGORIES: - infrastructure Test infrastructure services only (PostgreSQL, Redis, Consul, etc.) - services Test application services (API Gateway, Ping Service, etc.) - clients Test client applications (Web App, Desktop App) - network Test inter-service network connectivity - all Run full system integration test (default) - cleanup Clean up test environment only - -OPTIONS: - -h, --help Show this help message - -v, --verbose Enable verbose logging - --no-cleanup Skip cleanup on exit - --cleanup-only Only run cleanup and exit - -EXAMPLES: - $0 # Run full integration test - $0 infrastructure # Test infrastructure services only - $0 services # Test application services only - $0 clients # Test client applications only - $0 network # Test network connectivity only - $0 cleanup # Clean up test environment - $0 --help # Show this help - -ENVIRONMENT VARIABLES: - CLEANUP_SERVICES=false Skip cleanup on exit - REMOVE_CONTAINERS=true Remove containers during cleanup - MAX_RETRIES=30 Maximum retry attempts for health checks - HEALTH_CHECK_INTERVAL=10 Seconds between health check attempts - -The script automatically loads versions from the centralized Docker version -management system and integrates with the common utilities for consistent -logging, error handling, and cleanup procedures. -EOF -} - -# ============================================================================= -# Main Execution Function -# ============================================================================= - -# Main execution function with enhanced argument parsing -main() { - local category="${1:-all}" - local cleanup_on_exit=true - - # Parse options - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - show_help - exit 0 - ;; - -v|--verbose) - set -x - shift - ;; - --no-cleanup) - cleanup_on_exit=false - shift - ;; - --cleanup-only) - cleanup - exit 0 - ;; - -*) - log_error "Unknown option: $1" - show_help - exit 1 - ;; - *) - category="$1" - shift - ;; - esac - done - - # Set cleanup trap if requested - if [[ "$cleanup_on_exit" == "true" ]]; then - trap cleanup EXIT - fi - - # Execute based on category - log_section "Meldestelle Integration Test Suite" - log_info "Category: $category" - log_info "Cleanup on exit: $cleanup_on_exit" - - case "$category" in - "infrastructure") - test_infrastructure_services || exit 1 - ;; - "services") - test_application_services || exit 1 - ;; - "clients") - test_client_applications || exit 1 - ;; - "network") - test_network_connectivity || exit 1 - ;; - "all") - run_full_integration_test || exit 1 - ;; - "cleanup") - cleanup - exit 0 - ;; - *) - log_error "Unknown category: $category" - show_help - exit 1 - ;; - esac - - log_success "Integration test completed successfully!" -} - -# Execute main function with all arguments -main "$@" diff --git a/scripts/test/test-monitoring.sh b/scripts/test/test-monitoring.sh deleted file mode 100755 index 171def12..00000000 --- a/scripts/test/test-monitoring.sh +++ /dev/null @@ -1,505 +0,0 @@ -#!/bin/bash - -# ============================================================================= -# Enhanced Monitoring Setup Test Script -# ============================================================================= -# This script provides comprehensive testing of the monitoring setup including -# Prometheus, Grafana, and Alertmanager with improved error handling, retry -# logic, cleanup options, and configuration validation. -# ============================================================================= - -# Load common utilities -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=../utils/common.sh -source "$SCRIPT_DIR/../utils/common.sh" || { - echo "Error: Could not load common utilities from $SCRIPT_DIR/../utils/common.sh" - exit 1 -} - -# ============================================================================= -# Configuration -# ============================================================================= - -readonly COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" -readonly MONITORING_SERVICES=("prometheus" "grafana" "alertmanager") -readonly STARTUP_TIMEOUT=120 -readonly HEALTH_CHECK_TIMEOUT=30 -readonly RETRY_COUNT=3 -readonly RETRY_DELAY=10 - -# Service endpoints -readonly PROMETHEUS_URL="http://localhost:9090" -readonly GRAFANA_URL="http://localhost:3000" -readonly ALERTMANAGER_URL="http://localhost:9093" - -# Configuration files -readonly CONFIG_FILES=( - "config/monitoring/prometheus.yml" - "config/monitoring/grafana/provisioning/dashboards/dashboard.yml" - "config/monitoring/grafana/provisioning/datasources/prometheus.yml" -) - -# ============================================================================= -# Cleanup Function -# ============================================================================= - -cleanup() { - if [[ "${CLEANUP_SERVICES:-true}" == "true" ]]; then - log_info "Cleaning up monitoring services..." - - # Stop monitoring services - if docker-compose -f "$COMPOSE_FILE" ps | grep -q "prometheus\|grafana\|alertmanager"; then - log_info "Stopping monitoring services..." - docker-compose -f "$COMPOSE_FILE" stop "${MONITORING_SERVICES[@]}" >/dev/null 2>&1 || true - fi - - # Remove containers if requested - if [[ "${REMOVE_CONTAINERS:-false}" == "true" ]]; then - log_info "Removing monitoring containers..." - docker-compose -f "$COMPOSE_FILE" rm -f "${MONITORING_SERVICES[@]}" >/dev/null 2>&1 || true - fi - - log_info "Cleanup completed" - else - log_info "Cleanup skipped (CLEANUP_SERVICES=false)" - fi -} - -# ============================================================================= -# Configuration Validation Functions -# ============================================================================= - -validate_configuration() { - log_section "Configuration Validation" - - # Check docker-compose file - check_file "$COMPOSE_FILE" "Docker Compose file" || return 1 - - # Validate docker-compose syntax - log_info "Validating docker-compose syntax..." - if docker-compose -f "$COMPOSE_FILE" config >/dev/null 2>&1; then - print_status "OK" "Docker Compose file syntax is valid" - else - print_status "ERROR" "Docker Compose file has syntax errors" - return 1 - fi - - # Check required services are defined - for service in "${MONITORING_SERVICES[@]}"; do - if docker-compose -f "$COMPOSE_FILE" config | grep -q "^ ${service}:"; then - print_status "OK" "Service '$service' is defined in docker-compose" - else - print_status "ERROR" "Service '$service' is not defined in docker-compose" - fi - done - - # Check configuration files - for config_file in "${CONFIG_FILES[@]}"; do - if [[ -f "$config_file" ]]; then - print_status "OK" "Configuration file exists: $config_file" - - # Validate specific configuration files - case "$config_file" in - *prometheus.yml) - validate_prometheus_config "$config_file" - ;; - *grafana*) - validate_grafana_config "$config_file" - ;; - esac - else - print_status "WARNING" "Configuration file missing: $config_file" - fi - done - - return 0 -} - -validate_prometheus_config() { - local config_file=$1 - - # Check for required sections - if grep -q "global:" "$config_file" && grep -q "scrape_configs:" "$config_file"; then - print_status "OK" "Prometheus configuration has required sections" - else - print_status "WARNING" "Prometheus configuration may be incomplete" - fi - - # Check for application scrape targets - if grep -q "meldestelle" "$config_file"; then - print_status "OK" "Prometheus configured to scrape Meldestelle application" - else - print_status "WARNING" "Prometheus may not be configured to scrape application metrics" - fi -} - -validate_grafana_config() { - local config_file=$1 - - # Basic validation for Grafana config files - if [[ "$config_file" == *"datasources"* ]]; then - if grep -q "prometheus" "$config_file"; then - print_status "OK" "Grafana datasource configuration includes Prometheus" - else - print_status "WARNING" "Grafana datasource configuration may not include Prometheus" - fi - fi -} - -# ============================================================================= -# Service Management Functions -# ============================================================================= - -start_monitoring_services() { - log_section "Starting Monitoring Services" - - # Check Docker availability - check_docker || return 1 - check_docker_compose || return 1 - - # Start services with timeout - log_info "Starting monitoring stack: ${MONITORING_SERVICES[*]}" - - if run_with_timeout "$STARTUP_TIMEOUT" "Start monitoring services" \ - docker-compose -f "$COMPOSE_FILE" up -d "${MONITORING_SERVICES[@]}"; then - print_status "OK" "Monitoring services started successfully" - else - print_status "ERROR" "Failed to start monitoring services" - return 1 - fi - - # Wait for services to be ready - log_info "Waiting for services to be ready..." - sleep 15 # Initial wait for containers to initialize - - return 0 -} - -# ============================================================================= -# Health Check Functions -# ============================================================================= - -check_prometheus() { - log_section "Prometheus Health Check" - - local health_url="${PROMETHEUS_URL}/-/healthy" - local ready_url="${PROMETHEUS_URL}/-/ready" - - # Check if Prometheus is healthy - if check_http_endpoint "$health_url" "Prometheus health" "$HEALTH_CHECK_TIMEOUT" "$RETRY_COUNT"; then - print_status "OK" "Prometheus is healthy" - else - print_status "ERROR" "Prometheus health check failed" - return 1 - fi - - # Check if Prometheus is ready - if check_http_endpoint "$ready_url" "Prometheus readiness" "$HEALTH_CHECK_TIMEOUT" 1; then - print_status "OK" "Prometheus is ready" - else - print_status "WARNING" "Prometheus readiness check failed" - fi - - # Check Prometheus configuration - local config_url="${PROMETHEUS_URL}/api/v1/status/config" - if check_http_endpoint "$config_url" "Prometheus configuration" 10 1; then - print_status "OK" "Prometheus configuration is accessible" - else - print_status "WARNING" "Prometheus configuration endpoint not accessible" - fi - - # Check targets - check_prometheus_targets - - return 0 -} - -check_prometheus_targets() { - log_info "Checking Prometheus targets..." - - local targets_url="${PROMETHEUS_URL}/api/v1/targets" - local targets_response - - targets_response=$(curl -sf "$targets_url" 2>/dev/null || echo "") - - if [[ -n "$targets_response" ]]; then - # Check for application targets - if echo "$targets_response" | grep -q "meldestelle"; then - print_status "OK" "Prometheus can discover application targets" - else - print_status "WARNING" "No application targets found in Prometheus" - log_info "Make sure the application is running and exposing metrics" - fi - - # Check for healthy targets - local healthy_targets - healthy_targets=$(echo "$targets_response" | grep -o '"health":"up"' | wc -l) - if [[ "$healthy_targets" -gt 0 ]]; then - print_status "OK" "Found $healthy_targets healthy targets" - else - print_status "WARNING" "No healthy targets found" - fi - else - print_status "WARNING" "Could not retrieve Prometheus targets" - fi -} - -check_grafana() { - log_section "Grafana Health Check" - - local health_url="${GRAFANA_URL}/api/health" - local datasources_url="${GRAFANA_URL}/api/datasources" - - # Check if Grafana is healthy - if check_http_endpoint "$health_url" "Grafana health" "$HEALTH_CHECK_TIMEOUT" "$RETRY_COUNT"; then - print_status "OK" "Grafana is healthy" - - # Parse health response - local health_response - health_response=$(curl -sf "$health_url" 2>/dev/null || echo "") - if [[ "$health_response" == *"ok"* ]]; then - print_status "OK" "Grafana health status is OK" - else - print_status "WARNING" "Grafana health status unclear: $health_response" - fi - else - print_status "ERROR" "Grafana health check failed" - return 1 - fi - - # Check datasources (requires authentication, so this might fail) - log_info "Checking Grafana datasources..." - local datasources_response - datasources_response=$(curl -sf -u "admin:admin" "$datasources_url" 2>/dev/null || echo "") - - if [[ -n "$datasources_response" ]] && [[ "$datasources_response" != "Unauthorized" ]]; then - if echo "$datasources_response" | grep -q "prometheus"; then - print_status "OK" "Grafana has Prometheus datasource configured" - else - print_status "WARNING" "Prometheus datasource not found in Grafana" - fi - else - print_status "INFO" "Could not check Grafana datasources (authentication required)" - fi - - return 0 -} - -check_alertmanager() { - log_section "Alertmanager Health Check" - - local health_url="${ALERTMANAGER_URL}/-/healthy" - local ready_url="${ALERTMANAGER_URL}/-/ready" - local status_url="${ALERTMANAGER_URL}/api/v1/status" - - # Check if Alertmanager is healthy - if check_http_endpoint "$health_url" "Alertmanager health" "$HEALTH_CHECK_TIMEOUT" "$RETRY_COUNT"; then - print_status "OK" "Alertmanager is healthy" - else - print_status "ERROR" "Alertmanager health check failed" - return 1 - fi - - # Check if Alertmanager is ready - if check_http_endpoint "$ready_url" "Alertmanager readiness" "$HEALTH_CHECK_TIMEOUT" 1; then - print_status "OK" "Alertmanager is ready" - else - print_status "WARNING" "Alertmanager readiness check failed" - fi - - # Check Alertmanager status - if check_http_endpoint "$status_url" "Alertmanager status" 10 1; then - print_status "OK" "Alertmanager status endpoint is accessible" - else - print_status "WARNING" "Alertmanager status endpoint not accessible" - fi - - return 0 -} - -# ============================================================================= -# Integration Tests -# ============================================================================= - -test_monitoring_integration() { - log_section "Monitoring Integration Tests" - - # Test Prometheus-Grafana integration - log_info "Testing Prometheus-Grafana integration..." - - # Check if Prometheus metrics are accessible from Grafana's perspective - local prometheus_query_url="${PROMETHEUS_URL}/api/v1/query?query=up" - if check_http_endpoint "$prometheus_query_url" "Prometheus query API" 10 1; then - print_status "OK" "Prometheus query API is accessible for Grafana" - else - print_status "WARNING" "Prometheus query API may not be accessible for Grafana" - fi - - # Test alerting rules - log_info "Checking alerting rules..." - local rules_url="${PROMETHEUS_URL}/api/v1/rules" - local rules_response - rules_response=$(curl -sf "$rules_url" 2>/dev/null || echo "") - - if [[ -n "$rules_response" ]]; then - if echo "$rules_response" | grep -q "meldestelle"; then - print_status "OK" "Meldestelle alerting rules are loaded" - else - print_status "WARNING" "No Meldestelle-specific alerting rules found" - fi - else - print_status "WARNING" "Could not retrieve alerting rules" - fi - - return 0 -} - -# ============================================================================= -# Performance and Load Tests -# ============================================================================= - -test_monitoring_performance() { - log_section "Monitoring Performance Tests" - - # Test Prometheus query performance - log_info "Testing Prometheus query performance..." - - local start_time - local end_time - local duration - - start_time=$(date +%s%N) - curl -sf "${PROMETHEUS_URL}/api/v1/query?query=up" >/dev/null 2>&1 - local query_result=$? - end_time=$(date +%s%N) - - duration=$(( (end_time - start_time) / 1000000 )) # Convert to milliseconds - - if [[ $query_result -eq 0 ]]; then - if [[ $duration -lt 1000 ]]; then - print_status "OK" "Prometheus query performance is good (${duration}ms)" - else - print_status "WARNING" "Prometheus query performance is slow (${duration}ms)" - fi - else - print_status "WARNING" "Prometheus query performance test failed" - fi - - # Test Grafana response time - log_info "Testing Grafana response time..." - - start_time=$(date +%s%N) - curl -sf "${GRAFANA_URL}/api/health" >/dev/null 2>&1 - local grafana_result=$? - end_time=$(date +%s%N) - - duration=$(( (end_time - start_time) / 1000000 )) - - if [[ $grafana_result -eq 0 ]]; then - if [[ $duration -lt 2000 ]]; then - print_status "OK" "Grafana response time is good (${duration}ms)" - else - print_status "WARNING" "Grafana response time is slow (${duration}ms)" - fi - else - print_status "WARNING" "Grafana response time test failed" - fi - - return 0 -} - -# ============================================================================= -# Main Execution -# ============================================================================= - -show_usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " --no-cleanup Don't stop services after testing" - echo " --remove-containers Remove containers after testing" - echo " --config-only Only validate configuration, don't start services" - echo " --help Show this help message" - echo "" - echo "Environment Variables:" - echo " COMPOSE_FILE Docker compose file to use (default: docker-compose.yml)" - echo " CLEANUP_SERVICES Whether to cleanup services (default: true)" - echo " REMOVE_CONTAINERS Whether to remove containers (default: false)" -} - -main() { - # Parse command line arguments - while [[ $# -gt 0 ]]; do - case $1 in - --no-cleanup) - export CLEANUP_SERVICES=false - shift - ;; - --remove-containers) - export REMOVE_CONTAINERS=true - shift - ;; - --config-only) - local CONFIG_ONLY=true - shift - ;; - --help) - show_usage - exit 0 - ;; - *) - log_error "Unknown option: $1" - show_usage - exit 1 - ;; - esac - done - - log_section "Enhanced Monitoring Setup Test" - - log_info "Starting comprehensive monitoring tests..." - log_info "Compose file: $COMPOSE_FILE" - log_info "Test timestamp: $(date)" - - # Always validate configuration - validate_configuration || exit 1 - - # If config-only mode, exit after validation - if [[ "${CONFIG_ONLY:-false}" == "true" ]]; then - log_info "Configuration validation completed (config-only mode)" - print_summary "Monitoring Configuration Validation" - exit 0 - fi - - # Run all tests - local test_results=() - - start_monitoring_services && test_results+=("Startup: PASS") || test_results+=("Startup: FAIL") - check_prometheus && test_results+=("Prometheus: PASS") || test_results+=("Prometheus: FAIL") - check_grafana && test_results+=("Grafana: PASS") || test_results+=("Grafana: FAIL") - check_alertmanager && test_results+=("Alertmanager: PASS") || test_results+=("Alertmanager: FAIL") - test_monitoring_integration && test_results+=("Integration: PASS") || test_results+=("Integration: FAIL") - test_monitoring_performance && test_results+=("Performance: PASS") || test_results+=("Performance: FAIL") - - # Print test results summary - log_section "Test Results Summary" - for result in "${test_results[@]}"; do - if [[ "$result" == *"PASS" ]]; then - log_success "$result" - else - log_error "$result" - fi - done - - # Print access information - log_section "Monitoring Access Information" - log_info "Prometheus: ${PROMETHEUS_URL}" - log_info "Grafana: ${GRAFANA_URL} (default credentials: admin/admin)" - log_info "Alertmanager: ${ALERTMANAGER_URL}" - - # Print final summary - print_summary "Enhanced Monitoring Test" -} - -# Run main function -main "$@" diff --git a/scripts/test/test_database_initialization.sh b/scripts/test/test_database_initialization.sh deleted file mode 100755 index 1e35662d..00000000 --- a/scripts/test/test_database_initialization.sh +++ /dev/null @@ -1,650 +0,0 @@ -#!/bin/bash - -# ============================================================================= -# Enhanced Database Initialization Test Script -# ============================================================================= -# This script provides comprehensive testing of database initialization and -# configuration with actual connection testing, schema validation, performance -# testing, and cleanup capabilities. -# ============================================================================= - -# Load common utilities -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=../utils/common.sh -source "$SCRIPT_DIR/../utils/common.sh" || { - echo "Error: Could not load common utilities from $SCRIPT_DIR/../utils/common.sh" - exit 1 -} - -# ============================================================================= -# Configuration -# ============================================================================= - -readonly COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" -readonly DB_SERVICES=("postgres" "redis") -readonly BUILD_TIMEOUT=300 -readonly DB_STARTUP_TIMEOUT=120 -readonly CONNECTION_TIMEOUT=30 - -# Database configuration -readonly DB_HOST="${DB_HOST:-localhost}" -readonly DB_PORT="${DB_PORT:-5432}" -readonly DB_NAME="${DB_NAME:-meldestelle_test}" -readonly DB_USER="${DB_USER:-meldestelle_user}" -readonly DB_PASSWORD="${DB_PASSWORD:-meldestelle_password}" - -# Redis configuration -readonly REDIS_HOST="${REDIS_HOST:-localhost}" -readonly REDIS_PORT="${REDIS_PORT:-6379}" - -# Service modules -readonly SERVICE_MODULES=( - "infrastructure:gateway" - "horses:horses-service" - "events:events-service" - "masterdata:masterdata-service" - "members:members-service" -) - -# Test database name -readonly TEST_DB_NAME="meldestelle_test_$(date +%s)" - -# ============================================================================= -# Cleanup Function -# ============================================================================= - -cleanup() { - log_info "Cleaning up test environment..." - - # Drop test database if created - if [[ "${TEST_DB_CREATED:-false}" == "true" ]]; then - log_info "Dropping test database: $TEST_DB_NAME" - PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres \ - -c "DROP DATABASE IF EXISTS $TEST_DB_NAME;" >/dev/null 2>&1 || true - fi - - # Stop database services if started by this script - if [[ "${STARTED_DB_SERVICES:-false}" == "true" ]]; then - log_info "Stopping database services..." - docker-compose -f "$COMPOSE_FILE" stop "${DB_SERVICES[@]}" >/dev/null 2>&1 || true - fi - - # Clean up temporary files - rm -f /tmp/db_test_*.sql /tmp/db_performance_*.log 2>/dev/null || true - - log_info "Cleanup completed" -} - -# ============================================================================= -# Database Setup Functions -# ============================================================================= - -setup_database_services() { - log_section "Database Services Setup" - - # Check Docker availability - check_docker || return 1 - check_docker_compose || return 1 - - # Start database services - log_info "Starting database services: ${DB_SERVICES[*]}" - - if run_with_timeout "$DB_STARTUP_TIMEOUT" "Start database services" \ - docker-compose -f "$COMPOSE_FILE" up -d "${DB_SERVICES[@]}"; then - STARTED_DB_SERVICES=true - print_status "OK" "Database services started successfully" - else - print_status "ERROR" "Failed to start database services" - return 1 - fi - - # Wait for PostgreSQL to be ready - if wait_for_service "PGPASSWORD=$DB_PASSWORD pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER" \ - "PostgreSQL" "$CONNECTION_TIMEOUT" 5; then - print_status "OK" "PostgreSQL is ready" - else - print_status "ERROR" "PostgreSQL failed to become ready" - return 1 - fi - - # Wait for Redis to be ready - if wait_for_service "redis-cli -h $REDIS_HOST -p $REDIS_PORT ping | grep -q PONG" \ - "Redis" "$CONNECTION_TIMEOUT" 5; then - print_status "OK" "Redis is ready" - else - print_status "ERROR" "Redis failed to become ready" - return 1 - fi - - return 0 -} - -# ============================================================================= -# Environment Validation Functions -# ============================================================================= - -validate_environment() { - log_section "Environment Validation" - - # Load environment file - load_env_file - - # Validate required environment variables - local required_vars=( - "DB_HOST" "DB_PORT" "DB_NAME" "DB_USER" "DB_PASSWORD" - "REDIS_HOST" "REDIS_PORT" - ) - - validate_env_vars "${required_vars[@]}" || return 1 - - # Check for required tools - local required_tools=("psql" "redis-cli") - for tool in "${required_tools[@]}"; do - if command_exists "$tool"; then - print_status "OK" "$tool is available" - else - print_status "WARNING" "$tool is not available - some tests may be skipped" - fi - done - - return 0 -} - -# ============================================================================= -# Build Testing Functions -# ============================================================================= - -test_service_builds() { - log_section "Service Build Testing" - - local build_results=() - - # Test each service module build - for module in "${SERVICE_MODULES[@]}"; do - log_info "Building module: $module" - - if run_with_timeout "$BUILD_TIMEOUT" "Build $module" \ - ./gradlew ":${module}:build" -x test; then - print_status "OK" "$module builds successfully" - build_results+=("$module: PASS") - else - print_status "ERROR" "$module build failed" - build_results+=("$module: FAIL") - fi - done - - # Summary of build results - log_info "Build Results Summary:" - for result in "${build_results[@]}"; do - if [[ "$result" == *"PASS" ]]; then - log_success "$result" - else - log_error "$result" - fi - done - - # Check if any builds failed - if echo "${build_results[*]}" | grep -q "FAIL"; then - print_status "ERROR" "One or more service builds failed" - return 1 - else - print_status "OK" "All service builds successful" - return 0 - fi -} - -# ============================================================================= -# Database Connection Testing Functions -# ============================================================================= - -test_database_connections() { - log_section "Database Connection Testing" - - # Test PostgreSQL connection - test_postgresql_connection || return 1 - - # Test Redis connection - test_redis_connection || return 1 - - return 0 -} - -test_postgresql_connection() { - log_info "Testing PostgreSQL connection..." - - # Basic connection test - if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres \ - -c "SELECT version();" >/dev/null 2>&1; then - print_status "OK" "PostgreSQL connection successful" - else - print_status "ERROR" "PostgreSQL connection failed" - return 1 - fi - - # Test database creation - log_info "Testing database creation..." - if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres \ - -c "CREATE DATABASE $TEST_DB_NAME;" >/dev/null 2>&1; then - TEST_DB_CREATED=true - print_status "OK" "Test database created successfully" - else - print_status "ERROR" "Failed to create test database" - return 1 - fi - - # Test connection to new database - if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -c "SELECT current_database();" >/dev/null 2>&1; then - print_status "OK" "Connection to test database successful" - else - print_status "ERROR" "Failed to connect to test database" - return 1 - fi - - return 0 -} - -test_redis_connection() { - log_info "Testing Redis connection..." - - # Basic connection test - if redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ping | grep -q "PONG"; then - print_status "OK" "Redis connection successful" - else - print_status "ERROR" "Redis connection failed" - return 1 - fi - - # Test basic operations - local test_key="test_key_$(date +%s)" - local test_value="test_value_$(date +%s)" - - if redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" set "$test_key" "$test_value" >/dev/null 2>&1; then - print_status "OK" "Redis SET operation successful" - else - print_status "ERROR" "Redis SET operation failed" - return 1 - fi - - local retrieved_value - retrieved_value=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" get "$test_key" 2>/dev/null) - if [[ "$retrieved_value" == "$test_value" ]]; then - print_status "OK" "Redis GET operation successful" - else - print_status "ERROR" "Redis GET operation failed" - return 1 - fi - - # Cleanup test key - redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" del "$test_key" >/dev/null 2>&1 || true - - return 0 -} - -# ============================================================================= -# Schema Validation Functions -# ============================================================================= - -test_schema_initialization() { - log_section "Schema Initialization Testing" - - # Create test schema - create_test_schema || return 1 - - # Validate schema structure - validate_schema_structure || return 1 - - # Test schema constraints - test_schema_constraints || return 1 - - return 0 -} - -create_test_schema() { - log_info "Creating test schema..." - - # Create a simple test table - local create_table_sql=" - CREATE TABLE IF NOT EXISTS test_table ( - id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - email VARCHAR(255) UNIQUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - " - - if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -c "$create_table_sql" >/dev/null 2>&1; then - print_status "OK" "Test schema created successfully" - return 0 - else - print_status "ERROR" "Failed to create test schema" - return 1 - fi -} - -validate_schema_structure() { - log_info "Validating schema structure..." - - # Check if table exists - local table_exists - table_exists=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'test_table';" 2>/dev/null | tr -d ' ') - - if [[ "$table_exists" == "1" ]]; then - print_status "OK" "Test table exists in schema" - else - print_status "ERROR" "Test table not found in schema" - return 1 - fi - - # Check table columns - local column_count - column_count=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -t -c "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'test_table';" 2>/dev/null | tr -d ' ') - - if [[ "$column_count" == "4" ]]; then - print_status "OK" "Test table has correct number of columns" - else - print_status "WARNING" "Test table column count unexpected: $column_count" - fi - - return 0 -} - -test_schema_constraints() { - log_info "Testing schema constraints..." - - # Test NOT NULL constraint - if ! PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -c "INSERT INTO test_table (name) VALUES (NULL);" >/dev/null 2>&1; then - print_status "OK" "NOT NULL constraint working correctly" - else - print_status "WARNING" "NOT NULL constraint may not be working" - fi - - # Test UNIQUE constraint - PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -c "INSERT INTO test_table (name, email) VALUES ('Test User', 'test@example.com');" >/dev/null 2>&1 - - if ! PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -c "INSERT INTO test_table (name, email) VALUES ('Another User', 'test@example.com');" >/dev/null 2>&1; then - print_status "OK" "UNIQUE constraint working correctly" - else - print_status "WARNING" "UNIQUE constraint may not be working" - fi - - return 0 -} - -# ============================================================================= -# Performance Testing Functions -# ============================================================================= - -test_database_performance() { - log_section "Database Performance Testing" - - # Test PostgreSQL performance - test_postgresql_performance || return 1 - - # Test Redis performance - test_redis_performance || return 1 - - return 0 -} - -test_postgresql_performance() { - log_info "Testing PostgreSQL performance..." - - # Insert performance test - local start_time - local end_time - local duration - - start_time=$(date +%s%N) - - # Insert 1000 test records - for i in {1..1000}; do - PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -c "INSERT INTO test_table (name, email) VALUES ('User $i', 'user$i@example.com');" >/dev/null 2>&1 - done - - end_time=$(date +%s%N) - duration=$(( (end_time - start_time) / 1000000 )) # Convert to milliseconds - - if [[ $duration -lt 10000 ]]; then # Less than 10 seconds - print_status "OK" "PostgreSQL insert performance is good (${duration}ms for 1000 records)" - else - print_status "WARNING" "PostgreSQL insert performance is slow (${duration}ms for 1000 records)" - fi - - # Query performance test - start_time=$(date +%s%N) - - local record_count - record_count=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" \ - -t -c "SELECT COUNT(*) FROM test_table;" 2>/dev/null | tr -d ' ') - - end_time=$(date +%s%N) - duration=$(( (end_time - start_time) / 1000000 )) - - if [[ $duration -lt 1000 ]] && [[ "$record_count" -gt 0 ]]; then - print_status "OK" "PostgreSQL query performance is good (${duration}ms, $record_count records)" - else - print_status "WARNING" "PostgreSQL query performance may be suboptimal (${duration}ms)" - fi - - return 0 -} - -test_redis_performance() { - log_info "Testing Redis performance..." - - # Set performance test - local start_time - local end_time - local duration - - start_time=$(date +%s%N) - - # Set 1000 test keys - for i in {1..1000}; do - redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" set "perf_test_$i" "value_$i" >/dev/null 2>&1 - done - - end_time=$(date +%s%N) - duration=$(( (end_time - start_time) / 1000000 )) - - if [[ $duration -lt 5000 ]]; then # Less than 5 seconds - print_status "OK" "Redis SET performance is good (${duration}ms for 1000 keys)" - else - print_status "WARNING" "Redis SET performance is slow (${duration}ms for 1000 keys)" - fi - - # Get performance test - start_time=$(date +%s%N) - - for i in {1..100}; do - redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" get "perf_test_$i" >/dev/null 2>&1 - done - - end_time=$(date +%s%N) - duration=$(( (end_time - start_time) / 1000000 )) - - if [[ $duration -lt 1000 ]]; then # Less than 1 second - print_status "OK" "Redis GET performance is good (${duration}ms for 100 keys)" - else - print_status "WARNING" "Redis GET performance is slow (${duration}ms for 100 keys)" - fi - - # Cleanup performance test keys - redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" eval " - for i=1,1000 do - redis.call('del', 'perf_test_' .. i) - end - " 0 >/dev/null 2>&1 || true - - return 0 -} - -# ============================================================================= -# Integration Testing Functions -# ============================================================================= - -test_database_integration() { - log_section "Database Integration Testing" - - # Test DatabaseFactory usage - test_database_factory_integration || return 1 - - # Test service-specific database initialization - test_service_database_integration || return 1 - - return 0 -} - -test_database_factory_integration() { - log_info "Testing DatabaseFactory integration..." - - # Check for direct Database.connect() calls in gateway - local direct_connects - direct_connects=$(find infrastructure/gateway/src -name "*.kt" -exec grep -l "Database\.connect(" {} \; 2>/dev/null || true) - - if [[ -z "$direct_connects" ]]; then - print_status "OK" "No direct Database.connect() calls found in gateway" - else - print_status "ERROR" "Found direct Database.connect() calls in gateway: $direct_connects" - return 1 - fi - - # Check for DatabaseFactory usage in gateway - local factory_usage - factory_usage=$(find infrastructure/gateway/src -name "*.kt" -exec grep -l "DatabaseFactory" {} \; 2>/dev/null || true) - - if [[ -n "$factory_usage" ]]; then - print_status "OK" "DatabaseFactory usage found in gateway" - else - print_status "WARNING" "No DatabaseFactory usage found in gateway" - fi - - return 0 -} - -test_service_database_integration() { - log_info "Testing service database integration..." - - # Check that services don't call DatabaseFactory.init() - local factory_inits - factory_inits=$(find . -path "*/service/config/*" -name "*.kt" -exec grep -l "DatabaseFactory\.init(" {} \; 2>/dev/null || true) - - if [[ -z "$factory_inits" ]]; then - print_status "OK" "No DatabaseFactory.init() calls found in service configurations" - else - print_status "ERROR" "Found DatabaseFactory.init() calls in service configurations: $factory_inits" - return 1 - fi - - return 0 -} - -# ============================================================================= -# Main Execution -# ============================================================================= - -show_usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " --skip-builds Skip service build testing" - echo " --skip-performance Skip performance testing" - echo " --keep-test-data Don't cleanup test data after testing" - echo " --help Show this help message" - echo "" - echo "Environment Variables:" - echo " DB_HOST Database host (default: localhost)" - echo " DB_PORT Database port (default: 5432)" - echo " DB_NAME Database name (default: meldestelle_test)" - echo " DB_USER Database user (default: meldestelle_user)" - echo " DB_PASSWORD Database password (default: meldestelle_password)" - echo " REDIS_HOST Redis host (default: localhost)" - echo " REDIS_PORT Redis port (default: 6379)" -} - -main() { - local SKIP_BUILDS=false - local SKIP_PERFORMANCE=false - local KEEP_TEST_DATA=false - - # Parse command line arguments - while [[ $# -gt 0 ]]; do - case $1 in - --skip-builds) - SKIP_BUILDS=true - shift - ;; - --skip-performance) - SKIP_PERFORMANCE=true - shift - ;; - --keep-test-data) - KEEP_TEST_DATA=true - shift - ;; - --help) - show_usage - exit 0 - ;; - *) - log_error "Unknown option: $1" - show_usage - exit 1 - ;; - esac - done - - log_section "Enhanced Database Initialization Test" - - log_info "Starting comprehensive database tests..." - log_info "Test database: $TEST_DB_NAME" - log_info "Test timestamp: $(date)" - - # Run all tests - local test_results=() - - validate_environment && test_results+=("Environment: PASS") || test_results+=("Environment: FAIL") - setup_database_services && test_results+=("Setup: PASS") || test_results+=("Setup: FAIL") - - if [[ "$SKIP_BUILDS" != "true" ]]; then - test_service_builds && test_results+=("Builds: PASS") || test_results+=("Builds: FAIL") - else - test_results+=("Builds: SKIPPED") - fi - - test_database_connections && test_results+=("Connections: PASS") || test_results+=("Connections: FAIL") - test_schema_initialization && test_results+=("Schema: PASS") || test_results+=("Schema: FAIL") - test_database_integration && test_results+=("Integration: PASS") || test_results+=("Integration: FAIL") - - if [[ "$SKIP_PERFORMANCE" != "true" ]]; then - test_database_performance && test_results+=("Performance: PASS") || test_results+=("Performance: FAIL") - else - test_results+=("Performance: SKIPPED") - fi - - # Print test results summary - log_section "Test Results Summary" - for result in "${test_results[@]}"; do - if [[ "$result" == *"PASS" ]]; then - log_success "$result" - elif [[ "$result" == *"SKIPPED" ]]; then - log_info "$result" - else - log_error "$result" - fi - done - - # Print final summary - print_summary "Enhanced Database Initialization Test" -} - -# Run main function -main "$@" diff --git a/scripts/test/test_gateway.sh b/scripts/test/test_gateway.sh deleted file mode 100755 index 6ad8a45b..00000000 --- a/scripts/test/test_gateway.sh +++ /dev/null @@ -1,374 +0,0 @@ -#!/bin/bash - -# ============================================================================= -# Enhanced API Gateway Test Script -# ============================================================================= -# This script provides comprehensive testing of the API Gateway implementation -# including build validation, runtime testing, endpoint health checks, and -# performance measurements. -# ============================================================================= - -# Load common utilities -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=../utils/common.sh -source "$SCRIPT_DIR/../utils/common.sh" || { - echo "Error: Could not load common utilities from $SCRIPT_DIR/../utils/common.sh" - exit 1 -} - -# ============================================================================= -# Configuration -# ============================================================================= - -readonly GATEWAY_MODULE="infrastructure:gateway" -readonly GATEWAY_JAR="infrastructure/gateway/build/libs/gateway-1.0.0.jar" -readonly GATEWAY_PORT="${GATEWAY_PORT:-8081}" -readonly GATEWAY_HOST="${GATEWAY_HOST:-localhost}" -readonly GATEWAY_BASE_URL="http://${GATEWAY_HOST}:${GATEWAY_PORT}" - -# Test endpoints -readonly TEST_ENDPOINTS=( - "/health" - "/metrics" - "/api/masterdata/health" - "/api/horses/health" - "/api/events/health" - "/api/members/health" -) - -# Service discovery endpoints -readonly SERVICE_ENDPOINTS=( - "masterdata:8081" - "horses:8082 - "events:8083" - "members:8084" -) - -# ============================================================================= -# Cleanup Function -# ============================================================================= - -cleanup() { - log_info "Cleaning up test environment..." - - # Stop gateway if running - if [[ -n "${GATEWAY_PID:-}" ]]; then - log_info "Stopping gateway process (PID: $GATEWAY_PID)..." - kill "$GATEWAY_PID" 2>/dev/null || true - wait "$GATEWAY_PID" 2>/dev/null || true - fi - - # Stop Docker services if started by this script - if [[ "${STARTED_SERVICES:-false}" == "true" ]]; then - log_info "Stopping test services..." - docker-compose down --remove-orphans >/dev/null 2>&1 || true - fi - - log_info "Cleanup completed" -} - -# ============================================================================= -# Test Functions -# ============================================================================= - -# Test 1: Build Validation -test_build_validation() { - log_section "Test 1: Build Validation" - - log_info "Building gateway module..." - if run_with_timeout 300 "Gateway build" ./gradlew ":${GATEWAY_MODULE}:build" -x test; then - print_status "OK" "Gateway builds successfully" - else - print_status "ERROR" "Gateway build failed" - return 1 - fi - - # Check if JAR file exists - check_file "$GATEWAY_JAR" "Gateway JAR file" - - # Validate JAR contents - if jar -tf "$GATEWAY_JAR" | grep -q "Application.class"; then - print_status "OK" "Gateway JAR contains main application class" - else - print_status "WARNING" "Gateway JAR may not contain expected main class" - fi - - return 0 -} - -# Test 2: Configuration Validation -test_configuration_validation() { - log_section "Test 2: Configuration Validation" - - # Check required configuration files - local config_files=( - "infrastructure/gateway/src/main/resources/application.conf" - "infrastructure/gateway/src/main/resources/logback.xml" - ) - - for config_file in "${config_files[@]}"; do - check_file "$config_file" "Configuration file" - done - - # Validate environment variables - local required_vars=( - "API_HOST" - "API_PORT" - "CONSUL_HOST" - "CONSUL_PORT" - ) - - load_env_file - - local missing_vars=() - for var in "${required_vars[@]}"; do - if [[ -z "${!var:-}" ]]; then - missing_vars+=("$var") - fi - done - - if [[ ${#missing_vars[@]} -gt 0 ]]; then - print_status "WARNING" "Missing environment variables: ${missing_vars[*]} (using defaults)" - else - print_status "OK" "All required environment variables are set" - fi - - return 0 -} - -# Test 3: Service Dependencies -test_service_dependencies() { - log_section "Test 3: Service Dependencies" - - # Check Docker availability - check_docker || return 1 - check_docker_compose || return 1 - - # Start required services for testing - log_info "Starting required services for gateway testing..." - - local required_services=( - "postgres" - "redis" - "consul" - ) - - if start_docker_services "${required_services[@]}"; then - STARTED_SERVICES=true - print_status "OK" "Required services started successfully" - else - print_status "ERROR" "Failed to start required services" - return 1 - fi - - # Wait for Consul to be ready (service discovery) - if check_service_port 8500 "Consul" 60; then - print_status "OK" "Consul service discovery is ready" - else - print_status "WARNING" "Consul not available - service discovery may not work" - fi - - return 0 -} - -# Test 4: Gateway Runtime Testing -test_gateway_runtime() { - log_section "Test 4: Gateway Runtime Testing" - - # Start the gateway - log_info "Starting API Gateway..." - - # Set environment for gateway - export API_HOST="${GATEWAY_HOST}" - export API_PORT="${GATEWAY_PORT}" - export CONSUL_HOST="${CONSUL_HOST:-localhost}" - export CONSUL_PORT="${CONSUL_PORT:-8500}" - - # Start gateway in background - java -jar "$GATEWAY_JAR" > gateway.log 2>&1 & - GATEWAY_PID=$! - - log_info "Gateway started with PID: $GATEWAY_PID" - - # Wait for gateway to be ready - if wait_for_service "curl -sf ${GATEWAY_BASE_URL}/health" "API Gateway" 120 5; then - print_status "OK" "API Gateway is running and healthy" - else - print_status "ERROR" "API Gateway failed to start or become healthy" - if [[ -f "gateway.log" ]]; then - log_error "Gateway logs:" - tail -20 gateway.log >&2 - fi - return 1 - fi - - return 0 -} - -# Test 5: Endpoint Health Checks -test_endpoint_health() { - log_section "Test 5: Endpoint Health Checks" - - for endpoint in "${TEST_ENDPOINTS[@]}"; do - local url="${GATEWAY_BASE_URL}${endpoint}" - - if check_http_endpoint "$url" "Gateway endpoint $endpoint" 10 2; then - # Additional validation for specific endpoints - case $endpoint in - "/health") - local health_response - health_response=$(curl -sf "$url" 2>/dev/null || echo "") - if [[ "$health_response" == *"UP"* ]] || [[ "$health_response" == *"healthy"* ]]; then - print_status "OK" "Health endpoint returns positive status" - else - print_status "WARNING" "Health endpoint response unclear: $health_response" - fi - ;; - "/metrics") - if curl -sf "$url" | grep -q "jvm_"; then - print_status "OK" "Metrics endpoint returns JVM metrics" - else - print_status "WARNING" "Metrics endpoint may not be properly configured" - fi - ;; - esac - fi - done - - return 0 -} - -# Test 6: Service Discovery Integration -test_service_discovery() { - log_section "Test 6: Service Discovery Integration" - - # Check if gateway can discover services - local discovery_url="${GATEWAY_BASE_URL}/admin/services" - - if check_http_endpoint "$discovery_url" "Service discovery endpoint" 10 1; then - local services_response - services_response=$(curl -sf "$discovery_url" 2>/dev/null || echo "[]") - - if [[ "$services_response" != "[]" ]] && [[ "$services_response" != "" ]]; then - print_status "OK" "Service discovery is working - found services" - log_debug "Discovered services: $services_response" - else - print_status "WARNING" "Service discovery endpoint accessible but no services found" - fi - else - print_status "WARNING" "Service discovery endpoint not accessible" - fi - - return 0 -} - -# Test 7: Load and Performance Testing -test_performance() { - log_section "Test 7: Load and Performance Testing" - - if ! command_exists ab; then - print_status "WARNING" "Apache Bench (ab) not available - skipping performance tests" - return 0 - fi - - local health_url="${GATEWAY_BASE_URL}/health" - - log_info "Running basic load test (100 requests, concurrency 10)..." - - local ab_output - ab_output=$(ab -n 100 -c 10 "$health_url" 2>/dev/null || echo "") - - if [[ -n "$ab_output" ]]; then - local requests_per_sec - requests_per_sec=$(echo "$ab_output" | grep "Requests per second" | awk '{print $4}') - - local mean_time - mean_time=$(echo "$ab_output" | grep "Time per request" | head -1 | awk '{print $4}') - - if [[ -n "$requests_per_sec" ]] && [[ -n "$mean_time" ]]; then - print_status "OK" "Performance test completed - ${requests_per_sec} req/sec, ${mean_time}ms avg" - - # Basic performance validation - if (( $(echo "$requests_per_sec > 50" | bc -l 2>/dev/null || echo 0) )); then - print_status "OK" "Gateway performance is acceptable" - else - print_status "WARNING" "Gateway performance may be suboptimal" - fi - else - print_status "WARNING" "Could not parse performance test results" - fi - else - print_status "WARNING" "Performance test failed to run" - fi - - return 0 -} - -# Test 8: Error Handling and Resilience -test_error_handling() { - log_section "Test 8: Error Handling and Resilience" - - # Test 404 handling - local not_found_url="${GATEWAY_BASE_URL}/nonexistent" - local response_code - response_code=$(curl -sf -o /dev/null -w "%{http_code}" "$not_found_url" 2>/dev/null || echo "000") - - if [[ "$response_code" == "404" ]]; then - print_status "OK" "Gateway properly handles 404 errors" - else - print_status "WARNING" "Gateway 404 handling unclear (got $response_code)" - fi - - # Test service unavailable handling - local unavailable_service_url="${GATEWAY_BASE_URL}/api/unavailable-service/test" - response_code=$(curl -sf -o /dev/null -w "%{http_code}" "$unavailable_service_url" 2>/dev/null || echo "000") - - if [[ "$response_code" == "503" ]] || [[ "$response_code" == "502" ]]; then - print_status "OK" "Gateway properly handles unavailable services" - else - print_status "WARNING" "Gateway service unavailable handling unclear (got $response_code)" - fi - - return 0 -} - -# ============================================================================= -# Main Execution -# ============================================================================= - -main() { - log_section "Enhanced API Gateway Testing" - - log_info "Starting comprehensive API Gateway tests..." - log_info "Gateway URL: $GATEWAY_BASE_URL" - log_info "Test timestamp: $(date)" - - # Run all tests - local test_results=() - - test_build_validation && test_results+=("Build: PASS") || test_results+=("Build: FAIL") - test_configuration_validation && test_results+=("Config: PASS") || test_results+=("Config: FAIL") - test_service_dependencies && test_results+=("Dependencies: PASS") || test_results+=("Dependencies: FAIL") - test_gateway_runtime && test_results+=("Runtime: PASS") || test_results+=("Runtime: FAIL") - test_endpoint_health && test_results+=("Endpoints: PASS") || test_results+=("Endpoints: FAIL") - test_service_discovery && test_results+=("Discovery: PASS") || test_results+=("Discovery: FAIL") - test_performance && test_results+=("Performance: PASS") || test_results+=("Performance: FAIL") - test_error_handling && test_results+=("ErrorHandling: PASS") || test_results+=("ErrorHandling: FAIL") - - # Print test results summary - log_section "Test Results Summary" - for result in "${test_results[@]}"; do - if [[ "$result" == *"PASS" ]]; then - log_success "$result" - else - log_error "$result" - fi - done - - # Print final summary - print_summary "Enhanced API Gateway Test" -} - -# Run main function -# shellcheck disable=SC1073 -main "$@" diff --git a/scripts/troubleshooting/keycloak_repro.sh b/scripts/troubleshooting/keycloak_repro.sh deleted file mode 100644 index 98e1591d..00000000 --- a/scripts/troubleshooting/keycloak_repro.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Reproduce Keycloak restart issue and capture logs -# Usage: -# ./scripts/troubleshooting/keycloak_repro.sh -# Optional env: -# COMPOSE_FILE (defaults to docker-compose.yml) -# LOG_DIR (defaults to ./logs/troubleshooting) - -COMPOSE_FILE=${COMPOSE_FILE:-docker-compose.yml} -LOG_DIR=${LOG_DIR:-./logs/troubleshooting} -mkdir -p "$LOG_DIR" - -echo "[INFO] Using compose file: $COMPOSE_FILE" -echo "[INFO] Log directory: $LOG_DIR" - -# Show effective compose config for debugging -{ - echo "# docker compose config output"; - docker compose -f "$COMPOSE_FILE" config; -} >"$LOG_DIR/compose-config.txt" 2>&1 || true - -# Bring up Postgres first -echo "[INFO] Starting Postgres..." -docker compose -f "$COMPOSE_FILE" up -d postgres - -# Wait for Postgres health (max ~60s) -echo "[INFO] Waiting for Postgres to become healthy..." -for i in {1..30}; do - STATUS=$(docker inspect --format='{{json .State.Health.Status}}' meldestelle-postgres 2>/dev/null || echo '"unknown"') - if [[ $STATUS == '"healthy"' ]]; then - echo "[INFO] Postgres is healthy" - break - fi - if [[ $i -eq 30 ]]; then - echo "[WARN] Postgres not healthy after timeout. Proceeding anyway. Status: $STATUS" - fi - sleep 2 -done - -# Start Keycloak -echo "[INFO] Starting Keycloak..." -docker compose -f "$COMPOSE_FILE" up -d keycloak || true - -# Capture initial logs snapshot (non-follow) for both services -echo "[INFO] Capturing logs snapshot..." -docker compose -f "$COMPOSE_FILE" logs --no-log-prefix postgres >"$LOG_DIR/postgres.log" 2>&1 || true -# Capture more lines for keycloak as issues are often verbose -docker compose -f "$COMPOSE_FILE" logs --no-log-prefix --tail=500 keycloak >"$LOG_DIR/keycloak.log" 2>&1 || true - -# Show helpful status -echo "[INFO] docker compose ps" -docker compose -f "$COMPOSE_FILE" ps | tee "$LOG_DIR/compose-ps.txt" - -echo "[INFO] Done. Logs written to: $LOG_DIR" -echo "[INFO] To follow live logs, run:" -echo " docker compose -f $COMPOSE_FILE logs -f keycloak" -echo " docker compose -f $COMPOSE_FILE logs -f postgres" diff --git a/scripts/troubleshooting/logs/troubleshooting/compose-config.txt b/scripts/troubleshooting/logs/troubleshooting/compose-config.txt deleted file mode 100644 index 8ada3668..00000000 --- a/scripts/troubleshooting/logs/troubleshooting/compose-config.txt +++ /dev/null @@ -1,2 +0,0 @@ -# docker compose config output -open /home/stefan-mo/WsMeldestelle/Meldestelle/scripts/troubleshooting/docker-compose.yml: no such file or directory diff --git a/scripts/utils/common.sh b/scripts/utils/common.sh deleted file mode 100755 index 623fdca6..00000000 --- a/scripts/utils/common.sh +++ /dev/null @@ -1,462 +0,0 @@ -#!/bin/bash - -# ============================================================================= -# Common Utilities Library for Meldestelle Shell Scripts -# ============================================================================= -# This library provides common functions for logging, error handling, cleanup, -# and other utilities used across all shell scripts in the project. -# -# Usage: source "$(dirname "$0")/utils/common.sh" || source "scripts/utils/common.sh" -# ============================================================================= - -# Prevent multiple sourcing -if [[ "${COMMON_UTILS_LOADED:-}" == "true" ]]; then - return 0 -fi -COMMON_UTILS_LOADED=true - -# ============================================================================= -# Configuration and Constants -# ============================================================================= - -# Colors for output -readonly RED='\033[0;31m' -readonly GREEN='\033[0;32m' -readonly YELLOW='\033[1;33m' -readonly BLUE='\033[0;34m' -readonly PURPLE='\033[0;35m' -readonly CYAN='\033[0;36m' -readonly WHITE='\033[1;37m' -readonly NC='\033[0m' # No Color - -# Symbols -readonly CHECK_MARK="✓" -readonly CROSS_MARK="✗" -readonly WARNING_MARK="⚠" -readonly INFO_MARK="ℹ" -readonly ARROW_MARK="→" - -# Global counters -ERRORS=0 -WARNINGS=0 -CHECKS=0 -START_TIME=$(date +%s) - -# ============================================================================= -# Error Handling and Cleanup -# ============================================================================= - -# Enhanced error handling -set -euo pipefail - -# Error trap function -error_trap() { - local exit_code=$? - local line_number=$1 - log_error "Script failed at line $line_number with exit code $exit_code" - cleanup_on_exit - exit $exit_code -} - -# Set error trap -trap 'error_trap $LINENO' ERR - -# Cleanup function (can be overridden by scripts) -cleanup_on_exit() { - if declare -f cleanup > /dev/null; then - log_info "Running cleanup..." - cleanup - fi -} - -# Set exit trap -trap cleanup_on_exit EXIT - -# ============================================================================= -# Logging Functions -# ============================================================================= - -# Get timestamp -get_timestamp() { - date '+%Y-%m-%d %H:%M:%S' -} - -# Base logging function -log_base() { - local level=$1 - local color=$2 - local symbol=$3 - local message=$4 - local timestamp=$(get_timestamp) - - echo -e "${color}[${timestamp}] ${symbol} [${level}]${NC} ${message}" >&2 -} - -# Info logging -log_info() { - log_base "INFO" "$BLUE" "$INFO_MARK" "$1" -} - -# Success logging -log_success() { - log_base "SUCCESS" "$GREEN" "$CHECK_MARK" "$1" -} - -# Warning logging -log_warning() { - log_base "WARNING" "$YELLOW" "$WARNING_MARK" "$1" - ((WARNINGS++)) -} - -# Error logging -log_error() { - log_base "ERROR" "$RED" "$CROSS_MARK" "$1" - ((ERRORS++)) -} - -# Debug logging (only if DEBUG=true) -log_debug() { - if [[ "${DEBUG:-false}" == "true" ]]; then - log_base "DEBUG" "$PURPLE" "🐛" "$1" - fi -} - -# Progress logging -log_progress() { - log_base "PROGRESS" "$CYAN" "$ARROW_MARK" "$1" -} - -# Section header -log_section() { - local title=$1 - local line=$(printf '=%.0s' {1..80}) - echo -e "\n${BLUE}${line}${NC}" - echo -e "${BLUE}${title}${NC}" - echo -e "${BLUE}${line}${NC}\n" -} - -# ============================================================================= -# Status and Validation Functions -# ============================================================================= - -# Print status with counter increment -print_status() { - local status=$1 - local message=$2 - ((CHECKS++)) - - case $status in - "OK"|"SUCCESS") - log_success "$message" - ;; - "WARNING"|"WARN") - log_warning "$message" - ;; - "ERROR"|"FAIL") - log_error "$message" - ;; - "INFO") - log_info "$message" - ;; - *) - log_info "$message" - ;; - esac -} - -# Check if command exists -command_exists() { - command -v "$1" >/dev/null 2>&1 -} - -# Check if file exists with logging -check_file() { - local file=$1 - local description=${2:-"File"} - - if [[ -f "$file" ]]; then - print_status "OK" "$description exists: $file" - return 0 - else - print_status "ERROR" "$description not found: $file" - return 1 - fi -} - -# Check if directory exists with logging -check_directory() { - local dir=$1 - local description=${2:-"Directory"} - - if [[ -d "$dir" ]]; then - print_status "OK" "$description exists: $dir" - return 0 - else - print_status "ERROR" "$description not found: $dir" - return 1 - fi -} - -# Check if service is running on port -check_service_port() { - local port=$1 - local service_name=${2:-"Service"} - local timeout=${3:-30} - - log_info "Checking if $service_name is running on port $port..." - - if timeout "$timeout" bash -c "until nc -z localhost $port; do sleep 1; done" 2>/dev/null; then - print_status "OK" "$service_name is running on port $port" - return 0 - else - print_status "ERROR" "$service_name is not running on port $port (timeout: ${timeout}s)" - return 1 - fi -} - -# Check HTTP endpoint with retry -check_http_endpoint() { - local url=$1 - local service_name=${2:-"Service"} - local timeout=${3:-30} - local retry_count=${4:-3} - - log_info "Checking HTTP endpoint: $url" - - for ((i=1; i<=retry_count; i++)); do - if timeout "$timeout" curl -sf "$url" >/dev/null 2>&1; then - print_status "OK" "$service_name endpoint is healthy: $url" - return 0 - else - if [[ $i -lt $retry_count ]]; then - log_warning "Attempt $i/$retry_count failed, retrying in 5 seconds..." - sleep 5 - fi - fi - done - - print_status "ERROR" "$service_name endpoint is not healthy: $url (after $retry_count attempts)" - return 1 -} - -# ============================================================================= -# Utility Functions -# ============================================================================= - -# Wait for service with timeout -wait_for_service() { - local check_command=$1 - local service_name=$2 - local timeout=${3:-60} - local interval=${4:-5} - - log_info "Waiting for $service_name to be ready (timeout: ${timeout}s)..." - - local elapsed=0 - while [[ $elapsed -lt $timeout ]]; do - if eval "$check_command" >/dev/null 2>&1; then - log_success "$service_name is ready" - return 0 - fi - - sleep "$interval" - elapsed=$((elapsed + interval)) - log_progress "Waiting for $service_name... (${elapsed}s/${timeout}s)" - done - - log_error "$service_name failed to become ready within ${timeout}s" - return 1 -} - -# Create directory with logging -create_directory() { - local dir=$1 - local description=${2:-"Directory"} - - if [[ ! -d "$dir" ]]; then - if mkdir -p "$dir"; then - log_success "$description created: $dir" - else - log_error "Failed to create $description: $dir" - return 1 - fi - else - log_info "$description already exists: $dir" - fi -} - -# Backup file with timestamp -backup_file() { - local file=$1 - local backup_dir=${2:-"./backups"} - - if [[ -f "$file" ]]; then - create_directory "$backup_dir" "Backup directory" - local timestamp=$(date +%Y%m%d_%H%M%S) - local backup_file="$backup_dir/$(basename "$file").backup.$timestamp" - - if cp "$file" "$backup_file"; then - log_success "File backed up: $file → $backup_file" - echo "$backup_file" - else - log_error "Failed to backup file: $file" - return 1 - fi - else - log_warning "File not found for backup: $file" - return 1 - fi -} - -# Run command with timeout and logging -run_with_timeout() { - local timeout_duration=$1 - local description=$2 - shift 2 - local command=("$@") - - log_info "Running: $description" - log_debug "Command: ${command[*]}" - - if timeout "$timeout_duration" "${command[@]}"; then - log_success "$description completed successfully" - return 0 - else - local exit_code=$? - if [[ $exit_code -eq 124 ]]; then - log_error "$description timed out after ${timeout_duration}s" - else - log_error "$description failed with exit code $exit_code" - fi - return $exit_code - fi -} - -# ============================================================================= -# Summary and Reporting Functions -# ============================================================================= - -# Print execution summary -print_summary() { - local script_name=${1:-"Script"} - local end_time=$(date +%s) - local duration=$((end_time - START_TIME)) - - log_section "Execution Summary" - - echo -e "Script: ${WHITE}$script_name${NC}" - echo -e "Duration: ${WHITE}${duration}s${NC}" - echo -e "Total checks: ${WHITE}$CHECKS${NC}" - echo -e "${GREEN}Successful: $((CHECKS - ERRORS - WARNINGS))${NC}" - echo -e "${YELLOW}Warnings: $WARNINGS${NC}" - echo -e "${RED}Errors: $ERRORS${NC}" - echo - - if [[ $ERRORS -eq 0 ]]; then - if [[ $WARNINGS -eq 0 ]]; then - log_success "All checks passed! $script_name completed successfully." - return 0 - else - log_warning "$script_name completed with warnings. Please review the warnings above." - return 0 - fi - else - log_error "$script_name failed with $ERRORS errors. Please fix the errors above." - return 1 - fi -} - -# ============================================================================= -# Environment and Configuration -# ============================================================================= - -# Load environment file if it exists -load_env_file() { - local env_file=${1:-.env} - - if [[ -f "$env_file" ]]; then - log_info "Loading environment from: $env_file" - set -a - # shellcheck source=/dev/null - source "$env_file" - set +a - log_success "Environment loaded successfully" - else - log_warning "Environment file not found: $env_file" - fi -} - -# Validate required environment variables -validate_env_vars() { - local vars=("$@") - local missing_vars=() - - for var in "${vars[@]}"; do - if [[ -z "${!var:-}" ]]; then - missing_vars+=("$var") - fi - done - - if [[ ${#missing_vars[@]} -gt 0 ]]; then - log_error "Missing required environment variables: ${missing_vars[*]}" - return 1 - else - log_success "All required environment variables are set" - return 0 - fi -} - -# ============================================================================= -# Docker and Service Management -# ============================================================================= - -# Check if Docker is running -check_docker() { - if command_exists docker && docker info >/dev/null 2>&1; then - print_status "OK" "Docker is running" - return 0 - else - print_status "ERROR" "Docker is not running or not accessible" - return 1 - fi -} - -# Check if docker-compose is available -check_docker_compose() { - if command_exists docker-compose; then - print_status "OK" "docker-compose is available" - return 0 - elif docker compose version >/dev/null 2>&1; then - print_status "OK" "docker compose (plugin) is available" - return 0 - else - print_status "ERROR" "Neither docker-compose nor docker compose is available" - return 1 - fi -} - -# Start Docker services with health check wait -start_docker_services() { - local services=("$@") - local compose_file=${COMPOSE_FILE:-docker-compose.yml} - - log_info "Starting Docker services: ${services[*]}" - - if docker-compose -f "$compose_file" up -d "${services[@]}"; then - log_success "Docker services started" - - # Wait for services to be healthy - for service in "${services[@]}"; do - wait_for_service "docker-compose -f $compose_file ps $service | grep -q 'healthy\\|Up'" "$service" 120 10 - done - else - log_error "Failed to start Docker services" - return 1 - fi -} - -# ============================================================================= -# Initialization -# ============================================================================= - -log_debug "Common utilities library loaded successfully" diff --git a/scripts/validate-docker-consistency.sh b/scripts/validate-docker-consistency.sh deleted file mode 100755 index b95e2f04..00000000 --- a/scripts/validate-docker-consistency.sh +++ /dev/null @@ -1,826 +0,0 @@ -#!/bin/bash -# =================================================================== -# Docker Konsistenz-Prüfer -# Validiert Dockerfiles und docker-compose-Dateien gegen docker/versions.toml -# =================================================================== - -# Strikte Fehlerbehandlung: Abbruch bei Fehler, bei unset Variablen, und wenn ein Befehl in einer Pipe fehlschlägt. -set -euo pipefail - -# Skript-Verzeichnis und Projekt-Wurzel -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -DOCKER_DIR="$PROJECT_ROOT/docker" -VERSIONS_TOML="$DOCKER_DIR/versions.toml" -DOCKERFILES_DIR="$PROJECT_ROOT/dockerfiles" - -# Farben für die Ausgabe -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # Keine Farbe - -# Globale Zähler (werden innerhalb der Funktionen aktualisiert) -_ERRORS=0 -_WARNINGS=0 -_CHECKS_PASSED=0 - -# --- Hilfsfunktionen für Ausgabe und Zählung --- - -# Funktion zur Ausgabe von farbigem Text und zur Aktualisierung eines globalen Zählers -print_and_count() { - local color=$1 - local type=$2 - local message=$3 - local counter_name=$4 # Name der globalen Zählervariable - - echo -e "${color}[${type}]${NC} $message" - if [[ -n "$counter_name" ]]; then - # Nameref für robuste Zählerinkrementierung verwenden (Globale Variable) - declare -g -i "$counter_name" - eval "$counter_name=\$(( $counter_name + 1 ))" - fi -} - -print_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -print_success() { - print_and_count "$GREEN" "ERFOLG" "$1" "_CHECKS_PASSED" -} - -print_warning() { - print_and_count "$YELLOW" "WARNUNG" "$1" "_WARNINGS" -} - -print_error() { - print_and_count "$RED" "FEHLER" "$1" "_ERRORS" -} - -# --- TOML Parsing Funktionen --- - -# Funktion zum Extrahieren einer Version aus der [versions] Sektion der TOML-Datei -get_version() { - local key=$1 - # grep zur schnellen Lokalisierung des Abschnitts, dann awk zum Finden des Schlüssels - grep -A 20 -E '^\[versions\]' "$VERSIONS_TOML" | \ - awk -v k="$key" ' - /\[/ { exit } # Beim nächsten Abschnitt stoppen - $1 == k && $2 == "=" { v=$3; gsub(/"/,"",v); print v; exit } - ' || true -} - -# Funktion zum Abrufen aller gültigen ARG-Namen aus der TOML-Datei -get_valid_args() { - # 1. Versions-Schlüssel aus [versions] extrahieren - awk '/^\[versions\]/,/^\[/ {if (/^[a-zA-Z].*= /) print $1}' "$VERSIONS_TOML" | grep -v "^\[" || true - # 2. Build-Argumente aus [build-args] extrahieren (Tokens innerhalb von Anführungszeichen) - awk '/^\[build-args\]/,/^\[/ { - if ($0 ~ /^[[:space:]]*args[[:space:]]*=/) { - line = $0 - # Array-Begrenzer entfernen: [ und ] - gsub(/[\[\]]/, "", line) - # Durch die zitierten Tokens iterieren - while (match(line, /"[A-Za-z0-9_]+"/)) { - token = substr(line, RSTART+1, RLENGTH-2) - print token - line = substr(line, RSTART+RLENGTH) - } - } - }' "$VERSIONS_TOML" || true - # 3. Service-Ports extrahieren - awk '/^\[service-ports\]/,/^\[/ {if (/^[a-zA-Z].*= /) print $1}' "$VERSIONS_TOML" | grep -v "^\[" || true -} - -# Funktion zum Abrufen der Environment-Variablen-Mappings aus der TOML-Datei -get_env_mappings() { - awk '/^\[environment-mapping\]/,/^\[/ { - if (/^[a-zA-Z].*= /) { - key = $1 - value = $3 - gsub(/"/, "", value) - print key ":" value - } - }' "$VERSIONS_TOML" || true -} - -# Port-Wert aus [service-ports] in versions.toml abrufen -get_toml_port() { - local service_key=$1 - grep -A 20 -E '^\[service-ports\]' "$VERSIONS_TOML" | \ - awk -v key="$service_key" ' - /\[/ { exit } - $1 == key { print $3; exit } - ' || true -} - -# --- Validierungsfunktionen --- - -# Funktion zur Validierung der Dockerfile ARGs -validate_dockerfile_args() { - local dockerfile=$1 - local relative_path=${dockerfile#"$PROJECT_ROOT"/} - - print_info "Validiere Dockerfile: $relative_path" - - if [[ ! -f "$dockerfile" ]]; then - print_error "Dockerfile nicht gefunden: $relative_path" - return - fi - - local dockerfile_args - # Alle ARG-Deklarationen erfassen (nur Name) - dockerfile_args=$(grep "^ARG " "$dockerfile" 2>/dev/null | sed 's/^ARG //' | sed 's/=.*//' | sort -u || true) - - local valid_args - valid_args=$(get_valid_args | sort -u) - - local has_centralized_args=false - - # Jedes ARG im Dockerfile prüfen - while IFS= read -r arg; do - [[ -z "$arg" ]] && continue - - # Prüfen, ob ARG in versions.toml definiert oder ein Standard Docker ARG ist - case "$arg" in - # Standard Docker Build-Argumente - BUILDPLATFORM|TARGETPLATFORM|BUILDOS|TARGETOS|BUILDARCH|TARGETARCH) - print_success " ✓ Standard Docker ARG: $arg" - has_centralized_args=true - ;; - # Anwendungs-spezifische Argumente, die zentralisiert sein sollten - GRADLE_VERSION|JAVA_VERSION|NODE_VERSION|NGINX_VERSION|VERSION|SPRING_PROFILES_ACTIVE|SERVICE_PATH|SERVICE_NAME|SERVICE_PORT|CLIENT_PATH|CLIENT_MODULE|CLIENT_NAME) - if echo "$valid_args" | grep -q "^$arg$"; then - print_success " ✓ Zentralisiertes ARG: $arg" - has_centralized_args=true - else - print_warning " ⚠ ARG $arg sollte in versions.toml definiert werden" - fi - ;; - # Laufzeit-Konfigurationsargumente (lokal akzeptabel) - APP_USER|APP_GROUP|APP_UID|APP_GID) - print_success " ✓ Laufzeit-Konfigurations ARG: $arg" - ;; - *) - # Prüfen, ob es ein Versions-bezogenes ARG ist, das zentralisiert werden sollte - if [[ "$arg" =~ _(VERSION|PORT)$ ]] || [[ "$arg" =~ ^(DOCKER_|SERVICE_|CLIENT_) ]]; then - print_warning " ⚠ ARG $arg sollte möglicherweise in versions.toml zentralisiert werden" - else - print_success " ✓ Benutzerdefiniertes ARG: $arg" - fi - ;; - esac - done <<< "$dockerfile_args" - - if [[ "$has_centralized_args" == true ]]; then - print_success " ✓ Dockerfile verwendet zentralisiertes Versionsmanagement" - else - print_warning " ⚠ Dockerfile sollte zentralisierte ARGs aus versions.toml verwenden" - fi - - # Prüfen auf Standardzuweisungen bei zentralisierten ARGs (verboten) - local centralized_args_regex='^(GRADLE_VERSION|JAVA_VERSION|NODE_VERSION|NGINX_VERSION|VERSION|SPRING_PROFILES_ACTIVE)=' - local defaulted_args - defaulted_args=$(grep -nE "^ARG ${centralized_args_regex}" "$dockerfile" || true) - - if [[ -n "$defaulted_args" ]]; then - print_error " ❌ Zentralisierte ARGs dürfen keine Standardwerte in Dockerfiles haben:" - # Prozess-Substitution verwenden, um den Verlust von $ERRORS-Updates zu vermeiden - while IFS= read -r line; do - print_error " $relative_path:$line" - done < <(echo "$defaulted_args") - else - print_success " ✓ Keine Standardwerte für zentralisierte ARGs gesetzt" - fi - - # Prüfen auf festcodierte Versionen in ARG-Standardwerten - local hardcoded_versions - hardcoded_versions=$(grep -nE "^ARG [A-Z0-9_]+=.*(alpine|[0-9]+\.[0-9]+)" "$dockerfile" | grep -v "APP_" || true) - - if [[ -n "$hardcoded_versions" ]]; then - print_error " ❌ Festcodierte Versionen in ARG-Standardwerten gefunden (sollten versions.toml verwenden):" - # Prozess-Substitution verwenden, um den Verlust von $ERRORS-Updates zu vermeiden - while IFS= read -r line; do - print_error " $relative_path:$line" - done < <(echo "$hardcoded_versions") - else - print_success " ✓ Keine festcodierten Versionsliterale in ARG-Standardwerten" - fi -} - -# Funktion zur Validierung von docker-compose Versionsreferenzen -validate_compose_versions() { - local compose_file=$1 - local relative_path=${compose_file#"$PROJECT_ROOT"/} - - print_info "Validiere Docker Compose Datei: $relative_path" - - if [[ ! -f "$compose_file" ]]; then - print_error "Compose-Datei nicht gefunden: $relative_path" - return - fi - - local env_mappings - env_mappings=$(get_env_mappings) - - # 0) Fehler bei leeren ARG-Werten für kritische Build-Argumente - local blank_args - blank_args=$(grep -nE '^[[:space:]]*(GRADLE_VERSION|JAVA_VERSION|NODE_VERSION|NGINX_VERSION|VERSION|SPRING_PROFILES_ACTIVE):[[:space:]]*$' "$compose_file" || true) - if [[ -n "$blank_args" ]]; then - print_error " ❌ Leere Build-Argumente erkannt (müssen auf zentralisierte DOCKER_* Variablen verweisen):" - while IFS= read -r line; do - print_error " $relative_path:$line" - done < <(echo "$blank_args") - else - print_success " ✓ Keine leeren kritischen Build-Argumente in der Compose-Datei" - fi - - # Sicherstellen, dass kritische Build-Argumente auf zentralisierte DOCKER_* Variablen verweisen - local critical_vars=(GRADLE_VERSION JAVA_VERSION NODE_VERSION NGINX_VERSION VERSION SPRING_PROFILES_ACTIVE) - for v in "${critical_vars[@]}"; do - # Verwenden von awk, um Mapping-Einträge innerhalb von build->args zuverlässig zu finden - local mapping_lines - mapping_lines=$(awk -v var="$v" ' - { line[NR] = $0 } - END { - for (i = 1; i <= NR; i++) { - if (line[i] ~ "^[[:space:]]*" var ":[[:space:]]*.+$") { - found = 0 - # Bis zu 12 Zeilen zurückblicken, um nach "args:" zu suchen - for (j = i - 1; j >= 1 && j >= i - 12; j--) { - if (line[j] ~ /^[[:space:]]*args:[[:space:]]*$/) { found = 1; break } - # Suche stoppen, wenn eine übergeordnete Sektion erreicht wird - if (line[j] ~ /^[[:space:]]*(environment|services|volumes|secrets|networks):/ ) { break } - } - if (found) { printf("%d:%s\n", i, line[i]) } - } - } - }' "$compose_file" || true) - - if [[ -n "$mapping_lines" ]]; then - while IFS= read -r line; do - local ln - local content - ln=$(echo "$line" | cut -d: -f1) - content=$(echo "$line" | cut -d: -f2- | sed 's/^[[:space:]]*//') - # Prüfen, ob der Wert auf ${DOCKER_*} verweist - if ! echo "$content" | grep -q "\${DOCKER_"; then - print_error " ❌ $v sollte in Build-Args-Mappings auf zentralisierte DOCKER_* Variable verweisen (gefunden: $content)" - print_error " $relative_path:$ln" - fi - done < <(echo "$mapping_lines") - fi - done - - # 2a) Validierung von Standard-Fallbacks in ${DOCKER_*:-fallback} gegen SSoT-Werte - declare -A env_to_version_key - while IFS=':' read -r toml_key env_var; do - [[ -z "$toml_key" || -z "$env_var" ]] && continue - # Mapping der TOML-Schlüsselnamen zu Environment-Variablennamen - env_to_version_key[$env_var]=$(echo "$toml_key" | tr -d '\r') - done <<< "$env_mappings" - - # Vorkommen mit expliziten Standard-Fallbacks finden - local fallback_lines - fallback_lines=$(grep -nE "\${DOCKER_[A-Z0-9_]+:-[^}]+" "$compose_file" || true) - - if [[ -n "$fallback_lines" ]]; then - while IFS= read -r ln; do - [[ -z "$ln" ]] && continue - local num - local text - num=$(echo "$ln" | cut -d: -f1) - text=$(echo "$ln" | cut -d: -f2-) - - # Variablennamen und Fallback-Wert extrahieren - local var - local fallback - var=$(echo "$text" | sed -nE "s/.*\$([A-Z0-9_]+):-\([^}][^}]*\).*/\1/p") - fallback=$(echo "$text" | sed -nE "s/.*\$[A-Z0-9_]+:-([^}][^}]*).*/\1/p") - - if [[ -z "$var" || -z "$fallback" ]]; then - continue - fi - - local key - key=${env_to_version_key[$var]} - if [[ -z "$key" ]]; then - print_warning " ⚠ Variable $var wird mit Fallback verwendet, ist aber nicht in [environment-mapping] gemappt. Fallback-Prüfung übersprungen." - continue - fi - - local expected - expected=$(get_version "$key") - if [[ -z "$expected" ]]; then - print_warning " ⚠ Kein SSoT-Wert für $var (Schlüssel: $key) in versions.toml zum Vergleich des Fallbacks gefunden" - continue - fi - - if [[ "$fallback" != "$expected" ]]; then - print_error " ❌ Veralteter Standard-Fallback für $var in ${relative_path}:${num} — gefunden '$fallback', erwartet '$expected' aus versions.toml ($key)" - else - print_success " ✓ Fallback für $var stimmt mit SSoT überein ($expected)" - fi - done < <(echo "$fallback_lines") - fi - - # Prüfen auf Versionsreferenzen in der Compose-Datei - local version_refs - version_refs=$(grep -o "\${DOCKER_[^}]*}" "$compose_file" | sort -u || true) - - if [[ -z "$version_refs" ]]; then - print_warning " ⚠ Keine zentralisierten Versionsreferenzen gefunden" - else - while IFS= read -r ref; do - [[ -z "$ref" ]] && continue - - local var_name - var_name=${ref#\$\{} - var_name=${var_name%\}} - var_name=${var_name%%:-*} # Standard-Fallback (:-value) entfernen - - local mapping_found=false - while IFS=':' read -r toml_key env_var; do - if [[ "$env_var" == "$var_name" ]]; then - mapping_found=true - local toml_version - toml_version=$(get_version "$toml_key") - if [[ -n "$toml_version" ]]; then - print_success " ✓ Versionsreferenz $ref mappt zu $toml_key = $toml_version" - else - print_error " ❌ TOML-Schlüssel $toml_key hat keinen Wert" - fi - break - fi - done <<< "$env_mappings" - - if [[ "$mapping_found" == false ]]; then - print_warning " ⚠ Versionsreferenz $ref hat kein Mapping in der [environment-mapping] Sektion" - fi - done <<< "$version_refs" - fi - - # Prüfen auf festcodierte Image-Versionen - local hardcoded_images - hardcoded_images=$(grep -E "image:.*:[0-9]" "$compose_file" | grep -v "\${" || true) - if [[ -n "$hardcoded_images" ]]; then - print_error " ❌ Festcodierte Image-Versionen gefunden:" - while IFS= read -r line; do - print_error " $line" - done < <(echo "$hardcoded_images") - else - print_success " ✓ Keine festcodierten Image-Versionen gefunden" - fi -} - -# Funktion zur Validierung der Port-Konsistenz -# Diese Funktion benötigt keine Argumente. -# shellcheck disable=SC2120 -validate_port_consistency() { - - local toml_ports - local overall_success - local compose_files - local full_path - local compose_ports_raw - local found_port - - print_info "Validiere Port-Konsistenz..." - - # toml_ports werden durch awk und VERSIONS_TOML gefüllt - toml_ports=$(awk "/^\[service-ports\]/,/^\[/ { - if (/^[a-zA-Z].*= [0-9]/) { - service = $1 - port = $3 - print service ":" port - } - }" "$VERSIONS_TOML" || true) - - compose_files=("docker-compose.yml" "docker-compose.services.yml" "docker-compose.clients.yml") - overall_success=true - - for compose_file in "${compose_files[@]}"; do - full_path="$PROJECT_ROOT/$compose_file" - if [[ -f "$full_path" ]]; then - # Port-Mappings aus Compose-Datei extrahieren (z.B. "8080:8080" oder "80:8080") - compose_ports_raw=$(grep -E 'ports:[[:space:]]*$|\s*-\s*("?[0-9]+:[0-9]+"?)' "$full_path" 2>/dev/null | grep -v "ports:$" | sed -E 's/.*-\s*"?([^"]*)"?/\1/' || true) - - while IFS=':' read -r service expected_port; do - [[ -z "$service" ]] && continue - - # Prüfen, ob der Dienstname in der Compose-Datei existiert - if grep -qE "^[[:space:]]*${service}:" "$full_path"; then - found_port=false - - # Prüfen, ob ein Port-Eintrag mit dem erwarteten internen Port übereinstimmt - if echo "$compose_ports_raw" | grep -q ":${expected_port}$"; then - found_port=true - fi - - if [[ "$found_port" == true ]]; then - print_success " ✓ Port-Konsistenz für $service: $expected_port" - else - print_warning " ⚠ Port-Fehlübereinstimmung/Fehlendes Mapping für $service (erwarteter interner Port: $expected_port)" - overall_success=false - fi - fi - done <<< "$toml_ports" - fi - done - - if [[ "$overall_success" == true ]]; then - # Nur eine allgemeine Erfolgsmeldung hinzufügen, wenn keine detaillierten - # Warnungen aufgetreten sind, die overall_success auf false gesetzt hätten. - # Beachten Sie, dass print_success/print_warning die globalen Zähler aktualisieren. - print_success " ✓ Gesamte Port-Konsistenzprüfung abgeschlossen." - else - # Wenn overall_success auf false gesetzt wurde, wurden bereits detaillierte Warnungen ausgegeben. - # Hier ist keine weitere Fehlermeldung nötig. - : - fi -} - -# Funktion zur Validierung von Build-Args Environment-Dateien -validate_build_args_files() { - - local build_args_files - local full_path - local invalid_lines - local bare_docker - local docker_keys_count - - # Überspringen, wenn der Env-less-Modus aktiv ist - if [[ "${DOCKER_SSOT_MODE:-compat}" == "envless" ]]; then - print_info "Env-less Modus aktiv → Überspringe build-args/*.env Validierung" - print_success " ✓ Übersprungen: build-args Env-Dateien sind im Env-less Modus nicht erforderlich" - return - fi - - print_info "Validiere Build-Args Environment-Dateien..." - - build_args_files=("global.env" "services.env" "infrastructure.env" "clients.env") - - for env_file in "${build_args_files[@]}"; do - full_path="$DOCKER_DIR/build-args/$env_file" - - if [[ ! -f "$full_path" ]]; then - print_error " ❌ Build-Args-Datei fehlt: $env_file" - continue - fi - - print_success " ✓ Build-Args-Datei existiert: $env_file" - - if [[ -s "$full_path" ]]; then - print_success " ✓ Build-Args-Datei ist nicht leer: $env_file" - else - print_warning " ⚠ Build-Args-Datei ist leer: $env_file" - fi - - # 1) Sicherstellen, dass nur gültige Zeilen vorhanden sind: Kommentare, Leerzeilen oder key=value - invalid_lines=$(grep -n -vE "^(#|\s*$|[A-Za-z_][A-Za-z0-9_]*=)" "$full_path" || true) - if [[ -n "$invalid_lines" ]]; then - print_error " ❌ Ungültige Zeilen (muss key=value oder Kommentar sein):" - while IFS= read -r line; do - print_error " $env_file:$line" - done < <(echo "$invalid_lines") - else - print_success " ✓ Format OK (nur key=value/Kommentare) in $env_file" - fi - - # 2) Keine bloßen Platzhalter wie `DOCKER_XYZ` ohne Wert - bare_docker=$(grep -nE "^DOCKER_[A-Z0-9_]+$" "$full_path" || true) - if [[ -n "$bare_docker" ]]; then - print_error " ❌ Bloße DOCKER_* Platzhalter ohne Werte gefunden:" - while IFS= read -r line; do - print_error " $env_file:$line" - done < <(echo "$bare_docker") - else - print_success " ✓ Keine bloßen DOCKER_* Platzhalter in $env_file" - fi - - # 3) Richtlinie: Nur global.env darf DOCKER_* Schlüssel enthalten - docker_keys_count=$(grep -c -E "^DOCKER_[A-Z0-9_]+" "$full_path" || echo "0") - if [[ "$env_file" == "global.env" ]]; then - if [[ "$docker_keys_count" -gt 0 ]]; then - print_success " ✓ DOCKER_* Variablen sind nur in global.env vorhanden ($docker_keys_count gefunden)" - else - print_warning " ⚠ Einige DOCKER_* Variablen erwartet in global.env (prometheus/grafana/keycloak, etc.)" - fi - # Erforderliche Schlüssel in global.env - for key in GRADLE_VERSION JAVA_VERSION VERSION; do - if grep -q "^$key=" "$full_path"; then - print_success " ✓ $key ist in global.env vorhanden" - else - print_error " ❌ $key fehlt in global.env" - fi - done - else - if [[ "$docker_keys_count" -gt 0 ]]; then - print_error " ❌ DOCKER_* Variablen dürfen nicht in $env_file vorhanden sein (in global.env zentralisieren)" - else - print_success " ✓ Keine zentralisierten DOCKER_* Variablen in $env_file (wie erwartet)" - fi - fi - - # 4) DOCKER_APP_VERSION in Build-Args Env-Dateien verbieten - if grep -q '^DOCKER_APP_VERSION=' "$full_path"; then - print_error " ❌ DOCKER_APP_VERSION sollte nicht in Build-Args-Dateien definiert werden (wird zur Laufzeit von VERSION gemappt)" - fi - done -} - -# Validierung der Wertgleichheit zwischen versions.toml und Build-Args Env-Dateien -validate_env_value_equality() { - - local has_diff - - # Überspringen, wenn der Env-less-Modus aktiv ist - if [[ "${DOCKER_SSOT_MODE:-compat}" == "envless" ]]; then - print_info "Env-less Modus aktiv → Überspringe TOML↔Env-Wert-Gleichheitsprüfung" - print_success " ✓ Übersprungen: Werte stammen zur Laufzeit direkt aus versions.toml" - return - fi - - print_info "Validiere Wertgleichheit zwischen versions.toml und Build-Args Envs..." - - has_diff=false - - # Interne Hilfsfunktion zum Vergleichen eines TOML-Schlüssels mit einem Env-Schlüssel in einer bestimmten Datei - _check_env_pair() { - - local env_file=$1 - local env_key=$2 - local toml_key=$3 - local path="$DOCKER_DIR/build-args/$env_file" - local expected actual port_lookup - - if [[ ! -f "$path" ]]; then - print_error " ❌ Fehlende Env-Datei: $env_file" - has_diff=true - return - fi - - # Erwarteter Wert aus TOML (zuerst [versions] versuchen) - expected=$(get_version "$toml_key") - # Fallback: [service-ports] versuchen, falls nicht in [versions] gefunden - if [[ -z "$expected" ]]; then - port_lookup=$(get_toml_port "$toml_key") - if [[ -n "$port_lookup" ]]; then - expected="$port_lookup" - fi - fi - - # Aktueller Wert aus Env-Datei - actual=$(grep -E "^${env_key}=" "$path" | head -1 | sed "s/^[^=]*=//") - - if [[ -z "$expected" ]]; then - print_warning " ⚠ TOML-Schlüssel '$toml_key' lieferte keinen Wert (versions.toml prüfen)" - return - fi - if [[ -z "$actual" ]]; then - print_error " ❌ $env_file fehlt $env_key (erwartet $expected)" - has_diff=true - return - fi - - # Prüfen auf Nichtübereinstimmung - if [[ "$expected" != "$actual" ]]; then - # Versuch zur Normalisierung für den Vergleich (Anführungszeichen entfernen, falls vorhanden) - local clean_expected - clean_expected=$(echo "$expected" | sed 's/^"//;s/"$//') - local clean_actual - clean_actual=$(echo "$actual" | sed 's/^"//;s/"$//') - - if [[ "$clean_expected" != "$clean_actual" ]]; then - print_error " ❌ Nichtübereinstimmung in $env_file: $env_key=$actual != $toml_key=$expected" - has_diff=true - else - # Nur ein Anführungszeichen-Unterschied (z.B. actual="1.0" expected=1.0) - print_warning " ⚠ Anführungszeichen-Unterschied in $env_file: $env_key=$actual vs $toml_key=$expected (Wert stimmt überein)" - fi - else - print_success " ✓ $env_file: $env_key stimmt mit $toml_key überein ($expected)" - fi - } - - # Global.env Mappings - _check_env_pair "global.env" "GRADLE_VERSION" "gradle" - _check_env_pair "global.env" "JAVA_VERSION" "java" - _check_env_pair "global.env" "VERSION" "app-version" - _check_env_pair "global.env" "PROMETHEUS_IMAGE_TAG" "prometheus" - _check_env_pair "global.env" "GRAFANA_IMAGE_TAG" "grafana" - _check_env_pair "global.env" "KEYCLOAK_IMAGE_TAG" "keycloak" - - # Clients.env Mappings - _check_env_pair "clients.env" "NODE_VERSION" "node" - _check_env_pair "clients.env" "NGINX_VERSION" "nginx" - - if [[ "$has_diff" == false ]]; then - print_success "Umwelt-Dateien sind vollständig mit versions.toml synchronisiert" - fi -} - -# Nach frei schwebenden Versions-Strings außerhalb kontrollierter Dateien scannen -scan_free_floating_versions() { - - local version_values - local found_any=false - local search_paths - local hits="" - local generic_hits - - print_info "Scanne nach frei schwebenden Versions-Literalen außerhalb von SSoT-verwalteten Dateien..." - - # Versionswerte aus [versions] sammeln (muss einen Punkt oder Bindestrich enthalten, um ein Versions-String zu sein) - version_values=$(awk ' - /^\[versions\]/ { in_section=1; next } - /^\[/ { if (in_section) exit; in_section=0 } - in_section && $2 == "=" { v=$3; gsub(/"/,"",v); if (v ~ /[\.-]/) print v } - ' "$VERSIONS_TOML" || true) - - # Alle Dateien finden, die NICHT auf der Ausschlussliste stehen (Endet mit Null-Byte) - search_paths=$( - find "$PROJECT_ROOT" -type f \ - -not -path "*/.git/*" \ - -not -path "*/build/*" \ - -not -path "*/.gradle/*" \ - -not -path "*/node_modules/*" \ - -not -path "*/dist/*" \ - -not -path "*/out/*" \ - -not -path "*/target/*" \ - -not -path "$PROJECT_ROOT/README.md" \ - -not -path "$PROJECT_ROOT/docker/versions.toml" \ - -not -path "$PROJECT_ROOT/docker/build-args/*" \ - -not -name "docker-compose*.yml" \ - -not -name "docker-compose*.yml.optimized" \ - -not -path "$PROJECT_ROOT/scripts/*" \ - -not -name "*.sh" \ - -print0 - ) - - # 1. Nach exakten, kontrollierten Versions-Strings suchen - while IFS= read -r val; do - [[ -z "$val" ]] && continue - # Portable Suche nach exaktem Wert - while IFS= read -r -d '' f; do - grep -nF -- "$val" "$f" 2>/dev/null || true - done < <(echo "$search_paths" | tr '\0' '\n') | tr '\n' '\0' | while IFS= read -r -d '' line; do - hits+="${line}\n" - done - - if [[ -n "$hits" ]]; then - found_any=true - print_warning " ⚠ Vorkommen des Versions-Literals '$val' außerhalb kontrollierter Dateien entdeckt:" - while IFS= read -r line; do - print_warning " $line" - done < <(echo -e "$hits" | sed 's/\n$//') - fi - done <<< "$version_values" - - # 2. Generischer Muster-Scan nach verdächtigen Literalen - # Suche nach gängigen Versions-Mustern (X.Y.Z, X.Y) - generic_hits=$(\ - echo "$search_paths" | tr '\0' '\n' | xargs -r grep -nE -- "(^|[^0-9])([0-9]+\.[0-9]+\.[0-9]+([a-zA-Z0-9._-]+)?|([0-9]+\.[0-9]+))" 2>/dev/null \ - | head -n 200 || true) # Ausgabe auf 200 Zeilen begrenzen - - if [[ -n "$generic_hits" ]]; then - found_any=true - print_warning " ⚠ Generische Versions-ähnliche Strings gefunden (auf potenziellen Drift überprüfen):" - while IFS= read -r line; do - print_warning " $line" - done < <(echo "$generic_hits") - fi - - if [[ "$found_any" == false ]]; then - print_success " ✓ Keine frei schwebenden Versions-Literale entdeckt" - fi -} - -# Funktion zur Anzeige der Validierungszusammenfassung -show_summary() { - echo "" - echo "===============================================" - echo "Zusammenfassung der Docker Konsistenz-Validierung" - echo "===============================================" - echo -e "${GREEN}Prüfungen bestanden: $_CHECKS_PASSED${NC}" - echo -e "${YELLOW}Warnungen: $_WARNINGS${NC}" - echo -e "${RED}Fehler: $_ERRORS${NC}" - echo "===============================================" - - if [[ $_ERRORS -eq 0 ]]; then - if [[ $_WARNINGS -eq 0 ]]; then - print_info "Alle Konsistenzprüfungen bestanden! ✨" - return 0 - else - print_warning "Validierung mit Warnungen abgeschlossen. Bitte beheben Sie diese für optimale Konsistenz." - return 0 - fi - else - print_error "Validierung fehlgeschlagen mit $_ERRORS Fehlern. Bitte beheben Sie diese, bevor Sie fortfahren." - return 1 - fi -} - -# Funktion zur Anzeige der Hilfe -show_help() { - echo "Docker Konsistenz-Prüfer" - echo "" - echo "Verwendung: $0 [BEFEHL]" - echo "" - echo "Befehle:" - echo " all Führt alle Validierungsprüfungen aus (Standard)" - echo " dockerfiles Validiert Dockerfile ARG-Deklarationen" - echo " compose Validiert docker-compose Versionsreferenzen" - echo " ports Validiert Port-Konsistenz" - echo " build-args Validiert build-args Environment-Dateien und Wertgleichheit" - echo " scan-drift Scannt das Repo nach frei schwebenden Versions-Literalen" - echo "" - echo "Beispiele:" - echo " $0 # Alle Validierungen ausführen" - echo " $0 dockerfiles # Nur Dockerfiles validieren" -} - -# Hauptausführung -main() { - local command=${1:-all} - - # Prüfen, ob versions.toml existiert - if [[ ! -f "$VERSIONS_TOML" ]]; then - print_error "Versions-Datei nicht gefunden: $VERSIONS_TOML" - show_summary - exit 1 - fi - - print_info "Docker Konsistenz-Validierung - Startet..." - print_info "Versions-Datei: $VERSIONS_TOML" - echo "" - - case $command in - "all") - # Dockerfiles validieren - find "$DOCKERFILES_DIR" -name "Dockerfile" -type f -print0 | while IFS= read -r -d '' dockerfile; do - validate_dockerfile_args "$dockerfile" - echo "" - done - - # docker-compose-Dateien validieren - find "$PROJECT_ROOT" -maxdepth 1 -name "docker-compose*.yml*" -type f -print0 | while IFS= read -r -d '' compose_file; do - validate_compose_versions "$compose_file" - echo "" - done - - # Port-Konsistenz validieren - validate_port_consistency - echo "" - - # Build-Args-Dateien validieren - validate_build_args_files - echo "" - - # Wertgleichheit zwischen TOML und Env-Dateien validieren - validate_env_value_equality - echo "" - - # Repository nach frei schwebenden Versions-Literalen scannen - scan_free_floating_versions - ;; - "dockerfiles") - find "$DOCKERFILES_DIR" -name "Dockerfile" -type f -print0 | while IFS= read -r -d '' dockerfile; do - validate_dockerfile_args "$dockerfile" - echo "" - done - ;; - "compose") - find "$PROJECT_ROOT" -maxdepth 1 -name "docker-compose*.yml*" -type f -print0 | while IFS= read -r -d '' compose_file; do - validate_compose_versions "$compose_file" - echo "" - done - ;; - "ports") - validate_port_consistency - ;; - "build-args") - validate_build_args_files - validate_env_value_equality - ;; - "scan-drift") - scan_free_floating_versions - ;; - "-h"|"--help"|"help") - show_help - exit 0 - ;; - *) - print_error "Unbekannter Befehl: $command" - show_help - exit 1 - ;; - esac - - show_summary -} - -# Hauptfunktion mit allen Argumenten ausführen -main "$@" diff --git a/scripts/validation/validate-docs.sh b/scripts/validation/validate-docs.sh deleted file mode 100755 index c5bd0c35..00000000 --- a/scripts/validation/validate-docs.sh +++ /dev/null @@ -1,266 +0,0 @@ -#!/bin/bash - -# Documentation Validation Script -# Checks documentation completeness, consistency, and structure - -set -e - -echo "🔍 Starting documentation validation..." - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Counters -ERRORS=0 -WARNINGS=0 -CHECKS=0 - -# Function to log messages -log_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" - ((++WARNINGS)) -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $1" - ((++ERRORS)) -} - -# Check if required directories exist -check_directory_structure() { - log_info "Checking documentation directory structure..." - ((++CHECKS)) - - required_dirs=( - "docs" - "docs/api" - "docs/development" - "docs/architecture" - ) - - for dir in "${required_dirs[@]}"; do - if [ ! -d "$dir" ]; then - log_error "Required directory missing: $dir" - else - log_success "Directory exists: $dir" - fi - done -} - -# Check if all modules have README files -check_module_readmes() { - log_info "Checking module README files..." - ((++CHECKS)) - - modules=( - "members" - "horses" - "events" - "masterdata" - "infrastructure" - "core" - "client" - ) - - for module in "${modules[@]}"; do - if [ ! -f "$module/README.md" ]; then - log_error "Missing README.md in module: $module" - else - log_success "README.md exists for module: $module" - fi - done -} - -# Check for German translations -check_german_translations() { - log_info "Checking German translations..." - ((++CHECKS)) - - # Find English docs that should have German translations - english_docs=$(find docs -name "*.md" | grep -v "\-de\.md$" | grep -v "README.md") - - for doc in $english_docs; do - german_doc="${doc%.md}-de.md" - if [ ! -f "$german_doc" ]; then - log_warning "Missing German translation for: $doc" - else - log_success "German translation exists for: $doc" - fi - done -} - -# Check documentation consistency -check_documentation_consistency() { - log_info "Checking documentation consistency..." - ((++CHECKS)) - - # Check for consistent date format - inconsistent_dates=$(grep -r "Letzte Aktualisierung" docs/ | grep -v "25. Juli 2025" || true) - if [ -n "$inconsistent_dates" ]; then - log_warning "Inconsistent dates found in documentation" - echo "$inconsistent_dates" - else - log_success "All documentation dates are consistent" - fi - - # Check for broken internal links - log_info "Checking internal links..." - broken_links=$(grep -r "\[.*\](\..*\.md)" docs/ | while read -r line; do - file=$(echo "$line" | cut -d: -f1) - link=$(echo "$line" | grep -o "\[.*\](\..*\.md)" | sed 's/.*](\(.*\))/\1/') - - # Resolve relative path - dir=$(dirname "$file") - full_path="$dir/$link" - - if [ ! -f "$full_path" ]; then - echo "Broken link in $file: $link" - fi - done) - - if [ -n "$broken_links" ]; then - log_error "Broken internal links found:" - echo "$broken_links" - else - log_success "All internal links are valid" - fi -} - -# Check API documentation completeness -check_api_documentation() { - log_info "Checking API documentation..." - ((++CHECKS)) - - # Check if API controllers have corresponding documentation - controllers=$(find . -name "*Controller.kt" -type f | grep -E "(members|horses|events|masterdata)" | wc -l) - api_docs=$(find docs/api -name "*.md" | grep -v "README.md" | wc -l) - - if [ "$api_docs" -eq 0 ]; then - log_warning "No specific API documentation found (only found $api_docs docs for $controllers controllers)" - else - log_success "API documentation exists ($api_docs docs for $controllers controllers)" - fi -} - -# Check code examples in documentation -check_code_examples() { - log_info "Checking code examples in documentation..." - ((++CHECKS)) - - # Find Kotlin code blocks and check basic syntax - kotlin_blocks=$(grep -r '```kotlin' docs/ | wc -l) - if [ "$kotlin_blocks" -gt 0 ]; then - log_success "Found $kotlin_blocks Kotlin code examples" - else - log_warning "No Kotlin code examples found in documentation" - fi - - # Skipping code block syntax checks to avoid false positives in CI - true -} - -# Check documentation completeness score -calculate_completeness_score() { - log_info "Calculating documentation completeness score..." - - total_modules=7 - modules_with_readme=$(find members horses events masterdata infrastructure core client -maxdepth 1 -name "README.md" 2>/dev/null | wc -l) - - api_coverage=1 # We have API documentation - german_coverage=1 # We have German translations - - completeness_score=$(echo "scale=2; ($modules_with_readme + $api_coverage + $german_coverage) / ($total_modules + 2) * 100" | bc -l) - - log_info "Documentation completeness score: ${completeness_score}%" - - if (( $(echo "$completeness_score >= 90" | bc -l) )); then - log_success "Excellent documentation coverage!" - elif (( $(echo "$completeness_score >= 70" | bc -l) )); then - log_warning "Good documentation coverage, room for improvement" - else - log_error "Documentation coverage needs significant improvement" - fi -} - -# Optional external link check (lychee) -link_check() { - log_info "Running external link check (if 'lychee' is available)..." - ((++CHECKS)) - if command -v lychee &> /dev/null; then - # Allow common transient codes and skip localhost links - lychee --no-progress --accept 200,204,301,302,429 --exclude "localhost|127.0.0.1|yourdomain.com" docs/**/*.md || { - log_error "Lychee reported broken external links" - } - else - log_warning "'lychee' not found, skipping external link check" - fi -} - -# Optional PlantUML render check -plantuml_check() { - log_info "Rendering PlantUML diagrams to validate syntax (if 'plantuml' is available)..." - ((++CHECKS)) - if command -v plantuml &> /dev/null; then - # Render to SVG; failures should surface as non-zero exit - # Note: This may produce SVGs next to the .puml files in CI workspace - if compgen -G "docs/architecture/**/*.puml" > /dev/null; then - plantuml -tsvg -failfast2 docs/architecture/**/*.puml || log_error "PlantUML rendering failed" - elif compgen -G "docs/**/*.puml" > /dev/null; then - plantuml -tsvg -failfast2 docs/**/*.puml || log_error "PlantUML rendering failed" - else - log_warning "No .puml files found to render" - fi - else - log_warning "'plantuml' not found, skipping diagram rendering" - fi -} - -# Main execution -main() { - echo "📚 Meldestelle Documentation Validation" - echo "========================================" - - check_directory_structure - check_module_readmes - check_german_translations - check_documentation_consistency - check_api_documentation - check_code_examples - link_check - plantuml_check - calculate_completeness_score - - echo "" - echo "📊 Validation Summary" - echo "====================" - echo "Total checks performed: $CHECKS" - echo "Errors found: $ERRORS" - echo "Warnings found: $WARNINGS" - - if [ $ERRORS -eq 0 ]; then - log_success "✅ Documentation validation passed!" - exit 0 - else - log_error "❌ Documentation validation failed with $ERRORS errors" - exit 1 - fi -} - -# Check if bc is available for calculations -if ! command -v bc &> /dev/null; then - log_warning "bc calculator not found, skipping completeness score calculation" -fi - -main "$@" diff --git a/scripts/validation/validate-env.sh b/scripts/validation/validate-env.sh deleted file mode 100755 index cfbb94e0..00000000 --- a/scripts/validation/validate-env.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# ============================================================================= -# Environment Variables Validation Script -# ============================================================================= -# This script validates that all required environment variables are properly -# configured for the Meldestelle application. -# ============================================================================= - -# Load common utilities -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=../utils/common.sh -source "$SCRIPT_DIR/../utils/common.sh" || { - echo "Error: Could not load common utilities from $SCRIPT_DIR/../utils/common.sh" - exit 1 -} - -log_section "Meldestelle - Environment Variables Validation" - -# Check if .env file exists -log_info "1. Checking .env file..." -if [ -f ".env" ]; then - log_success ".env file exists" - - # Load .env file - set -a - source .env - set +a - - log_success ".env file loaded successfully" -else - log_error ".env file not found" - log_error "Please create a .env file based on the documentation." - exit 1 -fi - -# Check if docker-compose.yml exists -log_info "2. Checking docker-compose.yml file..." -if [ -f "docker-compose.yml" ]; then - print_status "OK" "docker-compose.yml file exists" -else - log_error "docker-compose.yml file not found" - exit 1 -fi - -# Define required environment variables -log_info "3. Checking required environment variables..." - -# Application Configuration -check_var() { - local var_name=$1 - local var_value=${!var_name} - local is_required=${2:-false} - local description=$3 - - if [ -n "$var_value" ]; then - log_success "$var_name is set: '$var_value'" - elif [ "$is_required" = true ]; then - log_error "$var_name is required but not set ($description)" - else - log_warning "$var_name is not set ($description)" - fi -} - -# Application Configuration -echo -e "${YELLOW}Application Configuration:${NC}" -check_var "API_HOST" true "Server host address" -check_var "API_PORT" true "Server port" -check_var "APP_NAME" false "Application name" -check_var "APP_VERSION" false "Application version" -check_var "APP_ENVIRONMENT" true "Current environment" -echo - -# Database Configuration -echo -e "${YELLOW}Database Configuration:${NC}" -check_var "DB_HOST" true "Database host" -check_var "DB_PORT" true "Database port" -check_var "DB_NAME" true "Database name" -check_var "DB_USER" true "Database user" -check_var "DB_PASSWORD" true "Database password" -check_var "POSTGRES_USER" true "PostgreSQL container user" -check_var "POSTGRES_PASSWORD" true "PostgreSQL container password" -check_var "POSTGRES_DB" true "PostgreSQL container database" -echo - -# Redis Configuration -echo -e "${YELLOW}Redis Configuration:${NC}" -check_var "REDIS_EVENT_STORE_HOST" true "Redis event store host" -check_var "REDIS_EVENT_STORE_PORT" true "Redis event store port" -check_var "REDIS_CACHE_HOST" true "Redis cache host" -check_var "REDIS_CACHE_PORT" true "Redis cache port" -echo - -# Security Configuration -echo -e "${YELLOW}Security Configuration:${NC}" -check_var "JWT_SECRET" true "JWT secret key" -check_var "JWT_ISSUER" true "JWT issuer" -check_var "JWT_AUDIENCE" true "JWT audience" -check_var "JWT_REALM" true "JWT realm" -check_var "API_KEY" true "API key for internal services" -echo - -# Keycloak Configuration -echo -e "${YELLOW}Keycloak Configuration:${NC}" -check_var "KEYCLOAK_ADMIN" true "Keycloak admin user" -check_var "KEYCLOAK_ADMIN_PASSWORD" true "Keycloak admin password" -check_var "KC_DB" true "Keycloak database type" -check_var "KC_DB_URL" true "Keycloak database URL" -check_var "KC_DB_USERNAME" true "Keycloak database user" -check_var "KC_DB_PASSWORD" true "Keycloak database password" -echo - -# Service Discovery -echo -e "${YELLOW}Service Discovery Configuration:${NC}" -check_var "CONSUL_HOST" true "Consul host" -check_var "CONSUL_PORT" true "Consul port" -echo - -# Messaging Configuration -echo -e "${YELLOW}Messaging Configuration:${NC}" -check_var "ZOOKEEPER_CLIENT_PORT" true "Zookeeper client port" -check_var "KAFKA_BROKER_ID" true "Kafka broker ID" -check_var "KAFKA_ZOOKEEPER_CONNECT" true "Kafka Zookeeper connection" -echo - -# Monitoring Configuration -echo -e "${YELLOW}Monitoring Configuration:${NC}" -check_var "GF_SECURITY_ADMIN_USER" true "Grafana admin user" -check_var "GF_SECURITY_ADMIN_PASSWORD" true "Grafana admin password" -echo - -# Security Checks -echo -e "${BLUE}4. Security validation...${NC}" - -# Check JWT secret strength -if [ -n "$JWT_SECRET" ]; then - if [ ${#JWT_SECRET} -lt 32 ]; then - print_status "WARNING" "JWT_SECRET should be at least 32 characters long for security" - else - print_status "OK" "JWT_SECRET length is adequate (${#JWT_SECRET} characters)" - fi - - if [[ "$JWT_SECRET" == *"default"* ]] || [[ "$JWT_SECRET" == *"change"* ]]; then - print_status "WARNING" "JWT_SECRET appears to be a default value - change for production" - else - print_status "OK" "JWT_SECRET appears to be customized" - fi -fi - -# Check for default passwords -if [ "$POSTGRES_PASSWORD" = "meldestelle" ]; then - print_status "WARNING" "Using default PostgreSQL password - change for production" -fi - -if [ "$KEYCLOAK_ADMIN_PASSWORD" = "admin" ]; then - print_status "WARNING" "Using default Keycloak admin password - change for production" -fi - -if [ "$GF_SECURITY_ADMIN_PASSWORD" = "admin" ]; then - print_status "WARNING" "Using default Grafana admin password - change for production" -fi -echo - -# Port conflict checks -echo -e "${BLUE}5. Port conflict checks...${NC}" -declare -A ports -ports["API_PORT"]=$API_PORT -ports["DB_PORT"]=$DB_PORT -ports["REDIS_EVENT_STORE_PORT"]=$REDIS_EVENT_STORE_PORT -ports["CONSUL_PORT"]=$CONSUL_PORT -ports["ZOOKEEPER_CLIENT_PORT"]=$ZOOKEEPER_CLIENT_PORT - -# Check for duplicate ports -declare -A port_usage -for service in "${!ports[@]}"; do - port=${ports[$service]} - if [ -n "$port" ]; then - if [ -n "${port_usage[$port]}" ]; then - print_status "ERROR" "Port conflict: $service ($port) conflicts with ${port_usage[$port]}" - else - port_usage[$port]=$service - print_status "OK" "$service using port $port" - fi - fi -done -echo - -# Environment-specific checks -echo -e "${BLUE}6. Environment-specific checks...${NC}" -if [ "$APP_ENVIRONMENT" = "production" ]; then - print_status "INFO" "Production environment detected - additional security checks recommended" - - if [ "$LOGGING_LEVEL" = "DEBUG" ]; then - print_status "WARNING" "DEBUG logging enabled in production environment" - fi - - if [ "$SERVER_CORS_ALLOWED_ORIGINS" = "*" ]; then - print_status "WARNING" "CORS allows all origins in production environment" - fi -else - print_status "OK" "Development environment detected" -fi -echo - -# Summary -echo -e "${BLUE}==============================================================================${NC}" -echo -e "${BLUE}Validation Summary${NC}" -echo -e "${BLUE}==============================================================================${NC}" -echo -e "Total checks performed: ${CHECKS}" -echo -e "${GREEN}Successful checks: $((CHECKS - ERRORS - WARNINGS))${NC}" -echo -e "${YELLOW}Warnings: ${WARNINGS}${NC}" -echo -e "${RED}Errors: ${ERRORS}${NC}" -echo - -if [ $ERRORS -eq 0 ]; then - if [ $WARNINGS -eq 0 ]; then - echo -e "${GREEN}✓ All checks passed! Your environment configuration is ready.${NC}" - exit 0 - else - echo -e "${YELLOW}⚠ Configuration is valid but has warnings. Review the warnings above.${NC}" - exit 0 - fi -else - echo -e "${RED}✗ Configuration has errors that must be fixed before running the application.${NC}" - exit 1 -fi diff --git a/settings.gradle.kts b/settings.gradle.kts index 63be3622..08ad1bcb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,79 +33,109 @@ dependencyResolutionManagement { } // ========================================================================== -// CORE & PLATFORM -// ========================================================================== -include(":core:core-domain") -include(":core:core-utils") - -include(":platform:platform-bom") -include(":platform:platform-dependencies") -include(":platform:platform-testing") - -// ========================================================================== -// INFRASTRUCTURE -// ========================================================================== -include(":backend:gateway") -include(":infrastructure:messaging:messaging-client") -include(":infrastructure:messaging:messaging-config") -include(":infrastructure:cache:cache-api") -include(":infrastructure:cache:redis-cache") -include(":infrastructure:event-store:event-store-api") -include(":infrastructure:event-store:redis-event-store") -include(":infrastructure:monitoring:monitoring-client") -include(":infrastructure:monitoring:monitoring-server") - -// ========================================================================== -// BUSINESS DOMAINS (Clean Architecture) +// Backend // ========================================================================== -// --- EVENTS (Competition Management) --- -// include(":domains:events:events-api") -// include(":domains:events:events-common") -// include(":domains:events:events-domain") -// include(":domains:events:events-infrastructure") -// include(":domains:events:events-service") +// === BACKEND - INFRASTRUCTURE === +// --- CACHE --- +include(":backend:infrastructure:cache:cache-api") +include(":backend:infrastructure:cache:redis-cache") + +// --- EVENT STORE --- +include(":backend:infrastructure:event-store:event-store-api") +include(":backend:infrastructure:event-store:redis-event-store") + +// --- GATEWAY --- +include(":backend:infrastructure:gateway") + +// --- MESSAGING --- +include(":backend:infrastructure:messaging:messaging-client") +include(":backend:infrastructure:messaging:messaging-config") + +// --- MONITORING --- +include(":backend:infrastructure:monitoring:monitoring-client") +include(":backend:infrastructure:monitoring:monitoring-server") + +// === BACKEND - SERVICES === +// --- EVENTS (Event Management) --- +// include(":backend:services:events:events-api") +// include(":backend:services:events:events-common") +// include(":backend:services:events:events-domain") +// include(":backend:services:events:events-infrastructure") +// include(":backend:services:events:events-service") // --- HORSES (Horse Management) --- -// include(":domains:horses:horses-api") -// include(":domains:horses:horses-common") -// include(":domains:horses:horses-domain") -// include(":domains:horses:horses-infrastructure") -// include(":domains:horses:horses-service") +// include(":backend:services:horses:horses-api") +// include(":backend:services:horses:horses-common") +// include(":backend:services:horses:horses-domain") +// include(":backend:services:horses:horses-infrastructure") +// include(":backend:services:horses:horses-service") // --- MASTERDATA (The Rulebook) --- -// include(":domains:masterdata:masterdata-api") -// include(":domains:masterdata:masterdata-common") -// include(":domains:masterdata:masterdata-domain") -// include(":domains:masterdata:masterdata-infrastructure") -// include(":domains:masterdata:masterdata-service") +// include(":backend:services:masterdata:masterdata-api") +// include(":backend:services:masterdata:masterdata-common") +// include(":backend:services:masterdata:masterdata-domain") +// include(":backend:services:masterdata:masterdata-infrastructure") +// include(":backend:services:masterdata:masterdata-service") + +// --- MEMBERS (Member Management) --- +// include(":backend:services:members:members-api") +// include(":backend:services:members:members-common") +// include(":backend:services:members:members-domain") +// include(":backend:services:members:members-infrastructure") +// include(":backend:services:members:members-service") + +// --- PING (Ping Service) --- +include(":backend:services:ping:ping-api") +include(":backend:services:ping:ping-service") // --- REGISTRY (Single Source of Truth) --- // Verwaltet Personen, Pferde & Vereine (ZNS Importe). // Ersetzt das alte 'members' und 'horses' Modul. -include(":domains:registry:oeps-importer") // NEU: Der Gatekeeper für ZNS Daten -include(":domains:registry:registry-api") -include(":domains:registry:registry-domain") -include(":domains:registry:registry-service") +include(":backend:services:registry:oeps-importer") // NEU: Der Gatekeeper für ZNS Daten +include(":backend:services:registry:registry-api") +include(":backend:services:registry:registry-domain") +include(":backend:services:registry:registry-service") // ========================================================================== -// TECHNICAL SERVICES +// CORE // ========================================================================== -include(":backend:services:ping:ping-api") -include(":backend:services:ping:ping-service") - -// ========================================================================== -// CLIENTS (Frontend) -// ========================================================================== -include(":frontend:shells:meldestelle-portal") -include(":clients:auth-feature") -include(":clients:ping-feature") -include(":clients:shared") -include(":frontend:core:design-system") -include(":frontend:core:navigation") -include(":frontend:core:network") +include(":core:core-domain") +include(":core:core-utils") // ========================================================================== // DOCUMENTATION // ========================================================================== include(":docs") + +// ========================================================================== +// FRONTEND +// ========================================================================== +// --- CORE --- +include(":frontend:core:design-system") +include(":frontend:core:navigation") +include(":frontend:core:network") +include(":frontend:core:local-db") + +// --- FEATURES --- +include(":frontend:features:auth-feature") +// include(":frontend:features:members-feature") +include(":frontend:features:ping-feature") + +// --- SHARED +include(":frontend:shared") + +// --- SHELLS --- +include(":frontend:shells:meldestelle-portal") + +// ========================================================================== +// PLATFORM +// ========================================================================== +// --- BOM --- +include(":platform:platform-bom") + +// --- DEPENDENCIES --- +include(":platform:platform-dependencies") + +// --- TESTING --- +include(":platform:platform-testing")