fixing client

This commit is contained in:
2025-09-24 00:23:25 +02:00
parent 14d6a95e3a
commit cd2b0796a6
11 changed files with 386 additions and 29 deletions
@@ -1,49 +1,172 @@
package at.mocode
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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 org.jetbrains.compose.resources.painterResource
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
import at.mocode.composeapp.generated.resources.Res
import at.mocode.composeapp.generated.resources.compose_multiplatform
@Composable
@Preview
fun App() {
MaterialTheme {
var showContent by remember { mutableStateOf(false) }
val viewModel: PingViewModel = viewModel()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.primaryContainer)
.safeContentPadding()
.fillMaxSize(),
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Header
Card(
modifier = Modifier.fillMaxWidth()
) {
Button(onClick = { showContent = !showContent }) {
Text("Click me!")
}
AnimatedVisibility(showContent) {
val greeting = remember { Greeting().greet() }
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
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()
)
}
}
}
@@ -0,0 +1,88 @@
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}"
)
}
}
}
}
+16 -1
View File
@@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
@@ -22,8 +23,22 @@ kotlin {
sourceSets {
commonMain.dependencies {
// put your Multiplatform dependencies here
// 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,3 +1,3 @@
package at.mocode
const val SERVER_PORT = 8080
const val SERVER_PORT = 8081
@@ -0,0 +1,27 @@
package at.mocode.model
import kotlinx.serialization.Serializable
@Serializable
data class PingResponse(
val status: String,
val timestamp: String,
val service: String
)
@Serializable
data class EnhancedPingResponse(
val status: String,
val timestamp: String,
val service: String,
val circuitBreakerState: String? = null,
val responseTime: Long? = null
)
@Serializable
data class HealthResponse(
val status: String,
val timestamp: String,
val service: String,
val healthy: Boolean
)
@@ -0,0 +1,65 @@
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
@@ -0,0 +1,4 @@
package at.mocode.service
// Use direct ping-service for JS Development - based on central.toml
actual fun getBaseUrl(): String = "http://localhost:8082"
@@ -0,0 +1,4 @@
package at.mocode.service
// Use direct ping-service for JVM (Desktop) - based on central.toml
actual fun getBaseUrl(): String = "http://localhost:8082"
@@ -0,0 +1,4 @@
package at.mocode.service
// Use direct ping-service for WASM Development - based on central.toml
actual fun getBaseUrl(): String = "http://localhost:8082"
@@ -1,12 +1,20 @@
package at.mocode.temp.pingservice
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
@RestController
@CrossOrigin(
origins = ["http://localhost:8080", "http://localhost:8083", "http://localhost:4000"],
methods = [RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.OPTIONS],
allowedHeaders = ["*"],
allowCredentials = "true"
)
class PingController(
private val pingService: PingServiceCircuitBreaker
) {
@@ -2,11 +2,30 @@ package at.mocode.temp.pingservice
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.EnableAspectJAutoProxy
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@SpringBootApplication
@EnableAspectJAutoProxy
class PingServiceApplication
class PingServiceApplication {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOriginPatterns("http://localhost:*")
.allowedOrigins("http://localhost:8080", "http://localhost:8083", "http://localhost:4000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600)
}
}
}
}
fun main(args: Array<String>) {
runApplication<PingServiceApplication>(*args)