feat: ZNS-Cloud-Sync und manuellen Veranstalter-Button im Wizard hinzugefügt
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 59s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m6s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m10s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 1m13s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m51s

Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-17 00:31:35 +02:00
parent a1194adeac
commit 4b6a242372
7 changed files with 346 additions and 4 deletions
@@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
import at.mocode.frontend.core.auth.data.AuthTokenManager
import at.mocode.frontend.core.domain.zns.ZnsImportProvider
import at.mocode.frontend.core.domain.zns.ZnsImportState
import at.mocode.frontend.core.domain.zns.ZnsRemoteVerein
import at.mocode.frontend.core.network.NetworkConfig
import io.ktor.client.*
import io.ktor.client.request.*
@@ -22,7 +23,6 @@ import kotlinx.serialization.json.Json
import java.io.File
import kotlin.time.Duration.Companion.milliseconds
@Serializable
data class ImportStartResponse(val jobId: String)
@@ -35,6 +35,15 @@ internal data class JobStatusResponse(
val fehler: List<String> = emptyList(),
)
@Serializable
internal data class VereinRemoteDto(
val vereinId: String,
val vereinsNummer: String,
val name: String,
val ort: String? = null,
val bundesland: String? = null,
)
private val TERMINAL_STATES = setOf("ABGESCHLOSSEN", "FEHLER")
private const val POLLING_INTERVAL_MS = 2000L
private const val MAX_VISIBLE_ERRORS = 50
@@ -98,6 +107,74 @@ class ZnsImportViewModel(
}
}
override fun searchRemote(query: String) {
if (query.length < 3) {
state = state.copy(remoteResults = emptyList())
return
}
viewModelScope.launch {
state = state.copy(isSearching = true)
try {
val token = authTokenManager.authState.value.token
// Wir nutzen den API-Gateway Pfad für masterdata
val response: HttpResponse = httpClient.get("${NetworkConfig.baseUrl}/api/v1/masterdata/verein/search") {
parameter("q", query)
if (token != null) header(HttpHeaders.Authorization, "Bearer $token")
}
if (response.status.isSuccess()) {
val results = json.decodeFromString<List<VereinRemoteDto>>(response.bodyAsText())
state = state.copy(
isSearching = false,
remoteResults = results.map {
ZnsRemoteVerein(it.vereinId, it.name, it.vereinsNummer, it.ort, it.bundesland)
}
)
} else {
state = state.copy(isSearching = false, errorMessage = "Suche fehlgeschlagen: HTTP ${response.status.value}")
}
} catch (e: Exception) {
state = state.copy(isSearching = false, errorMessage = "Fehler bei der Cloud-Suche: ${e.message}")
}
}
}
override fun syncFromCloud(onResult: (List<ZnsRemoteVerein>) -> Unit) {
viewModelScope.launch {
state = state.copy(isSyncing = true, errorMessage = null)
try {
val token = authTokenManager.authState.value.token
// Wir laden die Top 1000 Vereine für den Sync (einfache Implementierung)
val response: HttpResponse = httpClient.get("${NetworkConfig.baseUrl}/api/v1/masterdata/verein") {
parameter("limit", 1000)
if (token != null) header(HttpHeaders.Authorization, "Bearer $token")
}
if (response.status.isSuccess()) {
val results = json.decodeFromString<List<VereinRemoteDto>>(response.bodyAsText())
val domainResults = results.map {
ZnsRemoteVerein(it.vereinId, it.name, it.vereinsNummer, it.ort, it.bundesland)
}
val now = java.time.LocalDateTime.now()
val version = now.format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
state = state.copy(
isSyncing = false,
lastSyncVersion = version,
isFinished = true
)
onResult(domainResults)
} else {
state = state.copy(isSyncing = false, errorMessage = "Sync fehlgeschlagen: HTTP ${response.status.value}")
}
} catch (e: Exception) {
state = state.copy(isSyncing = false, errorMessage = "Fehler beim Cloud-Sync: ${e.message}")
}
}
}
private fun startPolling(jobId: String) {
pollingJob?.cancel()
pollingJob = viewModelScope.launch {