chore(ci): Align GH Workflows with Docker SSoT, new paths; minimal SSoT guard; staticAnalysis (#23)

* chore(MP-21): snapshot pre-refactor state (Epic 1)

* chore(MP-22): scaffold new repo structure, relocate Docker Compose, move frontend/backend modules, update Makefile; add docs mapping and env template

* MP-22 Epic 2: Erfolgreich umgesetzt und verifiziert

* MP-23 Epic 3: Gradle/Build Governance zentralisieren

* MP-23 Epic 3: Gradle/Build Governance zentralisieren

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard

- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)

Refs: MP-22, MP-23

* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard

- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)

Refs: MP-22, MP-23

* fix(ci): create .env from example before validating compose config

* fix(ci): update ssot-guard filename (.yaml) and sync workflow state

* fixing

* fix(webpack): correct sql.js fallback configuration for webpack 5
This commit is contained in:
StefanMo
2025-12-03 12:03:40 +01:00
committed by GitHub
parent 034892e890
commit 95fe3e0573
365 changed files with 2283 additions and 15142 deletions
@@ -0,0 +1,9 @@
plugins {
kotlin("jvm")
}
dependencies {
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
testImplementation(projects.platform.platformTesting)
}
@@ -0,0 +1,172 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.horses.domain.model
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.serialization.KotlinInstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlin.uuid.Uuid
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.todayIn
import kotlinx.serialization.Serializable
/**
* Domain model representing a horse in the registry system.
*
* This entity contains all essential information about a horse including
* identification, ownership, breeding information, and administrative data.
* It serves as the core aggregate root for the horse-registry bounded context.
*
* @property pferdId Unique internal identifier for this horse (UUID).
* @property pferdeName Name of the horse.
* @property geschlecht Gender of the horse (Hengst, Stute, Wallach).
* @property geburtsdatum Birth date of the horse.
* @property rasse Breed of the horse.
* @property farbe Color/coat of the horse.
* @property besitzerId ID of the current owner (Person from member-management context).
* @property verantwortlichePersonId ID of the responsible person (trainer, rider, etc.).
* @property zuechterName Name of the breeder.
* @property zuchtbuchNummer Studbook number if registered.
* @property lebensnummer Life number (unique identification number).
* @property chipNummer Microchip number for identification.
* @property passNummer Passport number.
* @property oepsNummer OEPS (Austrian Equestrian Federation) number.
* @property feiNummer FEI (International Equestrian Federation) number.
* @property vaterName Name of the sire (father).
* @property mutterName Name of the dam (mother).
* @property mutterVaterName Name of the maternal grandsire.
* @property stockmass Height of the horse in cm.
* @property istAktiv Whether the horse is currently active in the system.
* @property bemerkungen Additional notes or comments.
* @property datenQuelle Source of the data (manual entry, import, etc.).
* @property createdAt Timestamp when this record was created.
* @property updatedAt Timestamp when this record was last updated.
*/
@Serializable
data class DomPferd(
@Serializable(with = UuidSerializer::class)
val pferdId: Uuid = Uuid.random(),
// Basic Information
var pferdeName: String,
var geschlecht: PferdeGeschlechtE,
var geburtsdatum: LocalDate? = null,
var rasse: String? = null,
var farbe: String? = null,
// Ownership and Responsibility
@Serializable(with = UuidSerializer::class)
var besitzerId: Uuid? = null,
@Serializable(with = UuidSerializer::class)
var verantwortlichePersonId: Uuid? = null,
// Breeding Information
var zuechterName: String? = null,
var zuchtbuchNummer: String? = null,
// Identification Numbers
var lebensnummer: String? = null,
var chipNummer: String? = null,
var passNummer: String? = null,
var oepsNummer: String? = null,
var feiNummer: String? = null,
// Pedigree Information
var vaterName: String? = null,
var mutterName: String? = null,
var mutterVaterName: String? = null,
// Physical Characteristics
var stockmass: Int? = null, // Height in cm
// Status and Administrative
var istAktiv: Boolean = true,
var bemerkungen: String? = null,
var datenQuelle: DatenQuelleE = DatenQuelleE.MANUELL,
// Audit Fields
@Serializable(with = KotlinInstantSerializer::class)
val createdAt: Instant = Clock.System.now(),
@Serializable(with = KotlinInstantSerializer::class)
var updatedAt: Instant = Clock.System.now()
) {
/**
* Returns the display name for the horse, combining name and birth year if available.
*/
fun getDisplayName(): String {
return geburtsdatum?.let { birthDate ->
"$pferdeName (${birthDate.year})"
} ?: pferdeName
}
/**
* Checks if the horse has complete identification information.
*/
fun hasCompleteIdentification(): Boolean {
return !lebensnummer.isNullOrBlank() ||
!chipNummer.isNullOrBlank() ||
!passNummer.isNullOrBlank()
}
/**
* Checks if the horse is registered with OEPS.
*/
fun isOepsRegistered(): Boolean {
return !oepsNummer.isNullOrBlank()
}
/**
* Checks if the horse is registered with FEI.
*/
fun isFeiRegistered(): Boolean {
return !feiNummer.isNullOrBlank()
}
/**
* Returns the age of the horse in years, or null if birth date is unknown.
*/
fun getAge(): Int? {
return geburtsdatum?.let { birthDate ->
val today = kotlinx.datetime.Clock.System.todayIn(kotlinx.datetime.TimeZone.currentSystemDefault())
var age = today.year - birthDate.year
// Check if birthday has occurred this year
if (today.monthNumber < birthDate.monthNumber ||
(today.monthNumber == birthDate.monthNumber && today.dayOfMonth < birthDate.dayOfMonth)) {
age--
}
age
}
}
/**
* Validates that required fields are present for horse registration.
*/
fun validateForRegistration(): List<String> {
val errors = mutableListOf<String>()
if (pferdeName.isBlank()) {
errors.add("Horse name is required")
}
if (!hasCompleteIdentification()) {
errors.add("At least one identification number (life number, chip number, or passport number) is required")
}
if (besitzerId == null) {
errors.add("Owner is required")
}
return errors
}
/**
* Creates a copy of this horse with updated timestamp.
*/
fun withUpdatedTimestamp(): DomPferd {
return this.copy(updatedAt = Clock.System.now())
}
}
@@ -0,0 +1,243 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.horses.domain.repository
import at.mocode.horses.domain.model.DomPferd
import at.mocode.core.domain.model.PferdeGeschlechtE
import kotlin.uuid.Uuid
/**
* Repository interface for DomPferd (Horse) domain operations.
*
* This interface defines the contract for horse data access operations
* without depending on specific implementation details (database, etc.).
* Following the hexagonal architecture pattern, this interface belongs
* to the domain layer and will be implemented in the infrastructure layer.
*/
interface HorseRepository {
/**
* Finds a horse by its unique ID.
*
* @param id The unique identifier of the horse
* @return The horse if found, null otherwise
*/
suspend fun findById(id: Uuid): DomPferd?
/**
* Finds a horse by its life number (Lebensnummer).
*
* @param lebensnummer The life number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByLebensnummer(lebensnummer: String): DomPferd?
/**
* Finds a horse by its chip number.
*
* @param chipNummer The chip number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByChipNummer(chipNummer: String): DomPferd?
/**
* Finds a horse by its passport number.
*
* @param passNummer The passport number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByPassNummer(passNummer: String): DomPferd?
/**
* Finds a horse by its OEPS number.
*
* @param oepsNummer The OEPS number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByOepsNummer(oepsNummer: String): DomPferd?
/**
* Finds a horse by its FEI number.
*
* @param feiNummer The FEI number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByFeiNummer(feiNummer: String): DomPferd?
/**
* Finds horses by name (partial match).
*
* @param searchTerm The search term to match against horse names
* @param limit Maximum number of results to return
* @return List of matching horses
*/
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomPferd>
/**
* Finds all horses owned by a specific person.
*
* @param ownerId The ID of the owner (from member-management context)
* @param activeOnly Whether to return only active horses
* @return List of horses owned by the person
*/
suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean = true): List<DomPferd>
/**
* Finds all horses for which a person is responsible.
*
* @param responsiblePersonId The ID of the responsible person
* @param activeOnly Whether to return only active horses
* @return List of horses for which the person is responsible
*/
suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean = true): List<DomPferd>
/**
* Finds horses by gender.
*
* @param geschlecht The gender to filter by
* @param activeOnly Whether to return only active horses
* @param limit Maximum number of results to return
* @return List of horses with the specified gender
*/
suspend fun findByGeschlecht(geschlecht: PferdeGeschlechtE, activeOnly: Boolean = true, limit: Int = 100): List<DomPferd>
/**
* Finds horses by breed.
*
* @param rasse The breed to filter by
* @param activeOnly Whether to return only active horses
* @param limit Maximum number of results to return
* @return List of horses of the specified breed
*/
suspend fun findByRasse(rasse: String, activeOnly: Boolean = true, limit: Int = 100): List<DomPferd>
/**
* Finds horses by birth year.
*
* @param birthYear The birth year to filter by
* @param activeOnly Whether to return only active horses
* @return List of horses born in the specified year
*/
suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean = true): List<DomPferd>
/**
* Finds horses by birth year range.
*
* @param fromYear The start year (inclusive)
* @param toYear The end year (inclusive)
* @param activeOnly Whether to return only active horses
* @return List of horses born within the specified year range
*/
suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean = true): List<DomPferd>
/**
* Finds all active horses.
*
* @param limit Maximum number of results to return
* @return List of active horses
*/
suspend fun findAllActive(limit: Int = 1000): List<DomPferd>
/**
* Finds horses with OEPS registration.
*
* @param activeOnly Whether to return only active horses
* @return List of OEPS registered horses
*/
suspend fun findOepsRegistered(activeOnly: Boolean = true): List<DomPferd>
/**
* Finds horses with FEI registration.
*
* @param activeOnly Whether to return only active horses
* @return List of FEI registered horses
*/
suspend fun findFeiRegistered(activeOnly: Boolean = true): List<DomPferd>
/**
* Saves a horse (create or update).
*
* @param horse The horse to save
* @return The saved horse with updated timestamps
*/
suspend fun save(horse: DomPferd): DomPferd
/**
* Deletes a horse by ID.
*
* @param id The unique identifier of the horse to delete
* @return true if the horse was deleted, false if not found
*/
suspend fun delete(id: Uuid): Boolean
/**
* Checks if a horse with the given life number exists.
*
* @param lebensnummer The life number to check
* @return true if a horse with this life number exists, false otherwise
*/
suspend fun existsByLebensnummer(lebensnummer: String): Boolean
/**
* Checks if a horse with the given chip number exists.
*
* @param chipNummer The chip number to check
* @return true if a horse with this chip number exists, false otherwise
*/
suspend fun existsByChipNummer(chipNummer: String): Boolean
/**
* Checks if a horse with the given passport number exists.
*
* @param passNummer The passport number to check
* @return true if a horse with this passport number exists, false otherwise
*/
suspend fun existsByPassNummer(passNummer: String): Boolean
/**
* Checks if a horse with the given OEPS number exists.
*
* @param oepsNummer The OEPS number to check
* @return true if a horse with this OEPS number exists, false otherwise
*/
suspend fun existsByOepsNummer(oepsNummer: String): Boolean
/**
* Checks if a horse with the given FEI number exists.
*
* @param feiNummer The FEI number to check
* @return true if a horse with this FEI number exists, false otherwise
*/
suspend fun existsByFeiNummer(feiNummer: String): Boolean
/**
* Counts the total number of active horses.
*
* @return The total count of active horses
*/
suspend fun countActive(): Long
/**
* Counts horses by owner.
*
* @param ownerId The ID of the owner
* @param activeOnly Whether to count only active horses
* @return The count of horses owned by the person
*/
suspend fun countByOwnerId(ownerId: Uuid, activeOnly: Boolean = true): Long
/**
* Counts horses with OEPS registration.
*
* @param activeOnly Whether to count only active horses
* @return The count of OEPS registered horses
*/
suspend fun countOepsRegistered(activeOnly: Boolean = true): Long
/**
* Counts horses with FEI registration.
*
* @param activeOnly Whether to count only active horses
* @return The count of FEI registered horses
*/
suspend fun countFeiRegistered(activeOnly: Boolean = true): Long
}