Add microservices for masterdata, events, and ZNS import; configure API gateway routes; implement real Turnier and Verein repository integrations; and update infrastructure, frontend, and documentation.
This commit is contained in:
+9
-4
@@ -21,9 +21,14 @@ object ApiRoutes {
|
||||
}
|
||||
|
||||
object Masterdata {
|
||||
const val REITER = "/api/masterdata/reiter"
|
||||
const val PFERDE = "/api/masterdata/horse"
|
||||
const val FUNKTIONAERE = "/api/masterdata/funktionaer"
|
||||
const val VEREINE = "/api/masterdata/verein"
|
||||
const val ROOT = "/api/v1/masterdata"
|
||||
const val REITER = "$ROOT/reiter"
|
||||
const val PFERDE = "$ROOT/horse"
|
||||
const val FUNKTIONAERE = "$ROOT/funktionaer"
|
||||
const val VEREINE = "$ROOT/verein"
|
||||
}
|
||||
|
||||
object Events {
|
||||
const val ROOT = "/api/v1/events"
|
||||
}
|
||||
}
|
||||
|
||||
+21
-13
@@ -1,5 +1,7 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import at.mocode.turnier.feature.domain.Turnier
|
||||
import at.mocode.turnier.feature.domain.TurnierRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -34,11 +36,6 @@ sealed interface TurnierIntent {
|
||||
data object ClearError : TurnierIntent
|
||||
}
|
||||
|
||||
interface TurnierRepository {
|
||||
suspend fun list(): List<TurnierListItem>
|
||||
// Platzhalter für B-2: suspend fun get(id: Long): TurnierDetail
|
||||
}
|
||||
|
||||
class TurnierViewModel(
|
||||
private val repo: TurnierRepository,
|
||||
) {
|
||||
@@ -64,18 +61,29 @@ class TurnierViewModel(
|
||||
private fun load() {
|
||||
reduce { it.copy(isLoading = true, errorMessage = null) }
|
||||
scope.launch {
|
||||
try {
|
||||
val items = repo.list()
|
||||
reduce { cur ->
|
||||
val filtered = filterList(items, cur.searchQuery)
|
||||
cur.copy(isLoading = false, list = items, filtered = filtered)
|
||||
repo.list()
|
||||
.onSuccess { list ->
|
||||
val items = list.map { it.toListItem() }
|
||||
reduce { cur ->
|
||||
val filtered = filterList(items, cur.searchQuery)
|
||||
cur.copy(isLoading = false, list = items, filtered = filtered)
|
||||
}
|
||||
}
|
||||
.onFailure { t ->
|
||||
reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Fehler beim Laden") }
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Unbekannter Fehler beim Laden") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Turnier.toListItem() = TurnierListItem(
|
||||
id = id,
|
||||
name = name,
|
||||
ort = "Stadl-Paura", // Platzhalter bis API erweitert
|
||||
startDatum = "2026-05-01",
|
||||
endDatum = "2026-05-03",
|
||||
status = "AKTIV"
|
||||
)
|
||||
|
||||
private fun filter() {
|
||||
val cur = _state.value
|
||||
val filtered = filterList(cur.list, cur.searchQuery)
|
||||
|
||||
@@ -17,6 +17,7 @@ kotlin {
|
||||
implementation(projects.frontend.core.designSystem)
|
||||
implementation(projects.frontend.core.domain)
|
||||
implementation(projects.frontend.core.navigation)
|
||||
implementation(projects.frontend.core.network)
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.runtime)
|
||||
@@ -24,6 +25,7 @@ kotlin {
|
||||
implementation(compose.ui)
|
||||
implementation(compose.materialIconsExtended)
|
||||
implementation(libs.bundles.kmp.common)
|
||||
implementation(libs.bundles.ktor.client.common)
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.koin.compose)
|
||||
implementation(libs.koin.compose.viewmodel)
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package at.mocode.frontend.features.verein.data
|
||||
|
||||
import at.mocode.frontend.core.network.ApiRoutes
|
||||
import at.mocode.frontend.features.verein.domain.Verein
|
||||
import at.mocode.frontend.features.verein.domain.VereinRepository
|
||||
import at.mocode.frontend.features.verein.domain.VereinStatus
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
private data class VereinDto(
|
||||
val vereinId: String,
|
||||
val vereinsNummer: String,
|
||||
val name: String,
|
||||
val ort: String? = null,
|
||||
val plz: String? = null,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class VereinCreateRequest(
|
||||
val vereinsNummer: String,
|
||||
val name: String,
|
||||
val ort: String? = null,
|
||||
val plz: String? = null,
|
||||
val istVeranstalter: Boolean = true,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
|
||||
class KtorVereinRepository(
|
||||
private val client: HttpClient
|
||||
) : VereinRepository {
|
||||
|
||||
override suspend fun getVereine(): Result<List<Verein>> = runCatching {
|
||||
val response = client.get(ApiRoutes.Masterdata.VEREINE)
|
||||
if (response.status.isSuccess()) {
|
||||
response.body<List<VereinDto>>().map { it.toDomain() }
|
||||
} else emptyList()
|
||||
}
|
||||
|
||||
override suspend fun saveVerein(verein: Verein): Result<Verein> = runCatching {
|
||||
if (verein.id.isBlank() || verein.id.startsWith("new_")) {
|
||||
val request = VereinCreateRequest(
|
||||
vereinsNummer = verein.oepsNr ?: "",
|
||||
name = verein.name,
|
||||
ort = verein.ort,
|
||||
plz = verein.plz
|
||||
)
|
||||
val response = client.post(ApiRoutes.Masterdata.VEREINE) {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
if (response.status.isSuccess()) {
|
||||
response.body<VereinDto>().toDomain()
|
||||
} else throw Exception("Fehler beim Erstellen des Vereins: ${response.status}")
|
||||
} else {
|
||||
val response = client.put("${ApiRoutes.Masterdata.VEREINE}/${verein.id}") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(VereinCreateRequest(
|
||||
vereinsNummer = verein.oepsNr ?: "",
|
||||
name = verein.name,
|
||||
ort = verein.ort,
|
||||
plz = verein.plz
|
||||
))
|
||||
}
|
||||
if (response.status.isSuccess()) {
|
||||
verein
|
||||
} else throw Exception("Fehler beim Aktualisieren des Vereins: ${response.status}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun VereinDto.toDomain() = Verein(
|
||||
id = vereinId,
|
||||
name = name,
|
||||
oepsNr = vereinsNummer,
|
||||
ort = ort,
|
||||
plz = plz,
|
||||
status = if (istAktiv) VereinStatus.AKTIV else VereinStatus.RUHEND
|
||||
)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package at.mocode.frontend.features.verein.domain
|
||||
|
||||
interface VereinRepository {
|
||||
suspend fun getVereine(): Result<List<Verein>>
|
||||
suspend fun saveVerein(verein: Verein): Result<Verein>
|
||||
}
|
||||
+47
-14
@@ -4,8 +4,11 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.mocode.frontend.features.verein.domain.Verein
|
||||
import at.mocode.frontend.features.verein.domain.VereinRepository
|
||||
import at.mocode.frontend.features.verein.domain.VereinStatus
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* UI-State für die Vereins-Verwaltung.
|
||||
@@ -28,7 +31,10 @@ data class VereinUiState(
|
||||
/**
|
||||
* ViewModel für die Vereins-Verwaltung.
|
||||
*/
|
||||
open class VereinViewModel(initialLoad: Boolean = true) : ViewModel() {
|
||||
open class VereinViewModel(
|
||||
private val repository: VereinRepository,
|
||||
initialLoad: Boolean = true
|
||||
) : ViewModel() {
|
||||
var uiState by mutableStateOf(VereinUiState())
|
||||
protected set
|
||||
|
||||
@@ -38,17 +44,23 @@ open class VereinViewModel(initialLoad: Boolean = true) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVereine() {
|
||||
val mockData = listOf(
|
||||
Verein("1", "URV Neumarkt", "Union Reit- und Fahrverein Neumarkt", "4-201", "Neumarkt", "4212"),
|
||||
Verein("2", "RV Linz", "Reitverein Linz-Ebelsberg", "4-001", "Linz", "4030"),
|
||||
Verein("3", "RC Stadl-Paura", "Reitclub Pferdewelt Stadl-Paura", "4-100", "Stadl-Paura", "4650"),
|
||||
Verein("4", "Union Reitverein X", null, "1-123", "Wien", "1010", status = VereinStatus.RUHEND)
|
||||
)
|
||||
uiState = uiState.copy(
|
||||
allVereine = mockData,
|
||||
searchResults = mockData
|
||||
)
|
||||
fun loadVereine() {
|
||||
uiState = uiState.copy(isLoading = true)
|
||||
viewModelScope.launch {
|
||||
repository.getVereine()
|
||||
.onSuccess { vereine ->
|
||||
uiState = uiState.copy(
|
||||
allVereine = vereine,
|
||||
searchResults = vereine,
|
||||
isLoading = false
|
||||
)
|
||||
filterResults()
|
||||
}
|
||||
.onFailure {
|
||||
uiState = uiState.copy(isLoading = false)
|
||||
// Error handling could be added here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onSearchQueryChange(query: String) {
|
||||
@@ -108,8 +120,29 @@ open class VereinViewModel(initialLoad: Boolean = true) : ViewModel() {
|
||||
}
|
||||
|
||||
fun onSave() {
|
||||
// Mock-Speichern
|
||||
uiState = uiState.copy(isEditing = false)
|
||||
uiState = uiState.copy(isLoading = true)
|
||||
val verein = (uiState.selectedVerein ?: Verein(
|
||||
id = "",
|
||||
name = uiState.editName
|
||||
)).copy(
|
||||
name = uiState.editName,
|
||||
langname = uiState.editLangname,
|
||||
oepsNr = uiState.editOepsNr,
|
||||
ort = uiState.editOrt,
|
||||
plz = uiState.editPlz,
|
||||
status = uiState.editStatus
|
||||
)
|
||||
|
||||
viewModelScope.launch {
|
||||
repository.saveVerein(verein)
|
||||
.onSuccess {
|
||||
uiState = uiState.copy(isEditing = false, isLoading = false)
|
||||
loadVereine()
|
||||
}
|
||||
.onFailure {
|
||||
uiState = uiState.copy(isLoading = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onCancel() {
|
||||
|
||||
+5
@@ -1,9 +1,14 @@
|
||||
package at.mocode.frontend.features.verein.di
|
||||
|
||||
import at.mocode.frontend.features.verein.data.KtorVereinRepository
|
||||
import at.mocode.frontend.features.verein.domain.VereinRepository
|
||||
import at.mocode.frontend.features.verein.presentation.VereinViewModel
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.core.module.dsl.viewModelOf
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
val vereinFeatureModule = module {
|
||||
single<VereinRepository> { KtorVereinRepository(get()) }
|
||||
viewModelOf(::VereinViewModel)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user