feat(horses-service): remove legacy configuration and repository classes
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 7m12s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m42s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 6m28s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m51s

- Deleted outdated `ApplicationConfiguration` and `HorseRepositoryImpl` classes.
- Migrated to streamlined modular Gradle configuration in `build.gradle.kts`.
- Updated domain models and dependencies to use multiplatform and serialization plugins.
- Added modular setup for `horses` namespace in `settings.gradle.kts`.
- Reorganized database configuration with minimal JDBC bindings for development.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-23 13:47:04 +01:00
parent 7b35831d8c
commit c53daa926a
44 changed files with 75781 additions and 530 deletions
@@ -0,0 +1,26 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
jvm()
sourceSets {
commonMain {
kotlin.srcDir("src/main/kotlin")
dependencies {
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
}
}
commonTest {
kotlin.srcDir("src/test/kotlin")
dependencies {
implementation(projects.platform.platformTesting)
}
}
}
}
@@ -0,0 +1,40 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.officials.domain.model
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.serialization.KotlinInstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.serialization.Serializable
import kotlin.time.Clock
import kotlin.time.Instant
import kotlin.uuid.Uuid
/**
* Domain model representing an official (Richter) in the registry system.
*
* @property officialId Unique internal identifier (UUID).
* @property richterNummer ÖPS official number (from ZNS RICHT01.dat).
* @property name Full name of the official.
* @property qualifikation Qualification/class code (e.g. "GA", "G3").
* @property datenQuelle Source of the data.
* @property createdAt Timestamp when this record was created.
* @property updatedAt Timestamp when this record was last updated.
*/
@Serializable
data class DomOfficial(
@Serializable(with = UuidSerializer::class)
val officialId: Uuid = Uuid.random(),
val richterNummer: String,
val name: String,
val qualifikation: String? = null,
val istAktiv: Boolean = true,
val datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
@Serializable(with = KotlinInstantSerializer::class)
val createdAt: Instant = Clock.System.now(),
@Serializable(with = KotlinInstantSerializer::class)
var updatedAt: Instant = Clock.System.now()
)
@@ -0,0 +1,23 @@
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.kotlinSpring)
alias(libs.plugins.kotlinSerialization)
}
dependencies {
implementation(projects.platform.platformDependencies)
implementation(projects.officials.officialsDomain)
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(libs.spring.boot.starter.web)
implementation(libs.exposed.core)
implementation(libs.exposed.dao)
implementation(libs.exposed.jdbc)
implementation(libs.exposed.kotlin.datetime)
runtimeOnly(libs.postgresql.driver)
testImplementation(projects.platform.platformTesting)
}
@@ -0,0 +1,15 @@
package at.mocode.officials.infrastructure.persistence
import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable
import org.jetbrains.exposed.v1.datetime.timestamp
/**
* Tabelle für importierte Richter aus dem ZNS-Datenbestand (RICHT01.dat).
* Wird ausschließlich als Dev-Seed-Daten verwendet.
*/
object ZnsOfficialTable : UUIDTable("zns_officials") {
val richterNummer = varchar("richter_nummer", 6).uniqueIndex()
val name = varchar("name", 80)
val qualifikation = varchar("qualifikation", 5).nullable()
val createdAt = timestamp("created_at")
}
@@ -0,0 +1,38 @@
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.kotlinSpring)
alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependencyManagement)
}
springBoot {
mainClass.set("at.mocode.officials.service.OfficialsServiceApplicationKt")
}
dependencies {
implementation(projects.platform.platformDependencies)
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(projects.officials.officialsDomain)
implementation(projects.officials.officialsInfrastructure)
implementation(libs.spring.boot.starter.web)
implementation(libs.spring.boot.starter.validation)
implementation(libs.spring.boot.starter.actuator)
implementation(libs.exposed.core)
implementation(libs.exposed.dao)
implementation(libs.exposed.jdbc)
implementation(libs.exposed.kotlin.datetime)
implementation(libs.hikari.cp)
runtimeOnly(libs.postgresql.driver)
testRuntimeOnly(libs.h2.driver)
testImplementation(projects.platform.platformTesting)
testImplementation(libs.spring.boot.starter.test)
testImplementation(libs.logback.classic)
}
tasks.test {
useJUnitPlatform()
}
@@ -0,0 +1,18 @@
package at.mocode.officials.service
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
@SpringBootApplication
@ComponentScan(
basePackages = [
"at.mocode.officials.service",
"at.mocode.officials.infrastructure"
]
)
class OfficialsServiceApplication
fun main(args: Array<String>) {
runApplication<OfficialsServiceApplication>(*args)
}
@@ -0,0 +1,31 @@
package at.mocode.officials.service.config
import at.mocode.officials.infrastructure.persistence.ZnsOfficialTable
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.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
@Configuration
@Profile("dev")
class OfficialsDatabaseConfiguration(
@Value("\${spring.datasource.url}") private val jdbcUrl: String,
@Value("\${spring.datasource.username}") private val username: String,
@Value("\${spring.datasource.password}") private val password: String
) {
private val log = LoggerFactory.getLogger(OfficialsDatabaseConfiguration::class.java)
@PostConstruct
fun initializeDatabase() {
log.info("Initialisiere Datenbank-Schema für Officials-Service...")
Database.connect(jdbcUrl, user = username, password = password)
transaction {
SchemaUtils.createMissingTablesAndColumns(ZnsOfficialTable)
log.info("Datenbank-Schema erfolgreich initialisiert")
}
}
}
@@ -0,0 +1,90 @@
package at.mocode.officials.service.dev
import at.mocode.officials.infrastructure.persistence.ZnsOfficialTable
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
import org.jetbrains.exposed.v1.jdbc.batchInsert
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.CommandLineRunner
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import java.io.File
import kotlin.time.Clock
/**
* Dev-only Seeder: Importiert RICHT01.dat (ZNS) → zns_officials.
* Aktivierung: Umgebungsvariable ZNS_DATA_DIR setzen + Profil "dev".
*/
@Component
@Profile("dev")
class ZnsOfficialSeeder(
@Value("\${zns.data.dir:#{null}}") private val znsDataDir: String?
) : CommandLineRunner {
private val log = LoggerFactory.getLogger(ZnsOfficialSeeder::class.java)
override fun run(vararg args: String) {
if (znsDataDir == null) {
log.info("ZNS_DATA_DIR nicht gesetzt ZnsOfficialSeeder wird übersprungen.")
return
}
val dir = File(znsDataDir)
if (!dir.exists() || !dir.isDirectory) {
log.warn("ZNS_DATA_DIR '{}' existiert nicht oder ist kein Verzeichnis.", znsDataDir)
return
}
log.info("Starte ZNS-Richter-Import aus: {}", dir.absolutePath)
transaction {
SchemaUtils.createMissingTablesAndColumns(ZnsOfficialTable)
}
seedOfficials(dir)
log.info("ZNS-Richter-Import abgeschlossen.")
}
// -------------------------------------------------------------------------
// RICHT01.dat → zns_officials
// Format (~109 Zeichen):
// [0] 'Y' (Kennzeichen)
// [1-7] RichterNr (6 Zeichen)
// [7-87] Name (80 Zeichen)
// [87-89] Qualifikation (z.B. "GA", "G3")
// -------------------------------------------------------------------------
private fun seedOfficials(dir: File) {
val file = File(dir, "RICHT01.dat")
if (!file.exists()) {
log.warn("RICHT01.dat nicht gefunden Richter werden übersprungen.")
return
}
data class OfficialRow(
val richterNr: String,
val name: String,
val qualifikation: String?
)
val rows = file.readLines(Charsets.ISO_8859_1)
.filter { it.length >= 7 && it.startsWith("Y") }
.mapNotNull { line ->
val richterNr = line.substring(1, 7).trim()
if (richterNr.isBlank()) return@mapNotNull null
val name = line.safeSubstring(7, 87).trim()
val qualifikation = line.safeSubstring(87, 92).trim().takeIf { it.isNotBlank() }
OfficialRow(richterNr, name, qualifikation)
}
val now = Clock.System.now()
transaction {
ZnsOfficialTable.batchInsert(rows, ignore = true) { row ->
this[ZnsOfficialTable.richterNummer] = row.richterNr
this[ZnsOfficialTable.name] = row.name
this[ZnsOfficialTable.qualifikation] = row.qualifikation
this[ZnsOfficialTable.createdAt] = now
}
}
log.info("Richter importiert: {} Datensätze", rows.size)
}
private fun String.safeSubstring(start: Int, end: Int): String =
if (this.length >= end) this.substring(start, end) else if (this.length > start) this.substring(start) else ""
}