### feat: füge Mail-Service-
Some checks failed
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 58s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 5m59s
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m48s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m2s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 1m52s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m44s

This commit is contained in:
Stefan Mogeritsch 2026-04-22 23:17:08 +02:00
parent 309834d90c
commit 6b690232ff
10 changed files with 169 additions and 54 deletions

6
.env
View File

@ -20,6 +20,7 @@ DOCKER_GRADLE_VERSION=9.3.1
DOCKER_JAVA_VERSION=25 DOCKER_JAVA_VERSION=25
DOCKER_NODE_VERSION=24.12.0 DOCKER_NODE_VERSION=24.12.0
DOCKER_NGINX_VERSION=1.28.0-alpine DOCKER_NGINX_VERSION=1.28.0-alpine
DOCKER_CADDY_VERSION=2.11-alpine
# JVM Power Flags (Lokal leer lassen, da Intel/AMD Architektur) # JVM Power Flags (Lokal leer lassen, da Intel/AMD Architektur)
JVM_OPTS_ARM64= JVM_OPTS_ARM64=
@ -96,6 +97,7 @@ CONSUL_IMAGE=hashicorp/consul:1.22.1
CONSUL_PORT=8500:8500 CONSUL_PORT=8500:8500
CONSUL_UDP_PORT=8600:8600/udp CONSUL_UDP_PORT=8600:8600/udp
CONSUL_HOST=consul CONSUL_HOST=consul
CONSUL_HTTP_PORT=8500
SPRING_CLOUD_CONSUL_HOST=consul SPRING_CLOUD_CONSUL_HOST=consul
SPRING_CLOUD_CONSUL_PORT=8500 SPRING_CLOUD_CONSUL_PORT=8500
SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME=api-gateway SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME=api-gateway
@ -173,7 +175,7 @@ MAIL_SMTP_STARTTLS=true
SPRING_MAIL_HOST=localhost SPRING_MAIL_HOST=localhost
SPRING_MAIL_PORT=1025 SPRING_MAIL_PORT=1025
SPRING_MAIL_USERNAME=online-nennen@mo-code.at 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_AUTH=false
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=false SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=false
SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED=false SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED=false
@ -248,7 +250,7 @@ SERIES_CONSUL_PREFER_IP=true
# --- WEB-APP --- # --- WEB-APP ---
CADDY_VERSION=2.11-alpine CADDY_VERSION=2.11-alpine
WEB_APP_PORT=4000:4000 WEB_APP_PORT=8080:80
WEB_BUILD_PROFILE=dev WEB_BUILD_PROFILE=dev
# Lokal: http://localhost:8081 | Produktion: http://10.0.0.50:8081 # Lokal: http://localhost:8081 | Produktion: http://10.0.0.50:8081
WEB_APP_API_URL=http://localhost:8081 WEB_APP_API_URL=http://localhost:8081

View File

@ -45,6 +45,10 @@ jobs:
context: . context: .
dockerfile: backend/services/ping/Dockerfile dockerfile: backend/services/ping/Dockerfile
image: ping-service image: ping-service
- service: mail-service
context: .
dockerfile: backend/services/mail/Dockerfile
image: mail-service
- service: web-app - service: web-app
context: . context: .
dockerfile: config/docker/caddy/web-app/Dockerfile dockerfile: config/docker/caddy/web-app/Dockerfile

View File

@ -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).")
}
}

View File

@ -14,7 +14,7 @@ spring:
host: ${SPRING_MAIL_HOST:smtp.world4you.com} host: ${SPRING_MAIL_HOST:smtp.world4you.com}
port: ${SPRING_MAIL_PORT:587} port: ${SPRING_MAIL_PORT:587}
username: ${SPRING_MAIL_USERNAME:online-nennen@mo-code.at} username: ${SPRING_MAIL_USERNAME:online-nennen@mo-code.at}
password: ${SPRING_MAIL_PASSWORD:} password: ${SPRING_MAIL_PASSWORD:Mogi#2reiten}
properties: properties:
mail: mail:
smtp: smtp:
@ -26,9 +26,10 @@ spring:
consul: consul:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost} host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
port: ${SPRING_CLOUD_CONSUL_PORT:8500} port: ${SPRING_CLOUD_CONSUL_PORT:8500}
enabled: ${SPRING_CLOUD_CONSUL_ENABLED:false}
discovery: discovery:
enabled: true enabled: ${SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED:false}
register: true register: ${SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER:false}
prefer-ip-address: true prefer-ip-address: true
health-check-path: /actuator/health health-check-path: /actuator/health
health-check-interval: 10s health-check-interval: 10s
@ -43,7 +44,12 @@ management:
endpoints: endpoints:
web: web:
exposure: exposure:
include: "health,info,prometheus" include: health,info,prometheus
endpoint:
health:
show-details: always
probes:
enabled: true
# Feature-Flags # Feature-Flags
mail: mail:

View File

