feat: integrate new desktop shell and extend backend & ADRs
- Added `meldestelle-desktop` module using JVM/Compose Desktop, registered in `settings.gradle.kts`. - Integrated new screens and desktop navigation into core: `Veranstaltungen`, `TurnierDetail`, etc. - Expanded backend with `ExposedFunktionaerRepository` in `officials-infrastructure`. - Completed ADRs for bounded context mapping (`ADR-0014`) and context map (`ADR-0015`). - Updated and extended project documentation with session logs and architecture decisions. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.persons.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.LizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.persons.domain.model.DomReiter
|
||||
import at.mocode.persons.domain.repository.ReiterRepository
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.core.statements.UpdateBuilder
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.util.*
|
||||
import kotlin.time.Clock
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlin.uuid.toJavaUuid
|
||||
import kotlin.uuid.toKotlinUuid
|
||||
|
||||
/**
|
||||
* Exposed-basierte Implementierung des ReiterRepository.
|
||||
*/
|
||||
class ExposedReiterRepository : ReiterRepository {
|
||||
|
||||
override suspend fun findById(id: Uuid): DomReiter? = transaction {
|
||||
ReiterTable.selectAll().where { ReiterTable.id eq id.toJavaUuid() }
|
||||
.map { rowToReiter(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findBySatznummer(satznummer: String): DomReiter? = transaction {
|
||||
ReiterTable.selectAll().where { ReiterTable.satznummer eq satznummer }
|
||||
.map { rowToReiter(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByFeiId(feiId: String): DomReiter? = transaction {
|
||||
ReiterTable.selectAll().where { ReiterTable.feiId eq feiId }
|
||||
.map { rowToReiter(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<DomReiter> = transaction {
|
||||
val pattern = "%$searchTerm%"
|
||||
ReiterTable.selectAll().where {
|
||||
(ReiterTable.nachname like pattern) or (ReiterTable.vorname like pattern)
|
||||
}.limit(limit).map { rowToReiter(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByVereinsNummer(vereinsNummer: String, activeOnly: Boolean): List<DomReiter> = transaction {
|
||||
ReiterTable.selectAll().where {
|
||||
(ReiterTable.vereinsNummer eq vereinsNummer).let {
|
||||
if (activeOnly) it and (ReiterTable.istAktiv eq true) else it
|
||||
}
|
||||
}.map { rowToReiter(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByLizenzKlasse(lizenzKlasse: LizenzKlasseE, activeOnly: Boolean): List<DomReiter> =
|
||||
transaction {
|
||||
ReiterTable.selectAll().where {
|
||||
(ReiterTable.lizenzKlasse eq lizenzKlasse.name).let {
|
||||
if (activeOnly) it and (ReiterTable.istAktiv eq true) else it
|
||||
}
|
||||
}.map { rowToReiter(it) }
|
||||
}
|
||||
|
||||
override suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean): List<DomReiter> = transaction {
|
||||
ReiterTable.selectAll().where {
|
||||
(ReiterTable.lizenziertFuerSparten like "%${sparte.name}%").let {
|
||||
if (activeOnly) it and (ReiterTable.istAktiv eq true) else it
|
||||
}
|
||||
}.map { rowToReiter(it) }
|
||||
}
|
||||
|
||||
override suspend fun findGastreiter(activeOnly: Boolean): List<DomReiter> = transaction {
|
||||
ReiterTable.selectAll().where {
|
||||
(ReiterTable.istGastreiter eq true).let {
|
||||
if (activeOnly) it and (ReiterTable.istAktiv eq true) else it
|
||||
}
|
||||
}.map { rowToReiter(it) }
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(limit: Int, offset: Int): List<DomReiter> = transaction {
|
||||
ReiterTable.selectAll().where { ReiterTable.istAktiv eq true }
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map { rowToReiter(it) }
|
||||
}
|
||||
|
||||
override suspend fun findAll(limit: Int, offset: Int): List<DomReiter> = transaction {
|
||||
ReiterTable.selectAll()
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map { rowToReiter(it) }
|
||||
}
|
||||
|
||||
override suspend fun save(reiter: DomReiter): DomReiter = transaction {
|
||||
val now = Clock.System.now()
|
||||
val updated = reiter.copy(updatedAt = now)
|
||||
val javaId = reiter.reiterId.toJavaUuid()
|
||||
val existing = ReiterTable.selectAll().where { ReiterTable.id eq javaId }.singleOrNull()
|
||||
if (existing != null) {
|
||||
ReiterTable.update({ ReiterTable.id eq javaId }) { reiterToStatement(it, updated) }
|
||||
} else {
|
||||
ReiterTable.insert {
|
||||
it[id] = javaId
|
||||
reiterToStatement(it, updated)
|
||||
}
|
||||
}
|
||||
updated
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = transaction {
|
||||
ReiterTable.deleteWhere { ReiterTable.id eq id.toJavaUuid() } > 0
|
||||
}
|
||||
|
||||
override suspend fun countActive(): Long = transaction {
|
||||
ReiterTable.selectAll().where { ReiterTable.istAktiv eq true }.count()
|
||||
}
|
||||
|
||||
override suspend fun existsBySatznummer(satznummer: String): Boolean = transaction {
|
||||
ReiterTable.selectAll().where { ReiterTable.satznummer eq satznummer }.count() > 0
|
||||
}
|
||||
|
||||
private fun rowToReiter(row: ResultRow): DomReiter {
|
||||
val sparten = try {
|
||||
Json.decodeFromString<List<SparteE>>(row[ReiterTable.lizenziertFuerSparten])
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
return DomReiter(
|
||||
reiterId = (row[ReiterTable.id] as UUID).toKotlinUuid(),
|
||||
personId = (row[ReiterTable.id] as UUID).toKotlinUuid(), // same as reiterId for now
|
||||
satznummer = row[ReiterTable.satznummer] ?: "",
|
||||
feiId = row[ReiterTable.feiId],
|
||||
nation = row[ReiterTable.nation],
|
||||
vorname = row[ReiterTable.vorname],
|
||||
nachname = row[ReiterTable.nachname],
|
||||
geburtsdatum = row[ReiterTable.geburtsdatum],
|
||||
vereinsNummer = row[ReiterTable.vereinsNummer],
|
||||
vereinsName = row[ReiterTable.vereinsName],
|
||||
lizenzKlasse = runCatching { LizenzKlasseE.valueOf(row[ReiterTable.lizenzKlasse] ?: "") }.getOrDefault(
|
||||
LizenzKlasseE.LIZENZFREI
|
||||
),
|
||||
lizenzSparten = sparten,
|
||||
istGastreiter = row[ReiterTable.istGastreiter],
|
||||
istAktiv = row[ReiterTable.istAktiv],
|
||||
datenQuelle = runCatching { DatenQuelleE.valueOf(row[ReiterTable.datenQuelle]) }.getOrDefault(DatenQuelleE.IMPORT_ZNS),
|
||||
createdAt = row[ReiterTable.createdAt],
|
||||
updatedAt = row[ReiterTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
private fun reiterToStatement(stmt: UpdateBuilder<*>, r: DomReiter) {
|
||||
stmt[ReiterTable.satznummer] = r.satznummer
|
||||
stmt[ReiterTable.feiId] = r.feiId
|
||||
stmt[ReiterTable.vorname] = r.vorname
|
||||
stmt[ReiterTable.nachname] = r.nachname
|
||||
stmt[ReiterTable.geburtsdatum] = r.geburtsdatum
|
||||
stmt[ReiterTable.nation] = r.nation
|
||||
stmt[ReiterTable.vereinsNummer] = r.vereinsNummer
|
||||
stmt[ReiterTable.vereinsName] = r.vereinsName
|
||||
stmt[ReiterTable.lizenzKlasse] = r.lizenzKlasse.name
|
||||
stmt[ReiterTable.lizenziertFuerSparten] = Json.encodeToString(r.lizenzSparten)
|
||||
stmt[ReiterTable.istGastreiter] = r.istGastreiter
|
||||
stmt[ReiterTable.istAktiv] = r.istAktiv
|
||||
stmt[ReiterTable.datenQuelle] = r.datenQuelle.name
|
||||
stmt[ReiterTable.createdAt] = r.createdAt
|
||||
stmt[ReiterTable.updatedAt] = r.updatedAt
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package at.mocode.persons.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.core.java.javaUUID
|
||||
import org.jetbrains.exposed.v1.datetime.date
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für Reiter (DomReiter).
|
||||
*
|
||||
* Speichert alle Reiter-Daten inkl. Lizenz, Sparten (JSON) und ZNS-Identifikation.
|
||||
*/
|
||||
object ReiterTable : Table("reiter") {
|
||||
|
||||
val id = javaUUID("id").autoGenerate()
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
// Identifikation
|
||||
val satznummer = varchar("satznummer", 20).nullable()
|
||||
val feiId = varchar("fei_id", 20).nullable()
|
||||
|
||||
// Persönliche Daten
|
||||
val vorname = varchar("vorname", 100)
|
||||
val nachname = varchar("nachname", 100)
|
||||
val geburtsdatum = date("geburtsdatum").nullable()
|
||||
val nation = varchar("nation", 3).nullable().default("AUT")
|
||||
|
||||
// Vereinsdaten
|
||||
val vereinsNummer = varchar("vereins_nummer", 20).nullable()
|
||||
val vereinsName = varchar("vereins_name", 200).nullable()
|
||||
|
||||
// Lizenz & Qualifikation
|
||||
val lizenzKlasse = varchar("lizenz_klasse", 50).nullable()
|
||||
val lizenziertFuerSparten = text("lizenziert_fuer_sparten") // JSON array of SparteE
|
||||
|
||||
// Status & Verwaltung
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val istGastreiter = bool("ist_gastreiter").default(false)
|
||||
val bemerkungen = text("bemerkungen").nullable()
|
||||
val datenQuelle = varchar("daten_quelle", 50)
|
||||
|
||||
// Audit-Felder
|
||||
val createdAt = timestamp("created_at")
|
||||
val updatedAt = timestamp("updated_at")
|
||||
|
||||
init {
|
||||
index(false, nachname)
|
||||
index(false, vorname)
|
||||
index(true, satznummer)
|
||||
index(true, feiId)
|
||||
index(false, vereinsNummer)
|
||||
index(false, istAktiv)
|
||||
index(false, lizenzKlasse)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user