fixing web-app

This commit is contained in:
stefan
2025-09-24 14:21:57 +02:00
parent cd2b0796a6
commit 1c4184809a
156 changed files with 440 additions and 1708 deletions
+1 -1
View File
@@ -94,7 +94,7 @@ temp-data/
**/temp/
**/.temp/
# Exception: Allow temp/ping-service for Docker builds
!temp/ping-service/
!services/ping/ping-service/
# ===================================================================
# Gradle wrapper executable (keep gradle wrapper jar)
-110
View File
@@ -1,110 +0,0 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
// alias(libs.plugins.composeHotReload)
}
// Project version configuration
version = "1.0.0"
group = "at.mocode"
kotlin {
// Configure JVM toolchain for all JVM targets
jvmToolchain(21)
jvm()
js {
browser()
binaries.executable()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
}
sourceSets {
commonMain.dependencies {
implementation(projects.client.shared)
// Core Compose Dependencies - minimiert für kleinere Bundle-Größe
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
// UiToolingPreview nur für Development, nicht für Production WASM
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
// HTTP client dependencies for ping-service - optimiert
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.serialization.kotlinx.json)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
jvmMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.ktor.client.cio)
}
}
}
compose.desktop {
application {
mainClass = "at.mocode.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Meldestelle"
packageVersion = "1.0.0"
// Application metadata
description = "Pferdesport Meldestelle System - Client Application"
copyright = "© 2025 Meldestelle Project"
vendor = "at.mocode"
// Platform-specific configurations
linux {
iconFile.set(project.file("src/commonMain/resources/icon.png"))
packageName = "meldestelle"
debMaintainer = "stefan@mocode.at"
menuGroup = "Office"
}
windows {
iconFile.set(project.file("src/commonMain/resources/icon.ico"))
menuGroup = "Meldestelle"
upgradeUuid = "61DAB35E-17CB-43B8-8A72-39876CF0E021"
}
macOS {
iconFile.set(project.file("src/commonMain/resources/icon.icns"))
bundleID = "at.mocode.meldestelle"
packageBuildVersion = "1.0.0"
packageVersion = "1.0.0"
}
}
buildTypes.release.proguard {
configurationFiles.from(project.file("compose-desktop.pro"))
}
}
}
@@ -1,52 +0,0 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="450dp"
android:height="450dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M56.25,18V46L32,60 7.75,46V18L32,4Z"
android:fillColor="#6075f2"/>
<path
android:pathData="m41.5,26.5v11L32,43V60L56.25,46V18Z"
android:fillColor="#6b57ff"/>
<path
android:pathData="m32,43 l-9.5,-5.5v-11L7.75,18V46L32,60Z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="23.131"
android:centerY="18.441"
android:gradientRadius="42.132"
android:type="radial">
<item
android:offset="0"
android:color="#FF5383EC"/>
<item
android:offset="0.867"
android:color="#FF7F52FF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M22.5,26.5 L32,21 41.5,26.5 56.25,18 32,4 7.75,18Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="44.172"
android:startY="4.377"
android:endX="17.973"
android:endY="34.035"
android:type="linear">
<item
android:offset="0"
android:color="#FF33C3FF"/>
<item
android:offset="0.878"
android:color="#FF5383EC"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m32,21 l9.526,5.5v11L32,43 22.474,37.5v-11z"
android:fillColor="#000000"/>
</vector>
@@ -1,172 +0,0 @@
package at.mocode
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import at.mocode.ui.PingViewModel
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
@Preview
fun App() {
MaterialTheme {
val viewModel: PingViewModel = viewModel()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Header
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Meldestelle - Ping Service Client",
style = MaterialTheme.typography.headlineMedium
)
Text(
text = "Trace-Bullet Implementation",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.secondary
)
}
}
// Action Buttons
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "API Tests",
style = MaterialTheme.typography.titleMedium
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.simplePing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Simple Ping")
}
Button(
onClick = { viewModel.enhancedPing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Enhanced Ping")
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.healthCheck() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Health Check")
}
Button(
onClick = { viewModel.enhancedPing(simulate = true) },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text("Test Failure")
}
}
}
}
// Loading Indicator
if (uiState.isLoading) {
CircularProgressIndicator()
}
// Error Display
uiState.error?.let { error ->
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Text(
text = error,
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onErrorContainer
)
}
}
// Results Display
uiState.lastPingResponse?.let { response ->
ResultCard("Simple Ping Result", response)
}
uiState.lastEnhancedResponse?.let { response ->
ResultCard("Enhanced Ping Result", response)
}
uiState.lastHealthResponse?.let { response ->
ResultCard("Health Check Result", response)
}
}
}
}
@Composable
private fun ResultCard(title: String, data: Any) {
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = data.toString(),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.fillMaxWidth()
)
}
}
}
@@ -1,88 +0,0 @@
package at.mocode.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.mocode.model.EnhancedPingResponse
import at.mocode.model.HealthResponse
import at.mocode.model.PingResponse
import at.mocode.service.PingService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
data class PingUiState(
val isLoading: Boolean = false,
val lastPingResponse: PingResponse? = null,
val lastEnhancedResponse: EnhancedPingResponse? = null,
val lastHealthResponse: HealthResponse? = null,
val error: String? = null
)
class PingViewModel : ViewModel() {
private val pingService = PingService()
private val _uiState = MutableStateFlow(PingUiState())
val uiState: StateFlow<PingUiState> = _uiState.asStateFlow()
fun simplePing() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true, error = null)
pingService.ping()
.onSuccess { response ->
_uiState.value = _uiState.value.copy(
isLoading = false,
lastPingResponse = response
)
}
.onFailure { exception ->
_uiState.value = _uiState.value.copy(
isLoading = false,
error = "Ping failed: ${exception.message}"
)
}
}
}
fun enhancedPing(simulate: Boolean = false) {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true, error = null)
pingService.enhancedPing(simulate)
.onSuccess { response ->
_uiState.value = _uiState.value.copy(
isLoading = false,
lastEnhancedResponse = response
)
}
.onFailure { exception ->
_uiState.value = _uiState.value.copy(
isLoading = false,
error = "Enhanced ping failed: ${exception.message}"
)
}
}
}
fun healthCheck() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true, error = null)
pingService.health()
.onSuccess { response ->
_uiState.value = _uiState.value.copy(
isLoading = false,
lastHealthResponse = response
)
}
.onFailure { exception ->
_uiState.value = _uiState.value.copy(
isLoading = false,
error = "Health check failed: ${exception.message}"
)
}
}
}
}
@@ -1,12 +0,0 @@
package at.mocode
import kotlin.test.Test
import kotlin.test.assertEquals
class ComposeAppCommonTest {
@Test
fun example() {
assertEquals(3, 1 + 2)
}
}
@@ -1,21 +0,0 @@
package at.mocode
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Meldestelle",
) {
App()
}
}
@Preview
@Composable
fun AppDesktopPreview() {
App()
}
@@ -1,11 +0,0 @@
package at.mocode
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.ComposeViewport
@OptIn(ExperimentalComposeUiApi::class)
fun main() {
ComposeViewport {
App()
}
}
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meldestelle</title>
<link type="text/css" rel="stylesheet" href="styles.css">
<script type="application/javascript" src="composeApp.js"></script>
</head>
<body>
</body>
</html>
@@ -1,7 +0,0 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
@@ -1,21 +0,0 @@
/*
* Temporary workaround for [KT-80582](https://youtrack.jetbrains.com/issue/KT-80582)
*
* This file should be safe to be removed once the ticket is closed and the project is updated to Kotlin version which solves that issue.
*/
config.watchOptions = config.watchOptions || {
ignored: ["**/*.kt", "**/node_modules"]
}
if (config.devServer) {
config.devServer.static = config.devServer.static.map(file => {
if (typeof file === "string") {
return {
directory: file,
watch: false,
}
} else {
return file
}
})
}
-47
View File
@@ -1,47 +0,0 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
// Configure JVM toolchain for all JVM targets
jvmToolchain(21)
jvm()
js {
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
sourceSets {
commonMain.dependencies {
// HTTP Client dependencies for ping-service
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.serialization.kotlinx.json)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.coroutines.core)
}
jvmMain.dependencies {
implementation(libs.ktor.client.cio)
}
jsMain.dependencies {
implementation(libs.ktor.client.js)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
@@ -1,65 +0,0 @@
package at.mocode.service
import at.mocode.model.EnhancedPingResponse
import at.mocode.model.HealthResponse
import at.mocode.model.PingResponse
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class PingService {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
install(HttpTimeout) {
requestTimeoutMillis = 10000
connectTimeoutMillis = 5000
}
}
private val baseUrl = getBaseUrl()
suspend fun ping(): Result<PingResponse> = runCatching {
client.get("$baseUrl/ping").body<PingResponse>()
}
suspend fun enhancedPing(simulate: Boolean = false): Result<EnhancedPingResponse> = runCatching {
// Fallback: Use simple ping and enhance response locally
val response = client.get("$baseUrl/ping").body<PingResponse>()
EnhancedPingResponse(
status = response.status,
timestamp = response.timestamp,
service = response.service,
circuitBreakerState = if (simulate) "OPEN" else "CLOSED",
responseTime = 100L
)
}
suspend fun health(): Result<HealthResponse> = runCatching {
// Fallback: Use simple ping to determine health
val response = client.get("$baseUrl/ping").body<PingResponse>()
HealthResponse(
status = response.status,
timestamp = response.timestamp,
service = response.service,
healthy = response.status == "pong"
)
}
suspend fun testFailure(): Result<EnhancedPingResponse> = runCatching {
// Simulate failure for testing
throw RuntimeException("Simulated failure for testing")
}
}
// Platform-specific base URL
expect fun getBaseUrl(): String
+46
View File
@@ -0,0 +1,46 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
group = "at.mocode"
version = "1.0.0"
kotlin {
jvm()
js {
browser()
}
// Keep WASM for dev since sources already present
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmJs {
browser()
}
jvmToolchain(21)
sourceSets {
val commonMain by getting {
dependencies {
implementation(projects.services.ping.pingApi)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.serialization.kotlinx.json)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
}
val commonTest by getting {
dependencies {
implementation(libs.kotlin.test)
}
}
val jvmMain by getting {
dependencies {
implementation(libs.ktor.client.cio)
}
}
}
}
@@ -0,0 +1,10 @@
package at.mocode.model
// Deprecated local DTOs are replaced by typealiases to the shared API contract.
// This preserves binary/source compatibility for existing imports while enforcing SSoT.
typealias PingResponse = at.mocode.ping.api.PingResponse
typealias EnhancedPingResponse = at.mocode.ping.api.EnhancedPingResponse
typealias HealthResponse = at.mocode.ping.api.HealthResponse
@@ -0,0 +1,25 @@
package at.mocode.ping.client
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import at.mocode.ping.api.PingApi
import at.mocode.ping.api.PingResponse
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import at.mocode.service.getBaseUrl
class PingApiClient(
private val client: HttpClient,
baseUrl: String = getBaseUrl()
) : PingApi {
private val base = "$baseUrl/api/ping"
override suspend fun simplePing(): PingResponse = client.get("$base/simple").body()
override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse =
client.get("$base/enhanced") { parameter("simulate", simulate) }.body()
override suspend fun healthCheck(): HealthResponse = client.get("$base/health").body()
}
@@ -0,0 +1,43 @@
package at.mocode.service
import at.mocode.model.EnhancedPingResponse
import at.mocode.model.HealthResponse
import at.mocode.model.PingResponse
import at.mocode.ping.client.PingApiClient
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
@Deprecated("Use PingApiClient directly for new code")
class PingService(
private val client: HttpClient = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
install(HttpTimeout) {
requestTimeoutMillis = 10000
connectTimeoutMillis = 5000
}
}
) {
private val api = PingApiClient(client)
suspend fun ping(): Result<PingResponse> = runCatching { api.simplePing() }
suspend fun enhancedPing(simulate: Boolean = false): Result<EnhancedPingResponse> =
runCatching { api.enhancedPing(simulate) }
suspend fun health(): Result<HealthResponse> = runCatching { api.healthCheck() }
suspend fun testFailure(): Result<EnhancedPingResponse> = runCatching {
throw RuntimeException("Simulated failure for testing")
}
}
// Platform-specific base URL required by PingApiClient via getBaseUrl()
expect fun getBaseUrl(): String
+11 -3
View File
@@ -24,11 +24,12 @@ services:
SPRING_PROFILES_ACTIVE: ${DOCKER_SPRING_PROFILES_DOCKER:-docker}
container_name: meldestelle-ping-service
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-dev}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-docker}
SERVER_PORT: ${PING_SERVICE_PORT:-8082}
CONSUL_HOST: consul
CONSUL_PORT: ${CONSUL_PORT:-8500}
CONSUL_ENABLED: true
CONSUL_ENABLED: ${CONSUL_ENABLED:-true}
SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED: ${CONSUL_ENABLED:-true}
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: ${POSTGRES_DB:-meldestelle}
@@ -41,8 +42,15 @@ services:
- "${PING_SERVICE_PORT:-8082}:8082"
networks:
- meldestelle-network
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
consul:
condition: service_healthy
healthcheck:
test: [ "CMD", "curl", "--fail", "http://localhost:${PING_SERVICE_PORT:-8082}/actuator/health" ]
test: [ "CMD", "curl", "--fail", "http://localhost:8082/actuator/health/readiness" ]
interval: 15s
timeout: 5s
retries: 3
+7 -7
View File
@@ -50,7 +50,7 @@ COPY gradle/ gradle/
COPY platform/ platform/
# Copy client directories (required by settings.gradle.kts)
COPY client/ client/
COPY clients/ clients/
# Copy core directories (required by settings.gradle.kts)
COPY core/ core/
@@ -64,19 +64,19 @@ COPY docs/ docs/
# Copy root build configuration
COPY build.gradle.kts ./
# Copy ping-service specific files last (changes most frequently)
COPY temp/ping-service/build.gradle.kts temp/ping-service/
COPY temp/ping-service/src/ temp/ping-service/src/
# Copy ping modules (changes most frequently)
COPY services/ping/ping-api/ services/ping/ping-api/
COPY services/ping/ping-service/ services/ping/ping-service/
# Download and cache dependencies in a separate layer with build cache
RUN --mount=type=cache,target=/home/gradle/.gradle/caches \
--mount=type=cache,target=/home/gradle/.gradle/wrapper \
./gradlew :temp:ping-service:dependencies --no-daemon --info
./gradlew :services:ping:ping-service:dependencies --no-daemon --info
# Build the application with optimizations and build cache
RUN --mount=type=cache,target=/home/gradle/.gradle/caches \
--mount=type=cache,target=/home/gradle/.gradle/wrapper \
./gradlew :temp:ping-service:bootJar --no-daemon --info \
./gradlew :services:ping:ping-service:bootJar --no-daemon --info \
-Pspring.profiles.active=${SPRING_PROFILES_ACTIVE}
# ===================================================================
@@ -133,7 +133,7 @@ RUN apk update && \
# Copy the built JAR from builder stage with proper ownership
COPY --from=builder --chown=${APP_USER}:${APP_GROUP} \
/workspace/temp/ping-service/build/libs/*.jar app.jar
/workspace/services/ping/ping-service/build/libs/*.jar app.jar
# Switch to non-root user
USER ${APP_USER}
+18 -891
View File
File diff suppressed because it is too large Load Diff
@@ -454,4 +454,4 @@ spec:
**Letzte Aktualisierung**: 25. Juli 2025
Für weitere Informationen zur Gesamtarchitektur siehe [README.md](../README.md).
Für weitere Informationen zur Gesamtarchitektur siehe [README.md](../../README.md).
@@ -530,4 +530,4 @@ spec:
**Letzte Aktualisierung**: 25. Juli 2025
Für weitere Informationen zur Gesamtarchitektur siehe [README.md](../README.md).
Für weitere Informationen zur Gesamtarchitektur siehe [README.md](../../README.md).

Some files were not shown because too many files have changed in this diff Show More