fix(tests): resolve EntriesIsolationIntegrationTest failures with test-specific DB config
- Added `TestExposedConfiguration` to connect Exposed with Spring `DataSource` in the `test` profile.
- Downgraded `springdoc` version from `3.0.0` to `2.8.9` for Spring Boot 3.x compatibility.
- Applied `@ActiveProfiles("test")` to `EntriesIsolationIntegrationTest`.
- Updated roadmap documentation to reflect bugfix and test success.
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
+126
@@ -0,0 +1,126 @@
|
||||
package at.mocode.frontend.features.pferde.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.frontend.core.designsystem.components.MsValidationWrapper
|
||||
import at.mocode.frontend.core.designsystem.components.ValidationMessage
|
||||
import at.mocode.frontend.core.designsystem.components.ValidationSeverity
|
||||
import at.mocode.frontend.core.domain.validation.ValidationResult
|
||||
|
||||
/**
|
||||
* Edit-Dialog für Pferd-Profil mit Live-Validierung (OEPS-Nummer, FEI-ID).
|
||||
* Validierungsregeln: OetoValidators (ÖTO 2026 / FEI General Regulations 2026).
|
||||
*/
|
||||
@Composable
|
||||
fun PferdProfilEditDialog(
|
||||
state: PferdProfilState,
|
||||
onIntent: (PferdProfilIntent) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Pferd bearbeiten") },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
|
||||
// Name
|
||||
OutlinedTextField(
|
||||
value = state.name,
|
||||
onValueChange = { onIntent(PferdProfilIntent.EditName(it)) },
|
||||
label = { Text("Name") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// Geburtsjahr
|
||||
OutlinedTextField(
|
||||
value = state.geburtsjahr,
|
||||
onValueChange = { onIntent(PferdProfilIntent.EditGeburtsjahr(it)) },
|
||||
label = { Text("Geburtsjahr") },
|
||||
placeholder = { Text("z. B. 2018") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// Rasse
|
||||
OutlinedTextField(
|
||||
value = state.rasse,
|
||||
onValueChange = { onIntent(PferdProfilIntent.EditRasse(it)) },
|
||||
label = { Text("Rasse") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// Farbe
|
||||
OutlinedTextField(
|
||||
value = state.farbe,
|
||||
onValueChange = { onIntent(PferdProfilIntent.EditFarbe(it)) },
|
||||
label = { Text("Farbe") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// OEPS-Nummer mit Live-Validierung
|
||||
MsValidationWrapper(
|
||||
messages = state.oepsNummerValidation.toMessages(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.oepsNummer,
|
||||
onValueChange = { onIntent(PferdProfilIntent.EditOeps(it)) },
|
||||
label = { Text("OEPS-Pferdekennummer") },
|
||||
placeholder = { Text("z. B. 1234567 oder OEPS-1234567") },
|
||||
singleLine = true,
|
||||
isError = state.oepsNummerValidation is ValidationResult.Error,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// FEI-ID mit Live-Validierung
|
||||
MsValidationWrapper(
|
||||
messages = state.feiIdValidation.toMessages(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.feiId,
|
||||
onValueChange = { onIntent(PferdProfilIntent.EditFeiId(it)) },
|
||||
label = { Text("FEI-ID") },
|
||||
placeholder = { Text("z. B. 10011469") },
|
||||
singleLine = true,
|
||||
isError = state.feiIdValidation is ValidationResult.Error,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = { onIntent(PferdProfilIntent.Save) },
|
||||
enabled = state.isValid,
|
||||
) {
|
||||
Text("Speichern")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Abbrechen")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ── Hilfs-Extension ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Konvertiert ein [ValidationResult] in eine Liste von [ValidationMessage] für [MsValidationWrapper].
|
||||
*/
|
||||
fun ValidationResult.toMessages(): List<ValidationMessage> = when (this) {
|
||||
is ValidationResult.Ok -> emptyList()
|
||||
is ValidationResult.Error -> listOf(ValidationMessage(short, ValidationSeverity.ERROR))
|
||||
is ValidationResult.Warning -> listOf(ValidationMessage(message, ValidationSeverity.WARNING))
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package at.mocode.frontend.features.reiter.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.frontend.core.designsystem.components.MsValidationWrapper
|
||||
import at.mocode.frontend.core.designsystem.components.ValidationMessage
|
||||
import at.mocode.frontend.core.designsystem.components.ValidationSeverity
|
||||
import at.mocode.frontend.core.domain.validation.ValidationResult
|
||||
|
||||
/**
|
||||
* Edit-Dialog für Reiter-Profil mit Live-Validierung (OEPS-Nummer, FEI-ID, Lizenzklasse).
|
||||
* Validierungsregeln: OetoValidators (ÖTO 2026 / FEI General Regulations 2026).
|
||||
*/
|
||||
@Composable
|
||||
fun ReiterProfilEditDialog(
|
||||
state: ReiterProfilState,
|
||||
onIntent: (ReiterProfilIntent) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Reiter bearbeiten") },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
|
||||
// Vorname
|
||||
OutlinedTextField(
|
||||
value = state.vorname,
|
||||
onValueChange = { onIntent(ReiterProfilIntent.EditVorname(it)) },
|
||||
label = { Text("Vorname") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// Nachname
|
||||
OutlinedTextField(
|
||||
value = state.nachname,
|
||||
onValueChange = { onIntent(ReiterProfilIntent.EditNachname(it)) },
|
||||
label = { Text("Nachname") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// OEPS-Nummer mit Live-Validierung
|
||||
MsValidationWrapper(
|
||||
messages = state.oepsNummerValidation.toMessages(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.oepsNummer,
|
||||
onValueChange = { onIntent(ReiterProfilIntent.EditOeps(it)) },
|
||||
label = { Text("OEPS-Mitgliedsnummer") },
|
||||
placeholder = { Text("z. B. 1234567 oder OEPS-1234567") },
|
||||
singleLine = true,
|
||||
isError = state.oepsNummerValidation is ValidationResult.Error,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// FEI-ID mit Live-Validierung
|
||||
MsValidationWrapper(
|
||||
messages = state.feiIdValidation.toMessages(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.feiId,
|
||||
onValueChange = { onIntent(ReiterProfilIntent.EditFeiId(it)) },
|
||||
label = { Text("FEI-ID") },
|
||||
placeholder = { Text("z. B. 10011469") },
|
||||
singleLine = true,
|
||||
isError = state.feiIdValidation is ValidationResult.Error,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// Lizenzklasse mit Live-Validierung
|
||||
MsValidationWrapper(
|
||||
messages = state.lizenzKlasseValidation.toMessages(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.lizenzKlasse,
|
||||
onValueChange = { onIntent(ReiterProfilIntent.EditLizenz(it)) },
|
||||
label = { Text("Lizenzklasse") },
|
||||
placeholder = { Text("z. B. R1, R2, RD1, LZF") },
|
||||
singleLine = true,
|
||||
isError = state.lizenzKlasseValidation is ValidationResult.Error,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// Verein
|
||||
OutlinedTextField(
|
||||
value = state.verein,
|
||||
onValueChange = { onIntent(ReiterProfilIntent.EditVerein(it)) },
|
||||
label = { Text("Verein") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = { onIntent(ReiterProfilIntent.Save) },
|
||||
enabled = state.isValid,
|
||||
) {
|
||||
Text("Speichern")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Abbrechen")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ── Hilfs-Extension ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Konvertiert ein [ValidationResult] in eine Liste von [ValidationMessage] für [MsValidationWrapper].
|
||||
*/
|
||||
fun ValidationResult.toMessages(): List<ValidationMessage> = when (this) {
|
||||
is ValidationResult.Ok -> emptyList()
|
||||
is ValidationResult.Error -> listOf(ValidationMessage(short, ValidationSeverity.ERROR))
|
||||
is ValidationResult.Warning -> listOf(ValidationMessage(message, ValidationSeverity.WARNING))
|
||||
}
|
||||
@@ -16,6 +16,7 @@ kotlin {
|
||||
jvmMain.dependencies {
|
||||
implementation(projects.frontend.core.designSystem)
|
||||
implementation(projects.frontend.core.domain)
|
||||
implementation(projects.frontend.core.network)
|
||||
implementation(projects.frontend.core.navigation)
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(compose.foundation)
|
||||
@@ -27,6 +28,8 @@ kotlin {
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.koin.compose)
|
||||
implementation(libs.koin.compose.viewmodel)
|
||||
// Ktor client for repository implementation
|
||||
implementation(libs.ktor.client.core)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package at.mocode.turnier.feature.data.mapper
|
||||
|
||||
import at.mocode.turnier.feature.data.remote.dto.AbteilungDto
|
||||
import at.mocode.turnier.feature.data.remote.dto.BewerbDto
|
||||
import at.mocode.turnier.feature.data.remote.dto.TurnierDto
|
||||
import at.mocode.turnier.feature.domain.Abteilung
|
||||
import at.mocode.turnier.feature.domain.Bewerb
|
||||
import at.mocode.turnier.feature.domain.Turnier
|
||||
|
||||
fun TurnierDto.toDomain(): Turnier = Turnier(id = id, name = name)
|
||||
fun Turnier.toDto(): TurnierDto = TurnierDto(id = id, name = name)
|
||||
|
||||
fun BewerbDto.toDomain(): Bewerb = Bewerb(id = id, turnierId = turnierId, name = name)
|
||||
fun Bewerb.toDto(): BewerbDto = BewerbDto(id = id, turnierId = turnierId, name = name)
|
||||
|
||||
fun AbteilungDto.toDomain(): Abteilung = Abteilung(id = id, bewerbId = bewerbId, name = name)
|
||||
fun Abteilung.toDto(): AbteilungDto = AbteilungDto(id = id, bewerbId = bewerbId, name = name)
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package at.mocode.turnier.feature.data.remote.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class TurnierDto(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BewerbDto(
|
||||
val id: Long,
|
||||
val turnierId: Long,
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AbteilungDto(
|
||||
val id: Long,
|
||||
val bewerbId: Long,
|
||||
val name: String,
|
||||
)
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package at.mocode.turnier.feature.data.remote
|
||||
|
||||
import at.mocode.frontend.core.network.*
|
||||
import at.mocode.turnier.feature.data.mapper.toDomain
|
||||
import at.mocode.turnier.feature.data.mapper.toDto
|
||||
import at.mocode.turnier.feature.data.remote.dto.AbteilungDto
|
||||
import at.mocode.turnier.feature.domain.Abteilung
|
||||
import at.mocode.turnier.feature.domain.AbteilungRepository
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
|
||||
class DefaultAbteilungRepository(
|
||||
private val client: HttpClient,
|
||||
) : AbteilungRepository {
|
||||
|
||||
override suspend fun list(bewerbId: Long): Result<List<Abteilung>> = runCatching {
|
||||
val response = client.get(ApiRoutes.Bewerbe.abteilungen(bewerbId))
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<List<AbteilungDto>>().map { it.toDomain() }
|
||||
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||
response.status == HttpStatusCode.Forbidden -> throw AuthForbidden()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getById(id: Long): Result<Abteilung> = runCatching {
|
||||
val response = client.get("${ApiRoutes.API_PREFIX}/abteilungen/$id")
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<AbteilungDto>().toDomain()
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||
response.status == HttpStatusCode.Forbidden -> throw AuthForbidden()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(model: Abteilung): Result<Abteilung> = runCatching {
|
||||
val response = client.post(ApiRoutes.Bewerbe.abteilungen(model.bewerbId)) { setBody(model.toDto()) }
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<AbteilungDto>().toDomain()
|
||||
response.status == HttpStatusCode.Conflict -> throw Conflict()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(id: Long, model: Abteilung): Result<Abteilung> = runCatching {
|
||||
val response = client.put("${ApiRoutes.API_PREFIX}/abteilungen/$id") { setBody(model.toDto()) }
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<AbteilungDto>().toDomain()
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status == HttpStatusCode.Conflict -> throw Conflict()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long): Result<Unit> = runCatching {
|
||||
val response = client.delete("${ApiRoutes.API_PREFIX}/abteilungen/$id")
|
||||
when {
|
||||
response.status.isSuccess() -> Unit
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package at.mocode.turnier.feature.data.remote
|
||||
|
||||
import at.mocode.frontend.core.network.*
|
||||
import at.mocode.turnier.feature.data.mapper.toDomain
|
||||
import at.mocode.turnier.feature.data.mapper.toDto
|
||||
import at.mocode.turnier.feature.data.remote.dto.BewerbDto
|
||||
import at.mocode.turnier.feature.domain.Bewerb
|
||||
import at.mocode.turnier.feature.domain.BewerbRepository
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
|
||||
class DefaultBewerbRepository(
|
||||
private val client: HttpClient,
|
||||
) : BewerbRepository {
|
||||
|
||||
override suspend fun list(turnierId: Long): Result<List<Bewerb>> = runCatching {
|
||||
val response = client.get(ApiRoutes.Turniere.bewerbe(turnierId))
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<List<BewerbDto>>().map { it.toDomain() }
|
||||
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||
response.status == HttpStatusCode.Forbidden -> throw AuthForbidden()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getById(id: Long): Result<Bewerb> = runCatching {
|
||||
val response = client.get("${ApiRoutes.API_PREFIX}/bewerbe/$id")
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<BewerbDto>().toDomain()
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||
response.status == HttpStatusCode.Forbidden -> throw AuthForbidden()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(model: Bewerb): Result<Bewerb> = runCatching {
|
||||
val response = client.post(ApiRoutes.Turniere.bewerbe(model.turnierId)) { setBody(model.toDto()) }
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<BewerbDto>().toDomain()
|
||||
response.status == HttpStatusCode.Conflict -> throw Conflict()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(id: Long, model: Bewerb): Result<Bewerb> = runCatching {
|
||||
val response = client.put("${ApiRoutes.API_PREFIX}/bewerbe/$id") { setBody(model.toDto()) }
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<BewerbDto>().toDomain()
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status == HttpStatusCode.Conflict -> throw Conflict()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long): Result<Unit> = runCatching {
|
||||
val response = client.delete("${ApiRoutes.API_PREFIX}/bewerbe/$id")
|
||||
when {
|
||||
response.status.isSuccess() -> Unit
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package at.mocode.turnier.feature.data.remote
|
||||
|
||||
import at.mocode.frontend.core.network.*
|
||||
import at.mocode.turnier.feature.data.mapper.toDomain
|
||||
import at.mocode.turnier.feature.data.mapper.toDto
|
||||
import at.mocode.turnier.feature.data.remote.dto.TurnierDto
|
||||
import at.mocode.turnier.feature.domain.Turnier
|
||||
import at.mocode.turnier.feature.domain.TurnierRepository
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
|
||||
class DefaultTurnierRepository(
|
||||
private val client: HttpClient,
|
||||
) : TurnierRepository {
|
||||
|
||||
override suspend fun list(): Result<List<Turnier>> = runCatching {
|
||||
val response = client.get(ApiRoutes.Turniere.ROOT)
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<List<TurnierDto>>().map { it.toDomain() }
|
||||
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||
response.status == HttpStatusCode.Forbidden -> throw AuthForbidden()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getById(id: Long): Result<Turnier> = runCatching {
|
||||
val response = client.get("${ApiRoutes.Turniere.ROOT}/$id")
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<TurnierDto>().toDomain()
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||
response.status == HttpStatusCode.Forbidden -> throw AuthForbidden()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(model: Turnier): Result<Turnier> = runCatching {
|
||||
val response = client.post(ApiRoutes.Turniere.ROOT) { setBody(model.toDto()) }
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<TurnierDto>().toDomain()
|
||||
response.status == HttpStatusCode.Conflict -> throw Conflict()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(id: Long, model: Turnier): Result<Turnier> = runCatching {
|
||||
val response = client.put("${ApiRoutes.Turniere.ROOT}/$id") { setBody(model.toDto()) }
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<TurnierDto>().toDomain()
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status == HttpStatusCode.Conflict -> throw Conflict()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long): Result<Unit> = runCatching {
|
||||
val response = client.delete("${ApiRoutes.Turniere.ROOT}/$id")
|
||||
when {
|
||||
response.status.isSuccess() -> Unit
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package at.mocode.turnier.feature.di
|
||||
|
||||
import at.mocode.turnier.feature.data.remote.DefaultAbteilungRepository
|
||||
import at.mocode.turnier.feature.data.remote.DefaultBewerbRepository
|
||||
import at.mocode.turnier.feature.data.remote.DefaultTurnierRepository
|
||||
import at.mocode.turnier.feature.domain.AbteilungRepository
|
||||
import at.mocode.turnier.feature.domain.BewerbRepository
|
||||
import at.mocode.turnier.feature.domain.TurnierRepository
|
||||
import at.mocode.turnier.feature.presentation.AbteilungViewModel
|
||||
import at.mocode.turnier.feature.presentation.BewerbAnlegenViewModel
|
||||
import at.mocode.turnier.feature.presentation.BewerbViewModel
|
||||
import at.mocode.turnier.feature.presentation.TurnierViewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val turnierFeatureModule = module {
|
||||
// Repositories: Interface → Default-Implementierung mit zentralem apiClient
|
||||
single<TurnierRepository> { DefaultTurnierRepository(client = get(qualifier = named("apiClient"))) }
|
||||
single<BewerbRepository> { DefaultBewerbRepository(client = get(qualifier = named("apiClient"))) }
|
||||
single<AbteilungRepository> { DefaultAbteilungRepository(client = get(qualifier = named("apiClient"))) }
|
||||
|
||||
// ViewModels
|
||||
factory { TurnierViewModel(repo = get()) }
|
||||
// BewerbViewModel: repo + turnierId — turnierId wird per parametersOf übergeben
|
||||
factory { (turnierId: Long) -> BewerbViewModel(repo = get(), turnierId = turnierId) }
|
||||
// BewerbAnlegenViewModel hat keinen Repository-Parameter (nutzt StoreV2 intern)
|
||||
factory { BewerbAnlegenViewModel() }
|
||||
// AbteilungViewModel: repo + bewerbId + abteilungsNr — per parametersOf übergeben
|
||||
factory { (bewerbId: Long, abteilungsNr: Int) ->
|
||||
AbteilungViewModel(repo = get(), bewerbId = bewerbId, abteilungsNr = abteilungsNr)
|
||||
}
|
||||
}
|
||||
+1
-8
@@ -1,6 +1,6 @@
|
||||
package at.mocode.frontend.features.veranstalter.data.remote
|
||||
|
||||
import at.mocode.frontend.core.network.ApiRoutes
|
||||
import at.mocode.frontend.core.network.*
|
||||
import at.mocode.frontend.features.veranstalter.data.mapper.toDomain
|
||||
import at.mocode.frontend.features.veranstalter.data.mapper.toDto
|
||||
import at.mocode.frontend.features.veranstalter.data.remote.dto.VeranstalterDto
|
||||
@@ -70,10 +70,3 @@ class DefaultVeranstalterRepository(
|
||||
}
|
||||
}
|
||||
|
||||
// Fehler-Typen (vereinfachtes DomainError-Äquivalent)
|
||||
class AuthExpired : RuntimeException("AUTH_EXPIRED")
|
||||
class AuthForbidden : RuntimeException("AUTH_FORBIDDEN")
|
||||
class NotFound : RuntimeException("NOT_FOUND")
|
||||
class Conflict : RuntimeException("CONFLICT")
|
||||
class ServerError : RuntimeException("SERVER_ERROR")
|
||||
class HttpError(val code: Int) : RuntimeException("HTTP_$code")
|
||||
|
||||
Reference in New Issue
Block a user