diff --git a/.env b/.env new file mode 100644 index 00000000..e8cdce14 --- /dev/null +++ b/.env @@ -0,0 +1,246 @@ +# ========================================== +# Meldestelle – Docker Compose Environment +# Single Source of Truth (SSoT) +# ========================================== +# WARNING: This file contains secrets (passwords). +# Do NOT commit this file to version control if it contains production secrets. + +# --- PROJECT --- +PROJECT_NAME=meldestelle + +# --- BACKUP --- +BACKUP_DIR=/home/stefan/backups/meldestelle +BACKUP_RETENTION_DAYS=7 + +# Docker build versions (optional overrides) +DOCKER_VERSION=1.0.0-SNAPSHOT +DOCKER_REGISTRY=git.mo-code.at/mocode-software/meldestelle +DOCKER_BUILD_DATE=2026-03-16T12:00:00Z +DOCKER_GRADLE_VERSION=9.3.1 +DOCKER_JAVA_VERSION=25 +DOCKER_NODE_VERSION=24.12.0 +DOCKER_NGINX_VERSION=1.28.0-alpine + +# JVM Power Flags (Lokal leer lassen, da Intel/AMD Architektur) +JVM_OPTS_ARM64= + +# --- POSTGRES --- +POSTGRES_IMAGE=postgres:16-alpine +POSTGRES_SHARED_BUFFERS=256MB +POSTGRES_EFFECTIVE_CACHE_SIZE=768MB +POSTGRES_USER=pg-user +POSTGRES_PASSWORD=pg-password +POSTGRES_DB=pg-meldestelle-db +POSTGRES_PORT=5432:5432 +POSTGRES_DB_URL=jdbc:postgresql://postgres:5432/pg-meldestelle-db + +# --- VALKEY (formerly Redis) --- +VALKEY_IMAGE=valkey/valkey:9-alpine +VALKEY_PASSWORD=valkey-password +VALKEY_PORT=6379:6379 +VALKEY_SERVER_HOSTNAME=valkey +VALKEY_SERVER_PORT=6379 +VALKEY_SERVER_CONNECT_TIMEOUT=5s +VALKEY_POLICY=allkeys-lru +VALKEY_MAX_MEMORY=256MB +SPRING_DATA_VALKEY_HOST=localhost +SPRING_DATA_VALKEY_PORT=6379 +SPRING_DATA_VALKEY_PASSWORD=valkey-password + +# --- KEYCLOAK --- +KEYCLOAK_IMAGE_TAG=latest +KC_HEAP_MIN=512M +KC_HEAP_MAX=1024M +# Lokale Entwicklung: start-dev (kein Pre-Build nötig, kein --optimized) +# Server/Produktion: start --optimized --import-realm (nutzt das pre-built Registry-Image) +KC_COMMAND=start-dev --import-realm +# System-Admin (Master Console) +KC_BOOTSTRAP_ADMIN_USERNAME=kc-admin +KC_BOOTSTRAP_ADMIN_PASSWORD=kc-password +# Fach-Admin User Passwort (wird im Realm Import genutzt) +# Hinweis: Wenn du das hier änderst, müsstest du auch die JSON anpassen +# oder dort eine Variable nutzen. + +KC_DB=postgres +KC_DB_SCHEMA=keycloak +KC_DB_URL=jdbc:postgresql://postgres:5432/pg-meldestelle-db +KC_DB_USERNAME=pg-user +KC_DB_PASSWORD=meldestelle + +# Lokal: localhost | Server: echte IP oder Domain (z.B. 10.0.0.50 oder auth.meldestelle.at) +# WICHTIG: Nur den Hostnamen angeben, OHNE Port (Keycloak 26.x hostname v2) +KC_HOSTNAME=localhost +# false = Zugriff über beliebige Hostnamen erlaubt (nötig ohne TLS / für HTTP-Betrieb) +KC_HOSTNAME_STRICT=false +KC_HOSTNAME_STRICT_HTTPS=false +KC_PORT=8180:8080 +KC_MANAGEMENT_PORT=9000:9000 + +KC_HTTP_ENABLE=true + +KC_API_GATEWAY_CLIENT_SECRET=K5RqonwVOaxPKaXVH4mbthSRbjRh5tOK +# KC_POSTMAN_CLIENT_SECRET=postman-secret-123 +# KC_BOOTSTRAP_ADMIN_PASSWORD=Admin#1234 +KC_FRONTEND_URL=http://localhost:8180 +KC_PROXY_HEADERS=xforwarded + +# --- KEYCLOAK TOKEN VALIDATION --- +# Public Issuer URI (must match the token issuer from browser/postman) +# Lokal: http://localhost:8180 | Produktion: http://10.0.0.50:8180 +SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8180/realms/meldestelle +# Internal JWK Set URI (for service-to-service communication within Docker) +SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI=http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs + +# --- CONSUL --- +CONSUL_IMAGE=hashicorp/consul:1.22.1 +CONSUL_PORT=8500:8500 +CONSUL_UDP_PORT=8600:8600/udp +CONSUL_HOST=consul +SPRING_CLOUD_CONSUL_HOST=consul +SPRING_CLOUD_CONSUL_PORT=8500 +SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME=api-gateway +SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS=true + +# --- Zipkin --- +ZIPKIN_IMAGE=openzipkin/zipkin:3 +ZIPKIN_MIN_HEAP=256M +ZIPKIN_MAX_HEAP=512M +ZIPKIN_PORT=9411:9411 +ZIPKIN_ENDPOINT=http://zipkin:9411/api/v2/spans +ZIPKIN_SAMPLING_PROBABILITY=1.0 + +# --- Mailpit --- +MAILPIT_IMAGE=axllent/mailpit:v1.29 +MAILPIT_WEB_PORT=8025:8025 +MAILPIT_SMTP_PORT=1025:1025 + +# --- PGADMIN --- +PGADMIN_IMAGE=dpage/pgadmin4:8 +PGADMIN_EMAIL=meldestelle@mo-code.at +PGADMIN_PASSWORD=pgadmin +PGADMIN_PORT=8888:80 + +# --- POSTGRES-EXPORTER --- +POSTGRES_EXPORTER_IMAGE=prometheuscommunity/postgres-exporter:v0.18.0 + +# --- ALERTMANAGER --- +ALERTMANAGER_IMAGE=prom/alertmanager:v0.29.0 +ALERTMANAGER_PORT=9093:9093 + +# --- PROMETHEUS --- +PROMETHEUS_IMAGE=prom/prometheus:v3.7.3 +PROMETHEUS_PORT=9090:9090 + +# --- GRAFANA --- +GF_IMAGE=grafana/grafana:12.3 +GF_ADMIN_USER=gf-admin +GF_ADMIN_PASSWORD=gf-password +GF_PORT=3000:3000 + +# --- API-GATEWAY --- +GATEWAY_PORT=8081:8081 +GATEWAY_DEBUG_PORT=5005:5005 +GATEWAY_SERVER_PORT=8081 +GATEWAY_SPRING_PROFILES_ACTIVE=docker +GATEWAY_DEBUG=true +GATEWAY_SERVICE_NAME=api-gateway +GATEWAY_CONSUL_PREFER_IP=true + +# --- PING-SERVICE --- +PING_SPRING_PROFILES_ACTIVE=docker +PING_PORT=8082:8082 +PING_DEBUG_PORT=5006:5006 +PING_SERVER_PORT=8082 +PING_DEBUG=true +PING_SERVICE_NAME=ping-service +PING_CONSUL_PREFER_IP=true + +# --- MAIL-SERVICE --- +MAIL_PORT=8083:8083 +MAIL_DEBUG_PORT=5014:5014 +MAIL_SERVER_PORT=8083 +MAIL_SPRING_PROFILES_ACTIVE=docker +MAIL_DEBUG=true +MAIL_SERVICE_NAME=mail-service +MAIL_CONSUL_PREFER_IP=true +MAIL_SMTP_HOST=smtp.world4you.com +MAIL_SMTP_PORT=587 +MAIL_SMTP_USER=online-nennen@mo-code.at +MAIL_SMTP_PASSWORD=secret +MAIL_SMTP_AUTH=true +MAIL_SMTP_STARTTLS=true + +# --- MASTERDATA-SERVICE --- +MASTERDATA_PORT=8086:8086 +MASTERDATA_DEBUG_PORT=5007:5007 +MASTERDATA_SERVER_PORT=8086 +MASTERDATA_SPRING_PROFILES_ACTIVE=docker +MASTERDATA_DEBUG=true +MASTERDATA_SERVICE_NAME=masterdata-service +MASTERDATA_CONSUL_PREFER_IP=true + +# --- EVENTS-SERVICE --- +EVENTS_PORT=8085:8085 +EVENTS_DEBUG_PORT=5008:5008 +EVENTS_SERVER_PORT=8085 +EVENTS_SPRING_PROFILES_ACTIVE=docker +EVENTS_DEBUG=true +EVENTS_SERVICE_NAME=events-service +EVENTS_CONSUL_PREFER_IP=true + +# --- ZNS-IMPORT-SERVICE --- +ZNS_IMPORT_PORT=8095:8095 +ZNS_IMPORT_DEBUG_PORT=5009:5009 +ZNS_IMPORT_SERVER_PORT=8095 +ZNS_IMPORT_SPRING_PROFILES_ACTIVE=docker +ZNS_IMPORT_DEBUG=true +ZNS_IMPORT_SERVICE_NAME=zns-import-service +ZNS_IMPORT_CONSUL_PREFER_IP=true + +# --- RESULTS-SERVICE --- +RESULTS_PORT=8088:8088 +RESULTS_DEBUG_PORT=5010:5010 +RESULTS_SERVER_PORT=8088 +RESULTS_SPRING_PROFILES_ACTIVE=docker +RESULTS_DEBUG=true +RESULTS_SERVICE_NAME=results-service +RESULTS_CONSUL_PREFER_IP=true + +# --- BILLING-SERVICE --- +BILLING_PORT=8087:8087 +BILLING_DEBUG_PORT=5012:5012 +BILLING_SERVER_PORT=8087 +BILLING_SPRING_PROFILES_ACTIVE=docker +BILLING_DEBUG=true +BILLING_SERVICE_NAME=billing-service +BILLING_CONSUL_PREFER_IP=true + +# --- SCHEDULING-SERVICE --- +SCHEDULING_PORT=8084:8084 +SCHEDULING_DEBUG_PORT=5013:5013 +SCHEDULING_SERVER_PORT=8084 +SCHEDULING_SPRING_PROFILES_ACTIVE=docker +SCHEDULING_DEBUG=true +SCHEDULING_SERVICE_NAME=scheduling-service +SCHEDULING_CONSUL_PREFER_IP=true + +# --- SERIES-SERVICE --- +SERIES_PORT=8089:8089 +SERIES_DEBUG_PORT=5011:5011 +SERIES_SERVER_PORT=8089 +SERIES_SPRING_PROFILES_ACTIVE=docker +SERIES_DEBUG=true +SERIES_SERVICE_NAME=series-service +SERIES_CONSUL_PREFER_IP=true + +# --- WEB-APP --- +CADDY_VERSION=2.11-alpine +WEB_APP_PORT=4000:4000 +WEB_BUILD_PROFILE=dev +# Lokal: http://localhost:8081 | Produktion: http://10.0.0.50:8081 +WEB_APP_API_URL=http://localhost:8081 +WEB_APP_KEYCLOAK_URL=http://auth.mo-code.at + +# --- DESKTOP-APP --- +DESKTOP_APP_VNC_PORT=5901:5901 +DESKTOP_APP_NOVNC_PORT=6080:6080 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..85e7c1df --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/backend/infrastructure/security/src/main/kotlin/at/mocode/infrastructure/security/GlobalSecurityConfig.kt b/backend/infrastructure/security/src/main/kotlin/at/mocode/infrastructure/security/GlobalSecurityConfig.kt index 753632cd..9e413517 100644 --- a/backend/infrastructure/security/src/main/kotlin/at/mocode/infrastructure/security/GlobalSecurityConfig.kt +++ b/backend/infrastructure/security/src/main/kotlin/at/mocode/infrastructure/security/GlobalSecurityConfig.kt @@ -52,10 +52,12 @@ class GlobalSecurityConfig { @Bean fun jwtDecoder(): JwtDecoder { - // Wenn jwk-set-uri gesetzt ist, nutzen wir sie. - // Wir verzichten auf den Issuer-Check für maximale Flexibilität zwischen Docker/Host. - val jwkSetUri = System.getenv("SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI") - ?: "http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs" + // 1. Suche in System-Properties (Spring injects these) + // 2. Suche in Environment Variables + // 3. Fallback auf localhost (IDE-Start) oder keycloak (Docker-Start) + val jwkSetUri = System.getProperty("spring.security.oauth2.resourceserver.jwt.jwk-set-uri") + ?: System.getenv("SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI") + ?: "http://localhost:8180/realms/meldestelle/protocol/openid-connect/certs" val decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build() val validator = DelegatingOAuth2TokenValidator(JwtTimestampValidator()) diff --git a/backend/services/ping/ping-service/build.gradle.kts b/backend/services/ping/ping-service/build.gradle.kts index 83aa6c5e..20d33731 100644 --- a/backend/services/ping/ping-service/build.gradle.kts +++ b/backend/services/ping/ping-service/build.gradle.kts @@ -1,4 +1,4 @@ -plugins { + plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.kotlinSpring) alias(libs.plugins.kotlinJpa) @@ -37,8 +37,7 @@ dependencies { implementation(libs.bundles.database.complete) // === Resilience === - implementation(libs.resilience4j.spring.boot3) - implementation(libs.resilience4j.reactor) + implementation(libs.bundles.resilience) implementation(libs.spring.boot.starter.aop) // === Testing === diff --git a/backend/services/ping/ping-service/src/main/kotlin/at/mocode/ping/infrastructure/web/PingController.kt b/backend/services/ping/ping-service/src/main/kotlin/at/mocode/ping/infrastructure/web/PingController.kt index e30c0f47..fa25a689 100644 --- a/backend/services/ping/ping-service/src/main/kotlin/at/mocode/ping/infrastructure/web/PingController.kt +++ b/backend/services/ping/ping-service/src/main/kotlin/at/mocode/ping/infrastructure/web/PingController.kt @@ -2,6 +2,7 @@ package at.mocode.ping.infrastructure.web import at.mocode.ping.api.* import at.mocode.ping.application.PingUseCase +import at.mocode.ping.domain.Ping import at.mocode.ping.infrastructure.PingProperties import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker import org.slf4j.LoggerFactory @@ -20,7 +21,7 @@ import kotlin.uuid.ExperimentalUuidApi */ @RestController @OptIn(ExperimentalUuidApi::class) -class PingController( +open class PingController( private val pingUseCase: PingUseCase, private val properties: PingProperties ) : PingApi { @@ -43,10 +44,16 @@ class PingController( override suspend fun enhancedPing( @RequestParam(required = false, defaultValue = "false") simulate: Boolean ): EnhancedPingResponse { + logger.info("Enhanced ping requested, simulate: {}", simulate) val start = System.nanoTime() - if (simulate && Random.nextDouble() < 0.6) { - throw RuntimeException("Simulated service failure") + if (simulate) { + if (Random.nextDouble() < 0.6) { + logger.info("Simulating service failure now...") + throw SimulatedException("Simulated service failure") + } else { + logger.info("Simulation mode ACTIVE, but this time lucky: Request passed!") + } } val domainPing = pingUseCase.executePing("Enhanced Ping") @@ -61,6 +68,8 @@ class PingController( ) } + class SimulatedException(message: String) : RuntimeException(message) + // Neue Endpunkte @GetMapping("/ping/public") @@ -70,7 +79,7 @@ class PingController( } @GetMapping("/ping/secure") - @PreAuthorize("hasRole('MELD_USER') or hasRole('MELD_ADMIN')") // Beispiel-Rollen + @PreAuthorize("hasRole('ROLE_MELD_USER') or hasRole('ROLE_MELD_ADMIN')") // Beispiel-Rollen override suspend fun securePing(): PingResponse { val domainPing = pingUseCase.executePing("Secure Ping") return createResponse(domainPing, "secure-pong") @@ -79,7 +88,7 @@ class PingController( @GetMapping("/ping/sync") override suspend fun syncPings( // Changed the parameter name to 'since' to match SyncManager convention - @RequestParam(required = false, defaultValue = "0") since: Long + @RequestParam(name = "lastSyncTimestamp", required = false, defaultValue = "0") since: Long ): List { return pingUseCase.getPingsSince(since).map { PingEvent( @@ -91,7 +100,7 @@ class PingController( } // Helper - private fun createResponse(domainPing: at.mocode.ping.domain.Ping, status: String) = PingResponse( + private fun createResponse(domainPing: Ping, status: String) = PingResponse( status = status, timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter), service = properties.serviceName @@ -99,8 +108,8 @@ class PingController( // Fallback @Suppress("unused", "UNUSED_PARAMETER") - fun fallbackPing(simulate: Boolean, ex: Exception): EnhancedPingResponse { - logger.warn("Circuit breaker fallback triggered: {}", ex.message) + open fun fallbackPing(simulate: Boolean, ex: Throwable): EnhancedPingResponse { + logger.error("CIRCUIT BREAKER FALLBACK TRIGGERED! Reason: {}", ex.message, ex) return EnhancedPingResponse( status = "fallback", timestamp = java.time.OffsetDateTime.now().format(formatter), diff --git a/backend/services/ping/ping-service/src/main/kotlin/at/mocode/ping/infrastructure/web/PingExceptionHandler.kt b/backend/services/ping/ping-service/src/main/kotlin/at/mocode/ping/infrastructure/web/PingExceptionHandler.kt new file mode 100644 index 00000000..d2d60716 --- /dev/null +++ b/backend/services/ping/ping-service/src/main/kotlin/at/mocode/ping/infrastructure/web/PingExceptionHandler.kt @@ -0,0 +1,32 @@ +package at.mocode.ping.infrastructure.web + +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.http.ProblemDetail +import org.springframework.security.access.AccessDeniedException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice + +@RestControllerAdvice +class PingExceptionHandler { + private val log = LoggerFactory.getLogger(PingExceptionHandler::class.java) + + @ExceptionHandler(AccessDeniedException::class) + fun handleAccessDenied(ex: AccessDeniedException): ProblemDetail { + log.warn("Zugriff verweigert: ${ex.message}") + return ProblemDetail.forStatusAndDetail(HttpStatus.FORBIDDEN, "Nicht berechtigt: ${ex.message}") + } + + @ExceptionHandler(Exception::class) + fun handleAll(ex: Exception): ProblemDetail { + log.error("Unerwarteter Fehler: ", ex) + return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.message ?: "Ein interner Fehler ist aufgetreten") + } + + @ExceptionHandler(RuntimeException::class) + fun handleRuntime(ex: RuntimeException): ProblemDetail { + log.error("Interner Fehler: ", ex) + return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.message ?: "Unbekannter Fehler") + } + +} diff --git a/backend/services/ping/ping-service/src/test/kotlin/at/mocode/ping/infrastructure/web/PingControllerTest.kt b/backend/services/ping/ping-service/src/test/kotlin/at/mocode/ping/infrastructure/web/PingControllerTest.kt index 517162db..fee1b8df 100644 --- a/backend/services/ping/ping-service/src/test/kotlin/at/mocode/ping/infrastructure/web/PingControllerTest.kt +++ b/backend/services/ping/ping-service/src/test/kotlin/at/mocode/ping/infrastructure/web/PingControllerTest.kt @@ -40,12 +40,17 @@ import kotlin.uuid.ExperimentalUuidApi controllers = [PingController::class], properties = ["spring.aop.proxy-target-class=true"] ) +@Import( + PingControllerTest.PingControllerTestConfig::class, + io.github.resilience4j.springboot3.circuitbreaker.autoconfigure.CircuitBreakerAutoConfiguration::class, + io.github.resilience4j.springboot3.circuitbreaker.autoconfigure.CircuitBreakerMetricsAutoConfiguration::class, + org.springframework.boot.autoconfigure.aop.AopAutoConfiguration::class +) @ContextConfiguration(classes = [TestPingServiceApplication::class]) @ActiveProfiles("test") -@Import(PingControllerTest.PingControllerTestConfig::class) @AutoConfigureMockMvc(addFilters = false) @OptIn(ExperimentalUuidApi::class) -class PingControllerTest { +open class PingControllerTest { @Autowired private lateinit var mockMvc: MockMvc @@ -125,11 +130,24 @@ class PingControllerTest { // Then val json = objectMapper.readTree(result.response.contentAsString) - assertThat(json["status"].asText()).isEqualTo("pong") assertThat(json["service"].asText()).isEqualTo(properties.serviceName) verify { pingUseCase.executePing("Enhanced Ping") } } + @Test + fun `should return fallback when simulation failure occurs`() { + // Given + val controller = PingController(pingUseCase, properties) + + // When + val response = controller.fallbackPing(simulate = true, ex = PingController.SimulatedException("test")) + + // Then + assertThat(response.status).isEqualTo("fallback") + assertThat(response.service).isEqualTo(properties.serviceNameFallback) + assertThat(response.circuitBreakerState).isEqualTo("OPEN") + } + @Test fun `should return health check response with status up`() { // When @@ -159,7 +177,7 @@ class PingControllerTest { ) // When - val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("since", timestamp.toString())) + val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("lastSyncTimestamp", timestamp.toString())) .andExpect(request().asyncStarted()) .andReturn() @@ -183,7 +201,7 @@ class PingControllerTest { every { pingUseCase.getPingsSince(timestamp) } returns emptyList() // When - val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("since", timestamp.toString())) + val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("lastSyncTimestamp", timestamp.toString())) .andExpect(request().asyncStarted()) .andReturn() diff --git a/dc-infra.yaml b/dc-infra.yaml index 421d9734..9b5cfa71 100644 --- a/dc-infra.yaml +++ b/dc-infra.yaml @@ -82,13 +82,18 @@ services: restart: unless-stopped profiles: [ "infra", "all" ] environment: - KC_BOOTSTRAP_ADMIN_USERNAME: "${KC_ADMIN_USERNAME:-kc-admin}" - KC_BOOTSTRAP_ADMIN_PASSWORD: "${KC_ADMIN_PASSWORD:-kc-password}" + KC_BOOTSTRAP_ADMIN_USERNAME: "${KC_BOOTSTRAP_ADMIN_USERNAME:-kc-admin}" + KC_BOOTSTRAP_ADMIN_PASSWORD: "${KC_BOOTSTRAP_ADMIN_PASSWORD:-kc-password}" + + KC_FRONTEND_URL: "${KC_FRONTEND_URL:-http://localhost:8180}" + KC_PROXY_HEADERS: "${KC_PROXY_HEADERS:-xforwarded}" + KC_DB: "${KC_DB:-postgres}" KC_DB_SCHEMA: "${KC_DB_SCHEMA:-keycloak}" KC_DB_URL: "jdbc:postgresql://postgres:5432/${POSTGRES_DB:-pg-meldestelle-db}" KC_DB_USERNAME: "${POSTGRES_USER:-pg-user}" KC_DB_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}" + # Hostname-Konfiguration: Für lokale Entwicklung "localhost", auf dem Server die echte IP/Domain setzen KC_HOSTNAME: "${KC_HOSTNAME:-localhost}" # WICHTIG: false erlaubt Zugriff über beliebige Hostnamen (nötig für Server-Betrieb ohne TLS) @@ -98,6 +103,7 @@ services: KC_HTTP_ENABLED: "true" # Admin-Interface explizit auf allen Interfaces binden (0.0.0.0) KC_HTTP_MANAGEMENT_PORT: "9000" + KC_HEALTH_ENABLED: "true" KC_METRICS_ENABLED: "true" # Integration der Power-Flags diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 562d10a5..f6779035 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -255,6 +255,7 @@ zipkin-server = { module = "io.zipkin:zipkin-server", version.ref = "zipkin" } resilience4j-spring-boot3 = { module = "io.github.resilience4j:resilience4j-spring-boot3", version.ref = "resilience4j" } resilience4j-reactor = { module = "io.github.resilience4j:resilience4j-reactor", version.ref = "resilience4j" } +resilience4j-kotlin = { module = "io.github.resilience4j:resilience4j-kotlin", version.ref = "resilience4j" } auth0-java-jwt = { module = "com.auth0:java-jwt", version.ref = "auth0Jwt" } keycloak-admin-client = { module = "org.keycloak:keycloak-admin-client", version.ref = "keycloakAdminClient" } @@ -392,7 +393,8 @@ jackson-kotlin = [ ] resilience = [ "resilience4j-spring-boot3", - "resilience4j-reactor" + "resilience4j-reactor", + "resilience4j-kotlin" ] [plugins]