diff --git a/.env b/.env index 5f4e78bc..445b9afc 100644 --- a/.env +++ b/.env @@ -20,6 +20,7 @@ DOCKER_GRADLE_VERSION=9.3.1 DOCKER_JAVA_VERSION=25 DOCKER_NODE_VERSION=24.12.0 DOCKER_NGINX_VERSION=1.28.0-alpine +DOCKER_CADDY_VERSION=2.11-alpine # JVM Power Flags (Lokal leer lassen, da Intel/AMD Architektur) JVM_OPTS_ARM64= @@ -96,6 +97,7 @@ CONSUL_IMAGE=hashicorp/consul:1.22.1 CONSUL_PORT=8500:8500 CONSUL_UDP_PORT=8600:8600/udp CONSUL_HOST=consul +CONSUL_HTTP_PORT=8500 SPRING_CLOUD_CONSUL_HOST=consul SPRING_CLOUD_CONSUL_PORT=8500 SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME=api-gateway @@ -173,7 +175,7 @@ MAIL_SMTP_STARTTLS=true SPRING_MAIL_HOST=localhost SPRING_MAIL_PORT=1025 SPRING_MAIL_USERNAME=online-nennen@mo-code.at -SPRING_MAIL_PASSWORD=dev +SPRING_MAIL_PASSWORD=Mogi#2reiten SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=false SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=false SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED=false @@ -248,7 +250,7 @@ SERIES_CONSUL_PREFER_IP=true # --- WEB-APP --- CADDY_VERSION=2.11-alpine -WEB_APP_PORT=4000:4000 +WEB_APP_PORT=8080:80 WEB_BUILD_PROFILE=dev # Lokal: http://localhost:8081 | Produktion: http://10.0.0.50:8081 WEB_APP_API_URL=http://localhost:8081 diff --git a/.gitea/workflows/docker-publish.yaml b/.gitea/workflows/docker-publish.yaml index 26d526cd..04277a92 100644 --- a/.gitea/workflows/docker-publish.yaml +++ b/.gitea/workflows/docker-publish.yaml @@ -45,6 +45,10 @@ jobs: context: . dockerfile: backend/services/ping/Dockerfile image: ping-service + - service: mail-service + context: . + dockerfile: backend/services/mail/Dockerfile + image: mail-service - service: web-app context: . dockerfile: config/docker/caddy/web-app/Dockerfile diff --git a/backend/services/mail/mail-service/src/main/kotlin/at/mocode/mail/service/config/ExposedConfiguration.kt b/backend/services/mail/mail-service/src/main/kotlin/at/mocode/mail/service/config/ExposedConfiguration.kt new file mode 100644 index 00000000..59cefdaf --- /dev/null +++ b/backend/services/mail/mail-service/src/main/kotlin/at/mocode/mail/service/config/ExposedConfiguration.kt @@ -0,0 +1,34 @@ +package at.mocode.mail.service.config + +import at.mocode.mail.service.persistence.NennungTable +import jakarta.annotation.PostConstruct +import org.jetbrains.exposed.v1.jdbc.Database +import org.jetbrains.exposed.v1.jdbc.SchemaUtils +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.slf4j.LoggerFactory +import org.springframework.context.annotation.Configuration +import javax.sql.DataSource + +/** + * Wires Spring's DataSource into Exposed and ensures the schema exists. + * This replaces the implicit init that previously happened in the polling service. + */ +@Configuration +class ExposedConfiguration( + private val dataSource: DataSource, +) { + private val log = LoggerFactory.getLogger(ExposedConfiguration::class.java) + + @PostConstruct + fun connectAndInitSchema() { + // Bind Exposed to Spring's DataSource + Database.connect(dataSource) + + // Create required tables if missing (idempotent for H2 and typical RDBMS) + transaction { + SchemaUtils.create(NennungTable) + } + + log.info("Exposed connected to DataSource and schema initialized (NennungTable).") + } +} diff --git a/backend/services/mail/mail-service/src/main/resources/application.yaml b/backend/services/mail/mail-service/src/main/resources/application.yaml index 7e0299eb..184a4dc5 100644 --- a/backend/services/mail/mail-service/src/main/resources/application.yaml +++ b/backend/services/mail/mail-service/src/main/resources/application.yaml @@ -14,7 +14,7 @@ spring: host: ${SPRING_MAIL_HOST:smtp.world4you.com} port: ${SPRING_MAIL_PORT:587} username: ${SPRING_MAIL_USERNAME:online-nennen@mo-code.at} - password: ${SPRING_MAIL_PASSWORD:} + password: ${SPRING_MAIL_PASSWORD:Mogi#2reiten} properties: mail: smtp: @@ -26,9 +26,10 @@ spring: consul: host: ${SPRING_CLOUD_CONSUL_HOST:localhost} port: ${SPRING_CLOUD_CONSUL_PORT:8500} + enabled: ${SPRING_CLOUD_CONSUL_ENABLED:false} discovery: - enabled: true - register: true + enabled: ${SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED:false} + register: ${SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER:false} prefer-ip-address: true health-check-path: /actuator/health health-check-interval: 10s @@ -43,7 +44,12 @@ management: endpoints: web: exposure: - include: "health,info,prometheus" + include: health,info,prometheus + endpoint: + health: + show-details: always + probes: + enabled: true # Feature-Flags mail: diff --git a/config/docker/caddy/web-app/Caddyfile b/config/docker/caddy/web-app/Caddyfile index 4179492f..1884f4e5 100644 --- a/config/docker/caddy/web-app/Caddyfile +++ b/config/docker/caddy/web-app/Caddyfile @@ -3,7 +3,7 @@ metrics } -:4000 { +:80 { root * /usr/share/caddy log { output stdout @@ -17,14 +17,34 @@ encode gzip zstd - handle /api/* { - reverse_proxy api-gateway:8081 + # Reverse Proxy: Plan-B leitet nur /api/mail an den Mail-Service weiter (kein API-Gateway nötig) + handle /api/mail/* { + reverse_proxy mail-service:8085 } handle /health { respond "healthy" 200 } + # Korrekte MIME für .wasm sicherstellen (Caddy erkennt es i. d. R. automatisch; hier explizit) + @wasm { + path *.wasm + } + header @wasm Content-Type "application/wasm" + + # Caching-Strategie: Immutable Assets (hash-Dateien) lange cachen + @immutable { + path *.js *.css *.wasm *.png *.svg *.ico *.woff2 *.map + } + header @immutable Cache-Control "public, max-age=31536000, immutable" + + # Keine Cache-Header für SPA-Einstieg und Laufzeitkonfig + @nocache { + path /index.html /config.json + } + header @nocache Cache-Control "no-store" + + # Static file serving mit SPA-Fallback handle { try_files {path} /index.html file_server diff --git a/config/docker/caddy/web-app/Dockerfile b/config/docker/caddy/web-app/Dockerfile index 27fc6e45..4af273ba 100644 --- a/config/docker/caddy/web-app/Dockerfile +++ b/config/docker/caddy/web-app/Dockerfile @@ -31,9 +31,9 @@ COPY config/docker/caddy/web-app/entrypoint.sh /entrypoint.sh COPY config/docker/caddy/web-app/config.json /usr/share/caddy/config.json.tmpl RUN chmod +x /entrypoint.sh -# Copy Pre-built Static Assets from Host -# NOTE: You must run `./gradlew :frontend:shells:meldestelle-portal:jsBrowserDistribution -Pproduction=true` locally first! -COPY frontend/shells/meldestelle-portal/build/dist/js/productionExecutable/ /usr/share/caddy/ +# Copy Pre-built Static Assets from Host (WasmJs) +# NOTE: You must run `./gradlew :frontend:shells:meldestelle-web:wasmJsBrowserDistribution -Pproduction=true` locally first! +COPY frontend/shells/meldestelle-web/build/dist/wasmJs/productionExecutable/ /usr/share/caddy/ # index.html wird als Template abgelegt; der Entrypoint erzeugt daraus zur Laufzeit die finale index.html RUN mv /usr/share/caddy/index.html /usr/share/caddy/index.html.tmpl @@ -41,10 +41,10 @@ RUN mv /usr/share/caddy/index.html /usr/share/caddy/index.html.tmpl # Using the shared asset from existing config structure COPY config/docker/nginx/web-app/favicon.svg /usr/share/caddy/favicon.svg -EXPOSE 4000 +EXPOSE 80 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:4000/health || exit 1 + CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1 ENTRYPOINT ["/entrypoint.sh"] CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/config/docker/caddy/web-app/config.json b/config/docker/caddy/web-app/config.json index 9a6567e0..73468e6d 100644 --- a/config/docker/caddy/web-app/config.json +++ b/config/docker/caddy/web-app/config.json @@ -1,4 +1,5 @@ { "apiBaseUrl": "${API_BASE_URL}", + "mailServiceUrl": "${MAIL_SERVICE_URL}", "keycloakUrl": "${KEYCLOAK_URL}" } diff --git a/config/docker/caddy/web-app/entrypoint.sh b/config/docker/caddy/web-app/entrypoint.sh index 8c5b28f4..86658c2f 100644 --- a/config/docker/caddy/web-app/entrypoint.sh +++ b/config/docker/caddy/web-app/entrypoint.sh @@ -1,13 +1,13 @@ #!/bin/sh set -e -# Ersetze ${API_BASE_URL} und ${KEYCLOAK_URL} in index.html und config.json zur Container-Startzeit. +# Ersetze ${API_BASE_URL}, ${MAIL_SERVICE_URL} und ${KEYCLOAK_URL} in index.html und config.json zur Container-Startzeit. # Caddy bekommt fertige, statische Dateien — kein Template-Parsing mehr nötig. -envsubst '${API_BASE_URL} ${KEYCLOAK_URL}' \ +envsubst '${API_BASE_URL} ${MAIL_SERVICE_URL} ${KEYCLOAK_URL}' \ < /usr/share/caddy/index.html.tmpl \ > /usr/share/caddy/index.html -envsubst '${API_BASE_URL} ${KEYCLOAK_URL}' \ +envsubst '${API_BASE_URL} ${MAIL_SERVICE_URL} ${KEYCLOAK_URL}' \ < /usr/share/caddy/config.json.tmpl \ > /usr/share/caddy/config.json diff --git a/dc-gui.yaml b/dc-gui.yaml index 5ec250bc..fe4f169a 100644 --- a/dc-gui.yaml +++ b/dc-gui.yaml @@ -6,43 +6,43 @@ services: # ========================================== # --- WEB-APP --- - web-app: - image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/web-app:${DOCKER_TAG:-latest}" - build: - context: . # Wichtig: Root Context für Monorepo Zugriff - dockerfile: config/docker/caddy/web-app/Dockerfile - args: - # Frontend spezifisch: - CADDY_VERSION: "${DOCKER_CADDY_VERSION:-2.11-alpine}" - # Metadaten: - VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" - BUILD_DATE: "${DOCKER_BUILD_DATE}" - labels: - - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" - container_name: "${PROJECT_NAME:-meldestelle}-web-app" - restart: unless-stopped - ports: - - "${WEB_APP_PORT:-4000:4000}" - environment: - # Runtime Configuration — via envsubst in entrypoint.sh in config.json & index.html injiziert. - # Muss die öffentlich erreichbare URL sein (Browser-Zugriff!), NICHT localhost. - API_BASE_URL: "${WEB_APP_API_URL:-http://localhost:8081}" - # Keycloak Public URL (muss vom Browser aus erreichbar sein) - KEYCLOAK_URL: "${WEB_APP_KEYCLOAK_URL:-http://localhost:8180}" - depends_on: - api-gateway: - condition: "service_started" - healthcheck: - test: [ "CMD", "wget", "--spider", "-q", "http://localhost:4000/" ] - interval: 20s - timeout: 5s - retries: 5 - start_period: 20s - networks: - meldestelle-network: - aliases: - - "web-app" - profiles: [ "gui", "all" ] +# web-app: +# image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/web-app:${DOCKER_TAG:-latest}" +# build: +# context: . # Wichtig: Root Context für Monorepo Zugriff +# dockerfile: config/docker/caddy/web-app/Dockerfile +# args: +# # Frontend spezifisch: +# CADDY_VERSION: "${DOCKER_CADDY_VERSION:-2.11-alpine}" +# # Metadaten: +# VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" +# BUILD_DATE: "${DOCKER_BUILD_DATE}" +# labels: +# - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" +# container_name: "${PROJECT_NAME:-meldestelle}-web-app" +# restart: unless-stopped +# ports: +# - "${WEB_APP_PORT:-4000:4000}" +# environment: +# # Runtime Configuration — via envsubst in entrypoint.sh in config.json & index.html injiziert. +# # Muss die öffentlich erreichbare URL sein (Browser-Zugriff!), NICHT localhost. +# API_BASE_URL: "${WEB_APP_API_URL:-http://localhost:8081}" +# # Keycloak Public URL (muss vom Browser aus erreichbar sein) +# KEYCLOAK_URL: "${WEB_APP_KEYCLOAK_URL:-http://localhost:8180}" +# depends_on: +# api-gateway: +# condition: "service_started" +# healthcheck: +# test: [ "CMD", "wget", "--spider", "-q", "http://localhost:4000/" ] +# interval: 20s +# timeout: 5s +# retries: 5 +# start_period: 20s +# networks: +# meldestelle-network: +# aliases: +# - "web-app" +# profiles: [ "gui", "all" ] networks: meldestelle-network: diff --git a/dc-planb.yaml b/dc-planb.yaml new file mode 100644 index 00000000..c32d76e1 --- /dev/null +++ b/dc-planb.yaml @@ -0,0 +1,48 @@ +name: "${PROJECT_NAME:-meldestelle}" + +services: + # --- Statische Web-App (WASM) --- + web-app: + image: ${REGISTRY_INTERNAL:-10.0.0.22:3000}/mocode-software/meldestelle/web-app:${DOCKER_TAG:-latest} + container_name: ${PROJECT_NAME:-meldestelle}-web-app + restart: unless-stopped + environment: + # Diese Variablen werden vom Web-Container verwendet, um die Ziel-URLs in die index.html zu injizieren + API_BASE_URL: ${API_BASE_URL:-https://api.mo-code.at} + MAIL_SERVICE_URL: ${MAIL_SERVICE_URL:-https://api.mo-code.at/mail} + ports: + - "${WEB_APP_PORT:-8080:80}" + networks: [meldestelle-network] + + # --- Mail-Service (Plan-B: Form -> E-Mail) --- + mail-service: + image: ${REGISTRY_INTERNAL:-10.0.0.22:3000}/mocode-software/meldestelle/mail-service:${DOCKER_TAG:-latest} + container_name: ${PROJECT_NAME:-meldestelle}-mail-service + restart: unless-stopped + environment: + # Server-Port im Container (Spring Boot) + SERVER_PORT: ${SERVER_PORT:-8085} + + # SMTP (World4You - PROD) + SPRING_MAIL_HOST: ${SPRING_MAIL_HOST:-smtp.world4you.com} + SPRING_MAIL_PORT: ${SPRING_MAIL_PORT:-587} + SPRING_MAIL_USERNAME: ${SPRING_MAIL_USERNAME:-online-nennen@mo-code.at} + SPRING_MAIL_PASSWORD: ${SPRING_MAIL_PASSWORD:-changeme} + SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH: ${SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH:-true} + SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: ${SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE:-true} + + # Feature-Flags / Infra-Off + MAIL_POLLING_ENABLED: ${MAIL_POLLING_ENABLED:-false} + SPRING_CLOUD_CONSUL_ENABLED: ${SPRING_CLOUD_CONSUL_ENABLED:-false} + SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED: ${SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED:-false} + SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER: ${SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER:-false} + + # Datenbank: H2 In-Memory (Default in application.yaml) – KEINE Postgres-Variablen setzen + + ports: + - "8092:${SERVER_PORT:-8085}" # Extern 8092 beibehalten + networks: [meldestelle-network] + +networks: + meldestelle-network: + driver: bridge