diff --git a/backend/services/entries/entries-service/Dockerfile b/backend/services/entries/entries-service/Dockerfile new file mode 100644 index 00000000..3b4e91b0 --- /dev/null +++ b/backend/services/entries/entries-service/Dockerfile @@ -0,0 +1,131 @@ +# syntax=docker/dockerfile:1.8 + +# =================================================================== +# Multi-stage Dockerfile for Meldestelle Entries Service +# =================================================================== + +# === CENTRALIZED BUILD ARGUMENTS === +ARG GRADLE_VERSION +ARG JAVA_VERSION +ARG BUILD_DATE +ARG VERSION + +FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder + +LABEL stage=builder \ + service=entries-service \ + maintainer="Meldestelle Development Team" \ + version="${VERSION}" \ + build.date="${BUILD_DATE}" + +WORKDIR /workspace + +ENV GRADLE_OPTS="-Dorg.gradle.caching=true \ + -Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.workers.max=2 \ + -Dorg.gradle.jvmargs=-Xmx2g \ + -XX:+UseParallelGC \ + -XX:MaxMetaspaceSize=512m" + +ENV GRADLE_USER_HOME=/home/gradle/.gradle + +COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./ +COPY gradle/ gradle/ +RUN chmod +x gradlew +COPY platform/ platform/ +COPY frontend/ frontend/ +COPY core/ core/ +COPY backend/ backend/ +COPY docs/ docs/ +COPY build.gradle.kts ./ + +# Copy entries modules +COPY backend/services/entries/entries-service/ backend/services/entries/entries-service/ + +RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ + --mount=type=cache,target=/home/gradle/.gradle/wrapper \ + ./gradlew :backend:services:entries:entries-service:dependencies --no-daemon --info + +RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ + --mount=type=cache,target=/home/gradle/.gradle/wrapper \ + ./gradlew :backend:services:entries:entries-service:bootJar --no-daemon --info + +# =================================================================== +# Runtime stage +# =================================================================== +FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime + +ARG BUILD_DATE +ARG VERSION +ARG JAVA_VERSION + +ENV JAVA_VERSION=${JAVA_VERSION} \ + VERSION=${VERSION} \ + BUILD_DATE=${BUILD_DATE} + +LABEL service="entries-service" \ + version="${VERSION}" \ + description="Microservice for Entries Management" \ + maintainer="Meldestelle Development Team" \ + java.version="${JAVA_VERSION}" \ + build.date="${BUILD_DATE}" + +ARG APP_USER=appuser +ARG APP_GROUP=appgroup +ARG APP_UID=1001 +ARG APP_GID=1001 + +WORKDIR /app + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache curl tzdata tini && \ + rm -rf /var/cache/apk/* && \ + addgroup -g ${APP_GID} -S ${APP_GROUP} && \ + adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh && \ + mkdir -p /app/logs /app/tmp /app/config && \ + chown -R ${APP_USER}:${APP_GROUP} /app && \ + chmod -R 750 /app + +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} \ + /workspace/backend/services/entries/entries-service/build/libs/*.jar app.jar + +USER ${APP_USER} + +EXPOSE 8083 5005 + +HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -fsS --max-time 2 http://localhost:8083/actuator/health/readiness || exit 1 + +ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \ + -XX:+UseG1GC \ + -XX:+UseStringDeduplication \ + -XX:+UseContainerSupport \ + -XX:G1HeapRegionSize=16m \ + -XX:G1ReservePercent=25 \ + -XX:InitiatingHeapOccupancyPercent=30 \ + -XX:+AlwaysPreTouch \ + -XX:+DisableExplicitGC \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Duser.timezone=Europe/Vienna \ + -Dspring.backgroundpreinitializer.ignore=true \ + -Dmanagement.endpoints.web.exposure.include=health,info,metrics,prometheus \ + -Dmanagement.endpoint.health.show-details=always \ + -Dmanagement.prometheus.metrics.export.enabled=true" + +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS +ENV SERVER_PORT=8083 +ENV LOGGING_LEVEL_ROOT=INFO + +ENTRYPOINT ["tini", "--", "sh", "-c", "\ + echo 'Starting Entries Service with Java ${JAVA_VERSION}...'; \ + echo 'Service port: ${SERVER_PORT}'; \ + if [ \"${DEBUG:-false}\" = \"true\" ]; then \ + echo 'DEBUG mode enabled'; \ + exec java ${JAVA_OPTS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar; \ + else \ + exec java ${JAVA_OPTS} -jar app.jar; \ + fi"] diff --git a/backend/services/entries/entries-service/build.gradle.kts b/backend/services/entries/entries-service/build.gradle.kts new file mode 100644 index 00000000..b13592b2 --- /dev/null +++ b/backend/services/entries/entries-service/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSpring) + alias(libs.plugins.kotlinJpa) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependencyManagement) +} + +springBoot { + mainClass.set("at.mocode.entries.service.EntriesServiceApplicationKt") +} + +dependencies { + implementation(platform(projects.platform.platformBom)) + implementation(projects.platform.platformDependencies) + implementation(projects.backend.infrastructure.monitoring.monitoringClient) + + implementation(libs.bundles.spring.boot.service.complete) + implementation(libs.postgresql.driver) + implementation(libs.spring.boot.starter.web) + implementation(libs.bundles.jackson.kotlin) + implementation(libs.kotlin.reflect) + implementation(libs.spring.cloud.starter.consul.discovery) + implementation(libs.caffeine) + implementation(libs.spring.web) + implementation(libs.bundles.resilience) + implementation(libs.springdoc.openapi.starter.webmvc.ui) + + testImplementation(projects.platform.platformTesting) + testImplementation(libs.bundles.testing.jvm) + testImplementation(libs.spring.boot.starter.test) + testImplementation(libs.spring.boot.starter.web) +} diff --git a/backend/services/entries/entries-service/src/main/kotlin/at/mocode/entries/service/EntriesServiceApplication.kt b/backend/services/entries/entries-service/src/main/kotlin/at/mocode/entries/service/EntriesServiceApplication.kt new file mode 100644 index 00000000..b075e20c --- /dev/null +++ b/backend/services/entries/entries-service/src/main/kotlin/at/mocode/entries/service/EntriesServiceApplication.kt @@ -0,0 +1,27 @@ +package at.mocode.entries.service + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RestController +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity + +@SpringBootApplication +class EntriesServiceApplication + +fun main(args: Array) { + runApplication(*args) +} + +@RestController +class EntriesController { + @GetMapping("/") + fun health(): String = "Entries Service is running" + + @PostMapping("/entries/conflict-demo") + fun conflictDemo(): ResponseEntity { + return ResponseEntity.status(HttpStatus.CONFLICT).body("Conflict detected (Demo)") + } +} diff --git a/backend/services/results/results-service/Dockerfile b/backend/services/results/results-service/Dockerfile new file mode 100644 index 00000000..f9f5c9d4 --- /dev/null +++ b/backend/services/results/results-service/Dockerfile @@ -0,0 +1,122 @@ +# syntax=docker/dockerfile:1.8 +ARG GRADLE_VERSION +ARG JAVA_VERSION +ARG BUILD_DATE +ARG VERSION + +FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder + +LABEL stage=builder \ + service=results-service \ + maintainer="Meldestelle Development Team" \ + version="${VERSION}" \ + build.date="${BUILD_DATE}" + +WORKDIR /workspace + +ENV GRADLE_OPTS="-Dorg.gradle.caching=true \ + -Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.workers.max=2 \ + -Dorg.gradle.jvmargs=-Xmx2g \ + -XX:+UseParallelGC \ + -XX:MaxMetaspaceSize=512m" + +ENV GRADLE_USER_HOME=/home/gradle/.gradle + +COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./ +COPY gradle/ gradle/ +RUN chmod +x gradlew +COPY platform/ platform/ +COPY frontend/ frontend/ +COPY core/ core/ +COPY backend/ backend/ +COPY docs/ docs/ +COPY build.gradle.kts ./ + +# Copy results modules +COPY backend/services/results/results-service/ backend/services/results/results-service/ + +RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ + --mount=type=cache,target=/home/gradle/.gradle/wrapper \ + ./gradlew :backend:services:results:results-service:dependencies --no-daemon --info + +RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ + --mount=type=cache,target=/home/gradle/.gradle/wrapper \ + ./gradlew :backend:services:results:results-service:bootJar --no-daemon --info + +FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime + +ARG BUILD_DATE +ARG VERSION +ARG JAVA_VERSION + +ENV JAVA_VERSION=${JAVA_VERSION} \ + VERSION=${VERSION} \ + BUILD_DATE=${BUILD_DATE} + +LABEL service="results-service" \ + version="${VERSION}" \ + description="Microservice for Results Management" \ + maintainer="Meldestelle Development Team" \ + java.version="${JAVA_VERSION}" \ + build.date="${BUILD_DATE}" + +ARG APP_USER=appuser +ARG APP_GROUP=appgroup +ARG APP_UID=1001 +ARG APP_GID=1001 + +WORKDIR /app + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache curl tzdata tini && \ + rm -rf /var/cache/apk/* && \ + addgroup -g ${APP_GID} -S ${APP_GROUP} && \ + adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh && \ + mkdir -p /app/logs /app/tmp /app/config && \ + chown -R ${APP_USER}:${APP_GROUP} /app && \ + chmod -R 750 /app + +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} \ + /workspace/backend/services/results/results-service/build/libs/*.jar app.jar + +USER ${APP_USER} + +EXPOSE 8084 5005 + +HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -fsS --max-time 2 http://localhost:8084/actuator/health/readiness || exit 1 + +ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \ + -XX:+UseG1GC \ + -XX:+UseStringDeduplication \ + -XX:+UseContainerSupport \ + -XX:G1HeapRegionSize=16m \ + -XX:G1ReservePercent=25 \ + -XX:InitiatingHeapOccupancyPercent=30 \ + -XX:+AlwaysPreTouch \ + -XX:+DisableExplicitGC \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Duser.timezone=Europe/Vienna \ + -Dspring.backgroundpreinitializer.ignore=true \ + -Dmanagement.endpoints.web.exposure.include=health,info,metrics,prometheus \ + -Dmanagement.endpoint.health.show-details=always \ + -Dmanagement.prometheus.metrics.export.enabled=true" + +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS +ENV SERVER_PORT=8084 +ENV LOGGING_LEVEL_ROOT=INFO + +ENTRYPOINT ["tini", "--", "sh", "-c", "\ + echo 'Starting Results Service with Java ${JAVA_VERSION}...'; \ + echo 'Service port: ${SERVER_PORT}'; \ + if [ \"${DEBUG:-false}\" = \"true\" ]; then \ + echo 'DEBUG mode enabled'; \ + exec java ${JAVA_OPTS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar; \ + else \ + exec java ${JAVA_OPTS} -jar app.jar; \ + fi"] diff --git a/backend/services/results/results-service/build.gradle.kts b/backend/services/results/results-service/build.gradle.kts new file mode 100644 index 00000000..00a3d365 --- /dev/null +++ b/backend/services/results/results-service/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSpring) + alias(libs.plugins.kotlinJpa) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependencyManagement) +} + +springBoot { + mainClass.set("at.mocode.results.service.ResultsServiceApplicationKt") +} + +dependencies { + implementation(platform(projects.platform.platformBom)) + implementation(projects.platform.platformDependencies) + implementation(projects.backend.infrastructure.monitoring.monitoringClient) + + implementation(libs.bundles.spring.boot.service.complete) + implementation(libs.postgresql.driver) + implementation(libs.spring.boot.starter.web) + implementation(libs.bundles.jackson.kotlin) + implementation(libs.kotlin.reflect) + implementation(libs.spring.cloud.starter.consul.discovery) + implementation(libs.caffeine) + implementation(libs.spring.web) + implementation(libs.bundles.resilience) + implementation(libs.springdoc.openapi.starter.webmvc.ui) + + testImplementation(projects.platform.platformTesting) + testImplementation(libs.bundles.testing.jvm) + testImplementation(libs.spring.boot.starter.test) + testImplementation(libs.spring.boot.starter.web) +} diff --git a/backend/services/results/results-service/src/main/kotlin/at/mocode/results/service/ResultsServiceApplication.kt b/backend/services/results/results-service/src/main/kotlin/at/mocode/results/service/ResultsServiceApplication.kt new file mode 100644 index 00000000..cf09ae6e --- /dev/null +++ b/backend/services/results/results-service/src/main/kotlin/at/mocode/results/service/ResultsServiceApplication.kt @@ -0,0 +1,19 @@ +package at.mocode.results.service + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@SpringBootApplication +class ResultsServiceApplication + +fun main(args: Array) { + runApplication(*args) +} + +@RestController +class ResultsController { + @GetMapping("/") + fun health(): String = "Results Service is running" +} diff --git a/backend/services/scheduling/scheduling-service/Dockerfile b/backend/services/scheduling/scheduling-service/Dockerfile new file mode 100644 index 00000000..b102f1b2 --- /dev/null +++ b/backend/services/scheduling/scheduling-service/Dockerfile @@ -0,0 +1,122 @@ +# syntax=docker/dockerfile:1.8 +ARG GRADLE_VERSION +ARG JAVA_VERSION +ARG BUILD_DATE +ARG VERSION + +FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder + +LABEL stage=builder \ + service=scheduling-service \ + maintainer="Meldestelle Development Team" \ + version="${VERSION}" \ + build.date="${BUILD_DATE}" + +WORKDIR /workspace + +ENV GRADLE_OPTS="-Dorg.gradle.caching=true \ + -Dorg.gradle.daemon=false \ + -Dorg.gradle.parallel=true \ + -Dorg.gradle.workers.max=2 \ + -Dorg.gradle.jvmargs=-Xmx2g \ + -XX:+UseParallelGC \ + -XX:MaxMetaspaceSize=512m" + +ENV GRADLE_USER_HOME=/home/gradle/.gradle + +COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./ +COPY gradle/ gradle/ +RUN chmod +x gradlew +COPY platform/ platform/ +COPY frontend/ frontend/ +COPY core/ core/ +COPY backend/ backend/ +COPY docs/ docs/ +COPY build.gradle.kts ./ + +# Copy scheduling modules +COPY backend/services/scheduling/scheduling-service/ backend/services/scheduling/scheduling-service/ + +RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ + --mount=type=cache,target=/home/gradle/.gradle/wrapper \ + ./gradlew :backend:services:scheduling:scheduling-service:dependencies --no-daemon --info + +RUN --mount=type=cache,target=/home/gradle/.gradle/caches \ + --mount=type=cache,target=/home/gradle/.gradle/wrapper \ + ./gradlew :backend:services:scheduling:scheduling-service:bootJar --no-daemon --info + +FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime + +ARG BUILD_DATE +ARG VERSION +ARG JAVA_VERSION + +ENV JAVA_VERSION=${JAVA_VERSION} \ + VERSION=${VERSION} \ + BUILD_DATE=${BUILD_DATE} + +LABEL service="scheduling-service" \ + version="${VERSION}" \ + description="Microservice for Scheduling Management" \ + maintainer="Meldestelle Development Team" \ + java.version="${JAVA_VERSION}" \ + build.date="${BUILD_DATE}" + +ARG APP_USER=appuser +ARG APP_GROUP=appgroup +ARG APP_UID=1001 +ARG APP_GID=1001 + +WORKDIR /app + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache curl tzdata tini && \ + rm -rf /var/cache/apk/* && \ + addgroup -g ${APP_GID} -S ${APP_GROUP} && \ + adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh && \ + mkdir -p /app/logs /app/tmp /app/config && \ + chown -R ${APP_USER}:${APP_GROUP} /app && \ + chmod -R 750 /app + +COPY --from=builder --chown=${APP_USER}:${APP_GROUP} \ + /workspace/backend/services/scheduling/scheduling-service/build/libs/*.jar app.jar + +USER ${APP_USER} + +EXPOSE 8085 5005 + +HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -fsS --max-time 2 http://localhost:8085/actuator/health/readiness || exit 1 + +ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \ + -XX:+UseG1GC \ + -XX:+UseStringDeduplication \ + -XX:+UseContainerSupport \ + -XX:G1HeapRegionSize=16m \ + -XX:G1ReservePercent=25 \ + -XX:InitiatingHeapOccupancyPercent=30 \ + -XX:+AlwaysPreTouch \ + -XX:+DisableExplicitGC \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Duser.timezone=Europe/Vienna \ + -Dspring.backgroundpreinitializer.ignore=true \ + -Dmanagement.endpoints.web.exposure.include=health,info,metrics,prometheus \ + -Dmanagement.endpoint.health.show-details=always \ + -Dmanagement.prometheus.metrics.export.enabled=true" + +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS +ENV SERVER_PORT=8085 +ENV LOGGING_LEVEL_ROOT=INFO + +ENTRYPOINT ["tini", "--", "sh", "-c", "\ + echo 'Starting Scheduling Service with Java ${JAVA_VERSION}...'; \ + echo 'Service port: ${SERVER_PORT}'; \ + if [ \"${DEBUG:-false}\" = \"true\" ]; then \ + echo 'DEBUG mode enabled'; \ + exec java ${JAVA_OPTS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar; \ + else \ + exec java ${JAVA_OPTS} -jar app.jar; \ + fi"] diff --git a/backend/services/scheduling/scheduling-service/build.gradle.kts b/backend/services/scheduling/scheduling-service/build.gradle.kts new file mode 100644 index 00000000..66c82d70 --- /dev/null +++ b/backend/services/scheduling/scheduling-service/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSpring) + alias(libs.plugins.kotlinJpa) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependencyManagement) +} + +springBoot { + mainClass.set("at.mocode.scheduling.service.SchedulingServiceApplicationKt") +} + +dependencies { + implementation(platform(projects.platform.platformBom)) + implementation(projects.platform.platformDependencies) + implementation(projects.backend.infrastructure.monitoring.monitoringClient) + + implementation(libs.bundles.spring.boot.service.complete) + implementation(libs.postgresql.driver) + implementation(libs.spring.boot.starter.web) + implementation(libs.bundles.jackson.kotlin) + implementation(libs.kotlin.reflect) + implementation(libs.spring.cloud.starter.consul.discovery) + implementation(libs.caffeine) + implementation(libs.spring.web) + implementation(libs.bundles.resilience) + implementation(libs.springdoc.openapi.starter.webmvc.ui) + + testImplementation(projects.platform.platformTesting) + testImplementation(libs.bundles.testing.jvm) + testImplementation(libs.spring.boot.starter.test) + testImplementation(libs.spring.boot.starter.web) +} diff --git a/backend/services/scheduling/scheduling-service/src/main/kotlin/at/mocode/scheduling/service/SchedulingServiceApplication.kt b/backend/services/scheduling/scheduling-service/src/main/kotlin/at/mocode/scheduling/service/SchedulingServiceApplication.kt new file mode 100644 index 00000000..ccc63502 --- /dev/null +++ b/backend/services/scheduling/scheduling-service/src/main/kotlin/at/mocode/scheduling/service/SchedulingServiceApplication.kt @@ -0,0 +1,19 @@ +package at.mocode.scheduling.service + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@SpringBootApplication +class SchedulingServiceApplication + +fun main(args: Array) { + runApplication(*args) +} + +@RestController +class SchedulingController { + @GetMapping("/") + fun health(): String = "Scheduling Service is running" +}