chore: remove unused meldestelle-portal module
- Deleted obsolete `meldestelle-portal` module, including all associated screens, configurations, tests, and assets. - Includes removal of Compose multiplatform dependencies in `build.gradle.kts`. - Cleaned up redundant files such as `AppPreview`, `AuthStatusScreen`, `DashboardScreen`, and associated core implementations. - Streamlined module references in `settings.gradle.kts`. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Feature-Modul: ZNS-Stammdaten-Import (Desktop-only)
|
||||
* Kapselt ViewModel, State und API-Kommunikation für den ZNS-Import.
|
||||
*/
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
}
|
||||
group = "at.mocode.clients"
|
||||
version = "1.0.0"
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
sourceSets {
|
||||
jvmMain.dependencies {
|
||||
implementation(projects.frontend.core.network)
|
||||
implementation(projects.frontend.core.auth)
|
||||
implementation(libs.bundles.kmp.common)
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.contentNegotiation)
|
||||
implementation(libs.ktor.client.serialization.kotlinx.json)
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package at.mocode.zns.feature
|
||||
|
||||
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.core.auth.data.AuthTokenManager
|
||||
import at.mocode.frontend.core.network.NetworkConfig
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
data class ZnsImportState(
|
||||
val selectedFilePath: String? = null,
|
||||
val isUploading: Boolean = false,
|
||||
val jobId: String? = null,
|
||||
val jobStatus: String? = null,
|
||||
val progress: Int = 0,
|
||||
val progressDetail: String = "",
|
||||
val errors: List<String> = emptyList(),
|
||||
val errorMessage: String? = null,
|
||||
val isFinished: Boolean = false,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class JobIdResponse(val jobId: String)
|
||||
|
||||
@Serializable
|
||||
internal data class JobStatusResponse(
|
||||
val jobId: String,
|
||||
val status: String,
|
||||
val progress: Int = 0,
|
||||
val progressDetail: String = "",
|
||||
val errors: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
private val TERMINAL_STATES = setOf("ABGESCHLOSSEN", "FEHLER")
|
||||
private const val POLLING_INTERVAL_MS = 2000L
|
||||
private const val MAX_VISIBLE_ERRORS = 50
|
||||
|
||||
class ZnsImportViewModel(
|
||||
private val httpClient: HttpClient,
|
||||
private val authTokenManager: AuthTokenManager,
|
||||
) : ViewModel() {
|
||||
|
||||
var state by mutableStateOf(ZnsImportState())
|
||||
private set
|
||||
|
||||
private var pollingJob: Job? = null
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
fun onFileSelected(path: String) {
|
||||
state = ZnsImportState(selectedFilePath = path)
|
||||
}
|
||||
|
||||
fun startImport() {
|
||||
val filePath = state.selectedFilePath ?: return
|
||||
val file = File(filePath)
|
||||
if (!file.exists() || !file.name.endsWith(".zip", ignoreCase = true)) {
|
||||
state = state.copy(errorMessage = "Bitte eine gültige .zip-Datei auswählen.")
|
||||
return
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
state = state.copy(
|
||||
isUploading = true, errorMessage = null, isFinished = false,
|
||||
jobId = null, progress = 0, progressDetail = "", errors = emptyList()
|
||||
)
|
||||
try {
|
||||
val token = authTokenManager.authState.value.token
|
||||
val response: HttpResponse = httpClient.post("${NetworkConfig.baseUrl}/api/v1/import/zns") {
|
||||
if (token != null) header(HttpHeaders.Authorization, "Bearer $token")
|
||||
setBody(MultiPartFormDataContent(formData {
|
||||
append("file", file.readBytes(), Headers.build {
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
||||
append(HttpHeaders.ContentType, "application/zip")
|
||||
})
|
||||
}))
|
||||
}
|
||||
if (response.status == HttpStatusCode.Accepted) {
|
||||
val body = json.decodeFromString<JobIdResponse>(response.bodyAsText())
|
||||
state = state.copy(isUploading = false, jobId = body.jobId, jobStatus = "AUSSTEHEND")
|
||||
startPolling(body.jobId)
|
||||
} else {
|
||||
state = state.copy(isUploading = false, errorMessage = "Upload fehlgeschlagen: HTTP ${response.status.value}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
state = state.copy(isUploading = false, errorMessage = "Fehler beim Upload: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPolling(jobId: String) {
|
||||
pollingJob?.cancel()
|
||||
pollingJob = viewModelScope.launch {
|
||||
while (true) {
|
||||
try {
|
||||
val token = authTokenManager.authState.value.token
|
||||
val response: HttpResponse = httpClient.get("${NetworkConfig.baseUrl}/api/v1/import/zns/$jobId/status") {
|
||||
if (token != null) header(HttpHeaders.Authorization, "Bearer $token")
|
||||
}
|
||||
if (response.status.isSuccess()) {
|
||||
val status = json.decodeFromString<JobStatusResponse>(response.bodyAsText())
|
||||
state = state.copy(
|
||||
jobStatus = status.status,
|
||||
progress = status.progress,
|
||||
progressDetail = status.progressDetail,
|
||||
errors = status.errors.takeLast(MAX_VISIBLE_ERRORS),
|
||||
isFinished = status.status in TERMINAL_STATES,
|
||||
)
|
||||
if (status.status in TERMINAL_STATES) break
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
state = state.copy(errorMessage = "Polling-Fehler: ${e.message}", isFinished = true)
|
||||
break
|
||||
}
|
||||
delay(POLLING_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
pollingJob?.cancel()
|
||||
state = ZnsImportState()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
pollingJob?.cancel()
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package at.mocode.zns.feature.di
|
||||
|
||||
import at.mocode.zns.feature.ZnsImportViewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val znsImportModule = module {
|
||||
factory { ZnsImportViewModel(get(named("apiClient")), get()) }
|
||||
}
|
||||
Reference in New Issue
Block a user