@ -3,7 +3,7 @@
metrics metrics
} }
:4000 { :80 {
root * /usr/share/caddy root * /usr/share/caddy
log { log {
output stdout output stdout
@ -17,14 +17,34 @@
encode gzip zstd encode gzip zstd
handle /api/* { # Reverse Proxy: Plan-B leitet nur /api/mail an den Mail-Service weiter (kein API-Gateway nötig)
reverse_proxy api-gateway:8081 handle /api/mail/* {
reverse_proxy mail-service:8085
} }
handle /health { handle /health {
respond "healthy" 200 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 { handle {
try_files {path} /index.html try_files {path} /index.html
file_server file_server

View File

@ -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 COPY config/docker/caddy/web-app/config.json /usr/share/caddy/config.json.tmpl
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
# Copy Pre-built Static Assets from Host # Copy Pre-built Static Assets from Host (WasmJs)
# NOTE: You must run `./gradlew :frontend:shells:meldestelle-portal:jsBrowserDistribution -Pproduction=true` locally first! # NOTE: You must run `./gradlew :frontend:shells:meldestelle-web:wasmJsBrowserDistribution -Pproduction=true` locally first!
COPY frontend/shells/meldestelle-portal/build/dist/js/productionExecutable/ /usr/share/caddy/ 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 # 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 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 # Using the shared asset from existing config structure
COPY config/docker/nginx/web-app/favicon.svg /usr/share/caddy/favicon.svg 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 \ 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"] ENTRYPOINT ["/entrypoint.sh"]
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]

View File

@ -1,4 +1,5 @@
{ {
"apiBaseUrl": "${API_BASE_URL}", "apiBaseUrl": "${API_BASE_URL}",
"mailServiceUrl": "${MAIL_SERVICE_URL}",
"keycloakUrl": "${KEYCLOAK_URL}" "keycloakUrl": "${KEYCLOAK_URL}"
} }

View File

@ -1,13 +1,13 @@
#!/bin/sh #!/bin/sh
set -e 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. # 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.tmpl \
> /usr/share/caddy/index.html > /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.tmpl \
> /usr/share/caddy/config.json > /usr/share/caddy/config.json

View File

@ -6,43 +6,43 @@ services:
# ========================================== # ==========================================
# --- WEB-APP --- # --- WEB-APP ---
web-app: # web-app:
image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/web-app:${DOCKER_TAG:-latest}" # image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/web-app:${DOCKER_TAG:-latest}"
build: # build:
context: . # Wichtig: Root Context für Monorepo Zugriff # context: . # Wichtig: Root Context für Monorepo Zugriff
dockerfile: config/docker/caddy/web-app/Dockerfile # dockerfile: config/docker/caddy/web-app/Dockerfile
args: # args:
# Frontend spezifisch: # # Frontend spezifisch:
CADDY_VERSION: "${DOCKER_CADDY_VERSION:-2.11-alpine}" # CADDY_VERSION: "${DOCKER_CADDY_VERSION:-2.11-alpine}"
# Metadaten: # # Metadaten:
VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}" # VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
BUILD_DATE: "${DOCKER_BUILD_DATE}" # BUILD_DATE: "${DOCKER_BUILD_DATE}"
labels: # labels:
- "org.opencontainers.image.created=${DOCKER_BUILD_DATE}" # - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}"
container_name: "${PROJECT_NAME:-meldestelle}-web-app" # container_name: "${PROJECT_NAME:-meldestelle}-web-app"
restart: unless-stopped # restart: unless-stopped
ports: # ports:
- "${WEB_APP_PORT:-4000:4000}" # - "${WEB_APP_PORT:-4000:4000}"
environment: # environment:
# Runtime Configuration — via envsubst in entrypoint.sh in config.json & index.html injiziert. # # Runtime Configuration — via envsubst in entrypoint.sh in config.json & index.html injiziert.
# Muss die öffentlich erreichbare URL sein (Browser-Zugriff!), NICHT localhost. # # Muss die öffentlich erreichbare URL sein (Browser-Zugriff!), NICHT localhost.
API_BASE_URL: "${WEB_APP_API_URL:-http://localhost:8081}" # API_BASE_URL: "${WEB_APP_API_URL:-http://localhost:8081}"
# Keycloak Public URL (muss vom Browser aus erreichbar sein) # # Keycloak Public URL (muss vom Browser aus erreichbar sein)
KEYCLOAK_URL: "${WEB_APP_KEYCLOAK_URL:-http://localhost:8180}" # KEYCLOAK_URL: "${WEB_APP_KEYCLOAK_URL:-http://localhost:8180}"
depends_on: # depends_on:
api-gateway: # api-gateway:
condition: "service_started" # condition: "service_started"
healthcheck: # healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:4000/" ] # test: [ "CMD", "wget", "--spider", "-q", "http://localhost:4000/" ]
interval: 20s # interval: 20s
timeout: 5s # timeout: 5s
retries: 5 # retries: 5
start_period: 20s # start_period: 20s
networks: # networks:
meldestelle-network: # meldestelle-network:
aliases: # aliases:
- "web-app" # - "web-app"
profiles: [ "gui", "all" ] # profiles: [ "gui", "all" ]
networks: networks:
meldestelle-network: meldestelle-network:

48
dc-planb.yaml Normal file
View File

@ -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