(fix) Umbau zu SCS

This commit is contained in:
stefan
2025-07-19 11:26:09 +02:00
parent e1125a3fc0
commit e76db7e924
39 changed files with 1107 additions and 342 deletions
+1
View File
@@ -21,6 +21,7 @@ kotlin {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.materialIconsExtended)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
@@ -18,7 +18,7 @@ object AppDependencies {
private val mockPersonRepository = object : PersonRepository {
override suspend fun save(person: at.mocode.members.domain.model.DomPerson): at.mocode.members.domain.model.DomPerson {
// Mock implementation - just return the person with an ID
return person.copy(id = com.benasher44.uuid.uuid4())
return person.copy(personId = com.benasher44.uuid.uuid4())
}
override suspend fun findById(id: com.benasher44.uuid.Uuid): at.mocode.members.domain.model.DomPerson? {
@@ -29,16 +29,28 @@ object AppDependencies {
return null // Mock implementation
}
override suspend fun findByStammVereinId(vereinId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomPerson> {
return emptyList() // Mock implementation
}
override suspend fun findByName(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomPerson> {
return emptyList() // Mock implementation
}
override suspend fun findAllActive(limit: Int, offset: Int): List<at.mocode.members.domain.model.DomPerson> {
return emptyList() // Mock implementation
}
override suspend fun countActive(): Long {
return 0L // Mock implementation
}
override suspend fun existsByOepsSatzNr(oepsSatzNr: String): Boolean {
return false // Mock implementation - no duplicates for demo
}
override suspend fun findAll(): List<at.mocode.members.domain.model.DomPerson> {
return emptyList() // Mock implementation
}
override suspend fun delete(id: com.benasher44.uuid.Uuid) {
// Mock implementation
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
return true // Mock implementation
}
}
@@ -47,19 +59,75 @@ object AppDependencies {
return null // Mock implementation
}
override suspend fun existsById(id: com.benasher44.uuid.Uuid): Boolean {
return true // Mock implementation - assume all clubs exist
override suspend fun findByOepsVereinsNr(oepsVereinsNr: String): at.mocode.members.domain.model.DomVerein? {
return null // Mock implementation
}
override suspend fun findAll(): List<at.mocode.members.domain.model.DomVerein> {
override suspend fun findByName(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
return emptyList() // Mock implementation
}
override suspend fun findByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
return emptyList() // Mock implementation
}
override suspend fun findByLandId(landId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
return emptyList() // Mock implementation
}
override suspend fun findAllActive(limit: Int, offset: Int): List<at.mocode.members.domain.model.DomVerein> {
return emptyList() // Mock implementation
}
override suspend fun findByLocation(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
return emptyList() // Mock implementation
}
override suspend fun save(verein: at.mocode.members.domain.model.DomVerein): at.mocode.members.domain.model.DomVerein {
return verein.copy(vereinId = com.benasher44.uuid.uuid4()) // Mock implementation
}
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
return true // Mock implementation
}
override suspend fun existsByOepsVereinsNr(oepsVereinsNr: String): Boolean {
return false // Mock implementation
}
override suspend fun countActive(): Long {
return 0L // Mock implementation
}
override suspend fun countActiveByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): Long {
return 0L // Mock implementation
}
}
private val mockMasterDataService = object : MasterDataService {
override suspend fun countryExists(countryId: com.benasher44.uuid.Uuid): Boolean {
return true // Mock implementation - assume all countries exist
}
override suspend fun stateExists(stateId: com.benasher44.uuid.Uuid): Boolean {
return true // Mock implementation - assume all states exist
}
override suspend fun getCountryById(countryId: com.benasher44.uuid.Uuid): MasterDataService.CountryInfo? {
return null // Mock implementation
}
override suspend fun getStateById(stateId: com.benasher44.uuid.Uuid): MasterDataService.StateInfo? {
return null // Mock implementation
}
override suspend fun getAllCountries(): List<MasterDataService.CountryInfo> {
return emptyList() // Mock implementation
}
override suspend fun getStatesByCountry(countryId: com.benasher44.uuid.Uuid): List<MasterDataService.StateInfo> {
return emptyList() // Mock implementation
}
}
// Use case instances
@@ -5,6 +5,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
@@ -36,7 +37,7 @@ fun CreatePersonScreen(
title = { Text("Person erstellen") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Zurück")
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
}
}
)
@@ -135,7 +136,6 @@ fun CreatePersonScreen(
label = { Text("Geschlecht") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showGeschlechtDropdown) },
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
)
ExposedDropdownMenu(
@@ -29,7 +29,7 @@ class PersonListViewModel(
errorMessage = null
try {
persons = personRepository.findAll()
persons = personRepository.findAllActive(limit = 100, offset = 0)
} catch (e: Exception) {
errorMessage = "Fehler beim Laden der Personen: ${e.message}"
} finally {
@@ -1,18 +1,15 @@
package at.mocode.ui.viewmodel
import at.mocode.enums.GeschlechtE
import at.mocode.members.application.usecase.CreatePersonUseCase
import at.mocode.members.domain.model.DomPerson
import at.mocode.members.domain.repository.PersonRepository
import at.mocode.members.domain.repository.VereinRepository
import at.mocode.members.domain.service.MasterDataService
import at.mocode.enums.GeschlechtE
import at.mocode.enums.DatenQuelleE
import at.mocode.validation.ValidationResult
import com.benasher44.uuid.uuid4
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.*
import kotlinx.datetime.LocalDate
import kotlin.test.*
@OptIn(ExperimentalCoroutinesApi::class)
@@ -33,29 +30,44 @@ class CreatePersonViewModelTest {
private val persons = mutableListOf<DomPerson>()
override suspend fun save(person: DomPerson): DomPerson {
val savedPerson = person.copy(id = uuid4())
val savedPerson = person.copy(personId = uuid4())
persons.add(savedPerson)
return savedPerson
}
override suspend fun findById(id: com.benasher44.uuid.Uuid): DomPerson? {
return persons.find { it.id == id }
return persons.find { it.personId == id }
}
override suspend fun findByOepsSatzNr(oepsSatzNr: String): DomPerson? {
return persons.find { it.oepsSatzNr == oepsSatzNr }
}
override suspend fun findByStammVereinId(vereinId: com.benasher44.uuid.Uuid): List<DomPerson> {
return persons.filter { it.stammVereinId == vereinId }
}
override suspend fun findByName(searchTerm: String, limit: Int): List<DomPerson> {
return persons.filter {
it.vorname.contains(searchTerm, ignoreCase = true) ||
it.nachname.contains(searchTerm, ignoreCase = true)
}.take(limit)
}
override suspend fun findAllActive(limit: Int, offset: Int): List<DomPerson> {
return persons.filter { !it.istGesperrt }.drop(offset).take(limit)
}
override suspend fun existsByOepsSatzNr(oepsSatzNr: String): Boolean {
return persons.any { it.oepsSatzNr == oepsSatzNr }
}
override suspend fun findAll(): List<DomPerson> {
return persons.toList()
override suspend fun countActive(): Long {
return persons.count { !it.istGesperrt }.toLong()
}
override suspend fun delete(id: com.benasher44.uuid.Uuid) {
persons.removeAll { it.id == id }
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
return persons.removeAll { it.personId == id }
}
}
@@ -64,12 +76,48 @@ class CreatePersonViewModelTest {
return null
}
override suspend fun existsById(id: com.benasher44.uuid.Uuid): Boolean {
override suspend fun findByOepsVereinsNr(oepsVereinsNr: String): at.mocode.members.domain.model.DomVerein? {
return null
}
override suspend fun findByName(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
return emptyList()
}
override suspend fun findByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
return emptyList()
}
override suspend fun findByLandId(landId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
return emptyList()
}
override suspend fun findAllActive(limit: Int, offset: Int): List<at.mocode.members.domain.model.DomVerein> {
return emptyList()
}
override suspend fun findByLocation(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
return emptyList()
}
override suspend fun save(verein: at.mocode.members.domain.model.DomVerein): at.mocode.members.domain.model.DomVerein {
return verein
}
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
return true
}
override suspend fun findAll(): List<at.mocode.members.domain.model.DomVerein> {
return emptyList()
override suspend fun existsByOepsVereinsNr(oepsVereinsNr: String): Boolean {
return false
}
override suspend fun countActive(): Long {
return 0L
}
override suspend fun countActiveByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): Long {
return 0L
}
}
@@ -77,6 +125,26 @@ class CreatePersonViewModelTest {
override suspend fun countryExists(countryId: com.benasher44.uuid.Uuid): Boolean {
return true
}
override suspend fun stateExists(stateId: com.benasher44.uuid.Uuid): Boolean {
return true
}
override suspend fun getCountryById(countryId: com.benasher44.uuid.Uuid): MasterDataService.CountryInfo? {
return null
}
override suspend fun getStateById(stateId: com.benasher44.uuid.Uuid): MasterDataService.StateInfo? {
return null
}
override suspend fun getAllCountries(): List<MasterDataService.CountryInfo> {
return emptyList()
}
override suspend fun getStatesByCountry(countryId: com.benasher44.uuid.Uuid): List<MasterDataService.StateInfo> {
return emptyList()
}
}
createPersonUseCase = CreatePersonUseCase(
@@ -26,29 +26,44 @@ class PersonListViewModelTest {
private val persons = mutableListOf<DomPerson>()
override suspend fun save(person: DomPerson): DomPerson {
val savedPerson = person.copy(id = uuid4())
val savedPerson = person.copy(personId = uuid4())
persons.add(savedPerson)
return savedPerson
}
override suspend fun findById(id: com.benasher44.uuid.Uuid): DomPerson? {
return persons.find { it.id == id }
return persons.find { it.personId == id }
}
override suspend fun findByOepsSatzNr(oepsSatzNr: String): DomPerson? {
return persons.find { it.oepsSatzNr == oepsSatzNr }
}
override suspend fun findByStammVereinId(vereinId: com.benasher44.uuid.Uuid): List<DomPerson> {
return persons.filter { it.stammVereinId == vereinId }
}
override suspend fun findByName(searchTerm: String, limit: Int): List<DomPerson> {
return persons.filter {
it.nachname.contains(searchTerm, ignoreCase = true) ||
it.vorname.contains(searchTerm, ignoreCase = true)
}.take(limit)
}
override suspend fun findAllActive(limit: Int, offset: Int): List<DomPerson> {
return persons.filter { it.istAktiv }.drop(offset).take(limit)
}
override suspend fun countActive(): Long {
return persons.count { it.istAktiv }.toLong()
}
override suspend fun existsByOepsSatzNr(oepsSatzNr: String): Boolean {
return persons.any { it.oepsSatzNr == oepsSatzNr }
}
override suspend fun findAll(): List<DomPerson> {
return persons.toList()
}
override suspend fun delete(id: com.benasher44.uuid.Uuid) {
persons.removeAll { it.id == id }
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
return persons.removeAll { it.personId == id }
}
}
}
@@ -71,6 +86,7 @@ class PersonListViewModelTest {
fun `loadPersons should update persons list`() = runTest {
// Given
val testPerson = DomPerson(
oepsSatzNr = "123456",
nachname = "Test",
vorname = "User",
geschlechtE = GeschlechtE.M,
@@ -100,6 +116,7 @@ class PersonListViewModelTest {
// Add a new person to repository
val newPerson = DomPerson(
oepsSatzNr = "789012",
nachname = "New",
vorname = "Person",
geschlechtE = GeschlechtE.W,
+11 -4
View File
@@ -55,14 +55,21 @@ kotlin {
}
jsMain.dependencies {
// Kotlin React dependencies with explicit versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:${libs.versions.kotlinWrappers.get()}")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:${libs.versions.kotlinWrappers.get()}")
// Kotlin React dependencies with explicit stable versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:11.10.5-pre.467")
// Ktor client for data loading
implementation("io.ktor:ktor-client-core:3.1.2")
implementation("io.ktor:ktor-client-js:3.1.2")
implementation("io.ktor:ktor-client-content-negotiation:3.1.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.2")
// NPM dependencies
implementation(npm("react", "18.2.0"))
implementation(npm("react-dom", "18.2.0"))
implementation(npm("react-to-web-component", "2.0.2"))
implementation(npm("@r2wc/react-to-web-component", "2.0.4"))
}
}
}
@@ -0,0 +1,24 @@
import at.mocode.events.ui.components.VeranstaltungsListe
import react.create
/**
* Main entry point for the JavaScript build.
*
* This function serves as the entry point for the Kotlin/JS application.
* It registers the React component as a web component using r2wc.
*/
fun main() {
console.log("Event Management JS module loaded successfully!")
// Import r2wc function from @r2wc/react-to-web-component npm package
val r2wc = js("require('@r2wc/react-to-web-component')")
// Convert React component to Web Component using r2wc
val VeranstaltungsListeWebComponent = r2wc(VeranstaltungsListe, js("{}"))
// Register the new component with a custom HTML tag
js("customElements.define('veranstaltungs-liste', arguments[0])")(VeranstaltungsListeWebComponent)
console.log("Web component 'veranstaltungs-liste' registered successfully!")
console.log("You can now use <veranstaltungs-liste></veranstaltungs-liste> in your HTML")
}
@@ -0,0 +1,176 @@
package at.mocode.events.ui.components
import at.mocode.events.domain.model.Veranstaltung
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import react.*
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.h1
import react.dom.html.ReactHTML.h3
import react.dom.html.ReactHTML.p
import react.dom.html.ReactHTML.span
import emotion.react.css
/**
* Props for the VeranstaltungsListe component
*/
external interface VeranstaltungsListeProps : Props
// Create Ktor client for API calls
private val apiClient = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
}
/**
* React component that displays a list of events (Veranstaltungen).
*
* This component loads event data from the API and renders it as HTML.
* Uses useState for state management and useEffectOnce for data loading.
*/
val VeranstaltungsListe = FC<VeranstaltungsListeProps> { _ ->
// State management with useState
var events by useState<List<Veranstaltung>>(emptyList())
var loading by useState(true)
var error by useState<String?>(null)
// Data loading with useEffectOnce hook
useEffectOnce {
val scope = MainScope()
scope.launch {
try {
loading = true
error = null
// Load data with Ktor client
val response = apiClient.get("http://localhost:8080/api/events")
val loadedEvents: List<Veranstaltung> = response.body()
events = loadedEvents
} catch (e: Exception) {
error = "Fehler beim Laden der Veranstaltungen: ${e.message}"
console.error("Error loading events:", e)
} finally {
loading = false
}
}
}
// Render HTML with React DOM elements
div {
css {
// Basic styling for the main container
}
h1 {
+"Veranstaltungen"
}
when {
loading -> {
div {
+"Lade Veranstaltungen..."
}
}
error != null -> {
div {
+error!!
}
}
events.isEmpty() -> {
div {
+"Keine Veranstaltungen verfügbar"
}
}
else -> {
div {
events.forEach { event ->
div {
h3 {
+event.name
}
p {
span {
+"📍"
}
+" ${event.ort}"
}
p {
span {
+"📅"
}
if (event.isMultiDay()) {
+" ${event.startDatum} - ${event.endDatum} (${event.getDurationInDays()} Tage)"
} else {
+" ${event.startDatum} (Eintägige Veranstaltung)"
}
}
// Status indicators
val statusList = mutableListOf<String>()
if (event.istAktiv) statusList.add("Aktiv")
if (event.istOeffentlich) statusList.add("Öffentlich")
if (event.isRegistrationOpen()) statusList.add("Anmeldung offen")
if (statusList.isNotEmpty()) {
p {
span {
+""
}
+" Status: ${statusList.joinToString(", ")}"
}
}
// Description
if (!event.beschreibung.isNullOrBlank()) {
p {
span {
+"📝"
}
+" ${event.beschreibung}"
}
}
// Sports/Sparten
if (event.sparten.isNotEmpty()) {
p {
span {
+"🏆"
}
+" Sparten: ${event.sparten.joinToString(", ") { it.name }}"
}
}
// Additional info
event.maxTeilnehmer?.let { max ->
p {
span {
+"👥"
}
+" Max. Teilnehmer: $max"
}
}
event.anmeldeschluss?.let { deadline ->
p {
span {
+""
}
+" Anmeldeschluss: $deadline"
}
}
}
}
}
}
}
}
}
@@ -1,4 +1,4 @@
package at.mocode.events.ui
package at.mocode.events.ui.utils
import at.mocode.events.domain.model.Veranstaltung
@@ -42,10 +42,3 @@ object EventUIUtils {
}
}
/**
* Main entry point for the JS application
*/
fun main() {
console.log("Event Management JS module loaded successfully!")
console.log("React dependencies are available for UI development")
}
@@ -11,6 +11,7 @@ import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.statements.UpdateBuilder
/**
* Exposed-based implementation of VeranstaltungRepository.
+3
View File
@@ -41,3 +41,6 @@ org.gradle.vfs.watch=true
# Build-Reports minimieren für sauberen Build-Process
org.gradle.logging.level=lifecycle
kotlin.build.report.single_file=false
# Compose Experimental Features
org.jetbrains.compose.experimental.jscanvas.enabled=true
+7 -1
View File
@@ -6,7 +6,7 @@ kotlinxSerialization = "1.8.1"
kotlinxDatetime = "0.6.1"
# Kotlin Wrappers for JS
kotlinWrappers = "1.0.0-pre.761"
kotlinWrappers = "1.0.0-pre.710"
# Compose
composeMultiplatform = "1.8.0" #"1.7.3"
@@ -58,6 +58,12 @@ ktor-server-authJwt = { module = "io.ktor:ktor-server-auth-jwt", version.ref = "
ktor-server-openapi = { module = "io.ktor:ktor-server-openapi", version.ref = "ktor" }
ktor-server-swagger = { module = "io.ktor:ktor-server-swagger", version.ref = "ktor" }
# Ktor Client
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-contentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-serializationKotlinxJson = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
# Database
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
+3 -4
View File
@@ -37,14 +37,13 @@ kotlin {
}
jsMain.dependencies {
// Kotlin React dependencies with explicit versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:${libs.versions.kotlinWrappers.get()}")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:${libs.versions.kotlinWrappers.get()}")
// Kotlin React dependencies with explicit stable versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:11.10.5-pre.467")
// NPM dependencies
implementation(npm("react", "18.2.0"))
implementation(npm("react-dom", "18.2.0"))
implementation(npm("react-to-web-component", "2.0.2"))
}
}
}
@@ -3,6 +3,7 @@ package at.mocode.horses.infrastructure.api
import at.mocode.horses.application.usecase.*
import at.mocode.horses.domain.repository.HorseRepository
import at.mocode.dto.base.BaseDto
import at.mocode.dto.base.ApiResponse
import at.mocode.enums.PferdeGeschlechtE
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuidFrom
@@ -12,6 +13,7 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.Contextual
/**
* REST API controller for horse registry operations.
@@ -54,9 +56,9 @@ class HorseController(
else -> horseRepository.findAllActive(limit)
}
call.respond(HttpStatusCode.OK, BaseDto.success(horses))
call.respond(HttpStatusCode.OK, ApiResponse.success(horses))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to retrieve horses: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to retrieve horses: ${e.message}"))
}
}
@@ -64,18 +66,17 @@ class HorseController(
get("/{id}") {
try {
val horseId = uuidFrom(call.parameters["id"]!!)
val request = GetHorseUseCase.GetHorseRequest(horseId)
val response = getHorseUseCase.execute(request)
val horse = getHorseUseCase.getById(horseId)
if (response.success && response.horse != null) {
call.respond(HttpStatusCode.OK, BaseDto.success(response.horse))
if (horse != null) {
call.respond(HttpStatusCode.OK, ApiResponse.success(horse))
} else {
call.respond(HttpStatusCode.NotFound, BaseDto.error<Any>("Horse not found"))
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Any>("Horse not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Invalid horse ID format"))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Invalid horse ID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to retrieve horse: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to retrieve horse: ${e.message}"))
}
}
@@ -86,12 +87,12 @@ class HorseController(
val horse = horseRepository.findByLebensnummer(lebensnummer)
if (horse != null) {
call.respond(HttpStatusCode.OK, BaseDto.success(horse))
call.respond(HttpStatusCode.OK, ApiResponse.success(horse))
} else {
call.respond(HttpStatusCode.NotFound, BaseDto.error<Any>("Horse with life number '$lebensnummer' not found"))
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Any>("Horse with life number '$lebensnummer' not found"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to search horse: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to search horse: ${e.message}"))
}
}
@@ -102,12 +103,12 @@ class HorseController(
val horse = horseRepository.findByChipNummer(chipNummer)
if (horse != null) {
call.respond(HttpStatusCode.OK, BaseDto.success(horse))
call.respond(HttpStatusCode.OK, ApiResponse.success(horse))
} else {
call.respond(HttpStatusCode.NotFound, BaseDto.error<Any>("Horse with chip number '$chipNummer' not found"))
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Any>("Horse with chip number '$chipNummer' not found"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to search horse: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to search horse: ${e.message}"))
}
}
@@ -116,9 +117,9 @@ class HorseController(
try {
val activeOnly = call.request.queryParameters["activeOnly"]?.toBoolean() ?: true
val horses = horseRepository.findOepsRegistered(activeOnly)
call.respond(HttpStatusCode.OK, BaseDto.success(horses))
call.respond(HttpStatusCode.OK, ApiResponse.success(horses))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to retrieve OEPS horses: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to retrieve OEPS horses: ${e.message}"))
}
}
@@ -127,9 +128,9 @@ class HorseController(
try {
val activeOnly = call.request.queryParameters["activeOnly"]?.toBoolean() ?: true
val horses = horseRepository.findFeiRegistered(activeOnly)
call.respond(HttpStatusCode.OK, BaseDto.success(horses))
call.respond(HttpStatusCode.OK, ApiResponse.success(horses))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to retrieve FEI horses: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to retrieve FEI horses: ${e.message}"))
}
}
@@ -146,9 +147,9 @@ class HorseController(
feiRegistered = feiCount.toLong()
)
call.respond(HttpStatusCode.OK, BaseDto.success(stats))
call.respond(HttpStatusCode.OK, ApiResponse.success(stats))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to retrieve statistics: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to retrieve statistics: ${e.message}"))
}
}
@@ -159,12 +160,12 @@ class HorseController(
val response = createHorseUseCase.execute(createRequest)
if (response.success) {
call.respond(HttpStatusCode.Created, BaseDto.success(response.horse))
call.respond(HttpStatusCode.Created, ApiResponse.success(response.data!!))
} else {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Validation failed", response.errors))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Validation failed"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to create horse: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to create horse: ${e.message}"))
}
}
@@ -202,14 +203,14 @@ class HorseController(
val response = updateHorseUseCase.execute(updateRequest)
if (response.success && response.horse != null) {
call.respond(HttpStatusCode.OK, BaseDto.success(response.horse))
call.respond(HttpStatusCode.OK, ApiResponse.success(response.horse))
} else {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Update failed", response.errors))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Update failed: ${response.errors.joinToString(", ")}"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Invalid horse ID format"))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Invalid horse ID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to update horse: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to update horse: ${e.message}"))
}
}
@@ -223,14 +224,19 @@ class HorseController(
val response = deleteHorseUseCase.execute(deleteRequest)
if (response.success) {
call.respond(HttpStatusCode.OK, BaseDto.success("Horse deleted successfully", response.warnings))
val message = if (response.warnings.isNotEmpty()) {
"Horse deleted successfully. Warnings: ${response.warnings.joinToString(", ")}"
} else {
"Horse deleted successfully"
}
call.respond(HttpStatusCode.OK, ApiResponse.success(message))
} else {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Delete failed", response.errors))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Delete failed: ${response.errors.joinToString(", ")}"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Invalid horse ID format"))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Invalid horse ID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to delete horse: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to delete horse: ${e.message}"))
}
}
@@ -241,14 +247,19 @@ class HorseController(
val response = deleteHorseUseCase.softDelete(horseId)
if (response.success) {
call.respond(HttpStatusCode.OK, BaseDto.success("Horse marked as inactive", response.warnings))
val message = if (response.warnings.isNotEmpty()) {
"Horse marked as inactive. Warnings: ${response.warnings.joinToString(", ")}"
} else {
"Horse marked as inactive"
}
call.respond(HttpStatusCode.OK, ApiResponse.success(message))
} else {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Soft delete failed", response.errors))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Soft delete failed: ${response.errors.joinToString(", ")}"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<Any>("Invalid horse ID format"))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Any>("Invalid horse ID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to soft delete horse: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to soft delete horse: ${e.message}"))
}
}
@@ -259,9 +270,9 @@ class HorseController(
val response = deleteHorseUseCase.batchDelete(batchRequest.horseIds, batchRequest.forceDelete)
val statusCode = if (response.overallSuccess) HttpStatusCode.OK else HttpStatusCode.PartialContent
call.respond(statusCode, BaseDto.success(response))
call.respond(statusCode, ApiResponse.success(response))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Any>("Failed to batch delete horses: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Any>("Failed to batch delete horses: ${e.message}"))
}
}
}
@@ -277,8 +288,8 @@ class HorseController(
val geburtsdatum: kotlinx.datetime.LocalDate? = null,
val rasse: String? = null,
val farbe: String? = null,
val besitzerId: Uuid? = null,
val verantwortlichePersonId: Uuid? = null,
@Contextual val besitzerId: Uuid? = null,
@Contextual val verantwortlichePersonId: Uuid? = null,
val zuechterName: String? = null,
val zuchtbuchNummer: String? = null,
val lebensnummer: String? = null,
@@ -300,7 +311,7 @@ class HorseController(
*/
@Serializable
data class BatchDeleteRequest(
val horseIds: List<Uuid>,
val horseIds: List<@Contextual Uuid>,
val forceDelete: Boolean = false
)
@@ -1,13 +1,12 @@
package at.mocode.horses.infrastructure.repository
import at.mocode.enums.PferdeGeschlechtE
import at.mocode.horses.domain.model.DomPferd
import at.mocode.horses.domain.repository.HorseRepository
import at.mocode.enums.PferdeGeschlechtE
import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.statements.UpdateBuilder
/**
* PostgreSQL implementation of the HorseRepository using Exposed ORM.
@@ -18,25 +17,25 @@ import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
class HorseRepositoryImpl : HorseRepository {
override suspend fun findById(id: Uuid): DomPferd? {
return HorseTable.select { HorseTable.id eq id }
return HorseTable.selectAll().where { HorseTable.id eq id }
.map { rowToDomPferd(it) }
.singleOrNull()
}
override suspend fun findByLebensnummer(lebensnummer: String): DomPferd? {
return HorseTable.select { HorseTable.lebensnummer eq lebensnummer }
return HorseTable.selectAll().where { HorseTable.lebensnummer eq lebensnummer }
.map { rowToDomPferd(it) }
.singleOrNull()
}
override suspend fun findByChipNummer(chipNummer: String): DomPferd? {
return HorseTable.select { HorseTable.chipNummer eq chipNummer }
return HorseTable.selectAll().where { HorseTable.chipNummer eq chipNummer }
.map { rowToDomPferd(it) }
.singleOrNull()
}
override suspend fun findByPassNummer(passNummer: String): DomPferd? {
return HorseTable.select { HorseTable.passNummer eq passNummer }
return HorseTable.selectAll().where { HorseTable.passNummer eq passNummer }
.map { rowToDomPferd(it) }
.singleOrNull()
}
@@ -48,83 +47,98 @@ class HorseRepositoryImpl : HorseRepository {
}
override suspend fun findByFeiNummer(feiNummer: String): DomPferd? {
return HorseTable.select { HorseTable.feiNummer eq feiNummer }
return HorseTable.selectAll().where { HorseTable.feiNummer eq feiNummer }
.map { rowToDomPferd(it) }
.singleOrNull()
}
override suspend fun findByName(searchTerm: String, limit: Int): List<DomPferd> {
return HorseTable.select { HorseTable.pferdeName like "%$searchTerm%" }
.orderBy(HorseTable.pferdeName)
return HorseTable.selectAll().where { HorseTable.pferdeName like "%$searchTerm%" }
.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.limit(limit)
.map { rowToDomPferd(it) }
}
override suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean): List<DomPferd> {
val query = HorseTable.select { HorseTable.besitzerId eq ownerId }
val query = HorseTable.selectAll().where { HorseTable.besitzerId eq ownerId }
return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true }
} else {
query
}.orderBy(HorseTable.pferdeName)
}.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.map { rowToDomPferd(it) }
}
override suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean): List<DomPferd> {
val query = HorseTable.select { HorseTable.verantwortlichePersonId eq responsiblePersonId }
val query = HorseTable.selectAll().where { HorseTable.verantwortlichePersonId eq responsiblePersonId }
return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true }
} else {
query
}.orderBy(HorseTable.pferdeName)
}.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.map { rowToDomPferd(it) }
}
override suspend fun findByGeschlecht(geschlecht: PferdeGeschlechtE, activeOnly: Boolean, limit: Int): List<DomPferd> {
val query = HorseTable.select { HorseTable.geschlecht eq geschlecht }
val query = HorseTable.selectAll().where { HorseTable.geschlecht eq geschlecht }
return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true }
} else {
query
}.orderBy(HorseTable.pferdeName)
}.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.limit(limit)
.map { rowToDomPferd(it) }
}
override suspend fun findByRasse(rasse: String, activeOnly: Boolean, limit: Int): List<DomPferd> {
val query = HorseTable.select { HorseTable.rasse eq rasse }
val query = HorseTable.selectAll().where { HorseTable.rasse eq rasse }
return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true }
} else {
query
}.orderBy(HorseTable.pferdeName)
}.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.limit(limit)
.map { rowToDomPferd(it) }
}
override suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean): List<DomPferd> {
val query = HorseTable.select {
val query = HorseTable.selectAll().where {
HorseTable.geburtsdatum.isNotNull() and
HorseTable.geburtsdatum.year() eq birthYear
(CustomFunction(
"EXTRACT",
IntegerColumnType(),
stringLiteral("YEAR FROM "),
HorseTable.geburtsdatum
) eq birthYear)
}
return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true }
} else {
query
}.orderBy(HorseTable.pferdeName)
}.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.map { rowToDomPferd(it) }
}
override suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean): List<DomPferd> {
val query = HorseTable.select {
val query = HorseTable.selectAll().where {
HorseTable.geburtsdatum.isNotNull() and
(HorseTable.geburtsdatum.year() greaterEq fromYear) and
(HorseTable.geburtsdatum.year() lessEq toYear)
(CustomFunction(
"EXTRACT",
IntegerColumnType(),
stringLiteral("YEAR FROM "),
HorseTable.geburtsdatum
) greaterEq fromYear) and
(CustomFunction(
"EXTRACT",
IntegerColumnType(),
stringLiteral("YEAR FROM "),
HorseTable.geburtsdatum
) lessEq toYear)
}
return if (activeOnly) {
@@ -136,31 +150,31 @@ class HorseRepositoryImpl : HorseRepository {
}
override suspend fun findAllActive(limit: Int): List<DomPferd> {
return HorseTable.select { HorseTable.istAktiv eq true }
.orderBy(HorseTable.pferdeName)
return HorseTable.selectAll().where { HorseTable.istAktiv eq true }
.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.limit(limit)
.map { rowToDomPferd(it) }
}
override suspend fun findOepsRegistered(activeOnly: Boolean): List<DomPferd> {
val query = HorseTable.select { HorseTable.oepsNummer.isNotNull() }
val query = HorseTable.selectAll().where { HorseTable.oepsNummer.isNotNull() }
return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true }
} else {
query
}.orderBy(HorseTable.pferdeName)
}.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.map { rowToDomPferd(it) }
}
override suspend fun findFeiRegistered(activeOnly: Boolean): List<DomPferd> {
val query = HorseTable.select { HorseTable.feiNummer.isNotNull() }
val query = HorseTable.selectAll().where { HorseTable.feiNummer.isNotNull() }
return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true }
} else {
query
}.orderBy(HorseTable.pferdeName)
}.orderBy(HorseTable.pferdeName to SortOrder.ASC)
.map { rowToDomPferd(it) }
}
@@ -175,7 +189,7 @@ class HorseRepositoryImpl : HorseRepository {
}
updatedHorse
} else {
// Insert new horse
// Insert a new horse
HorseTable.insert {
it[id] = horse.pferdId
domPferdToStatement(it, horse)
@@ -190,22 +204,22 @@ class HorseRepositoryImpl : HorseRepository {
}
override suspend fun existsByLebensnummer(lebensnummer: String): Boolean {
return HorseTable.select { HorseTable.lebensnummer eq lebensnummer }
return HorseTable.selectAll().where { HorseTable.lebensnummer eq lebensnummer }
.count() > 0
}
override suspend fun existsByChipNummer(chipNummer: String): Boolean {
return HorseTable.select { HorseTable.chipNummer eq chipNummer }
return HorseTable.selectAll().where { HorseTable.chipNummer eq chipNummer }
.count() > 0
}
override suspend fun existsByPassNummer(passNummer: String): Boolean {
return HorseTable.select { HorseTable.passNummer eq passNummer }
return HorseTable.selectAll().where { HorseTable.passNummer eq passNummer }
.count() > 0
}
override suspend fun existsByOepsNummer(oepsNummer: String): Boolean {
return HorseTable.select { HorseTable.oepsNummer eq oepsNummer }
return HorseTable.selectAll().where { HorseTable.oepsNummer eq oepsNummer }
.count() > 0
}
+417 -3
View File
@@ -2,6 +2,91 @@
# yarn lockfile v1
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be"
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
dependencies:
"@babel/helper-validator-identifier" "^7.27.1"
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/generator@^7.28.0":
version "7.28.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2"
integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==
dependencies:
"@babel/parser" "^7.28.0"
"@babel/types" "^7.28.0"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/helper-globals@^7.28.0":
version "7.28.0"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674"
integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
"@babel/helper-module-imports@^7.16.7":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
dependencies:
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
"@babel/helper-string-parser@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
"@babel/helper-validator-identifier@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
"@babel/parser@^7.27.2", "@babel/parser@^7.28.0":
version "7.28.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e"
integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==
dependencies:
"@babel/types" "^7.28.0"
"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3":
version "7.27.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6"
integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==
"@babel/template@^7.27.2":
version "7.27.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/parser" "^7.27.2"
"@babel/types" "^7.27.1"
"@babel/traverse@^7.27.1":
version "7.28.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b"
integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/generator" "^7.28.0"
"@babel/helper-globals" "^7.28.0"
"@babel/parser" "^7.28.0"
"@babel/template" "^7.27.2"
"@babel/types" "^7.28.0"
debug "^4.3.1"
"@babel/types@^7.27.1", "@babel/types@^7.28.0":
version "7.28.1"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.1.tgz#2aaf3c10b31ba03a77ac84f52b3912a0edef4cf9"
integrity sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
@@ -12,6 +97,132 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@emotion/babel-plugin@^11.13.5":
version "11.13.5"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0"
integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/runtime" "^7.18.3"
"@emotion/hash" "^0.9.2"
"@emotion/memoize" "^0.9.0"
"@emotion/serialize" "^1.3.3"
babel-plugin-macros "^3.1.0"
convert-source-map "^1.5.0"
escape-string-regexp "^4.0.0"
find-root "^1.1.0"
source-map "^0.5.7"
stylis "4.2.0"
"@emotion/cache@^11.13.5", "@emotion/cache@^11.14.0":
version "11.14.0"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76"
integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==
dependencies:
"@emotion/memoize" "^0.9.0"
"@emotion/sheet" "^1.4.0"
"@emotion/utils" "^1.4.2"
"@emotion/weak-memoize" "^0.4.0"
stylis "4.2.0"
"@emotion/css@^11.10.5":
version "11.13.5"
resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.13.5.tgz#db2d3be6780293640c082848e728a50544b9dfa4"
integrity sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==
dependencies:
"@emotion/babel-plugin" "^11.13.5"
"@emotion/cache" "^11.13.5"
"@emotion/serialize" "^1.3.3"
"@emotion/sheet" "^1.4.0"
"@emotion/utils" "^1.4.2"
"@emotion/hash@^0.9.2":
version "0.9.2"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b"
integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
"@emotion/is-prop-valid@^1.3.0":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz#8d5cf1132f836d7adbe42cf0b49df7816fc88240"
integrity sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==
dependencies:
"@emotion/memoize" "^0.9.0"
"@emotion/memoize@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102"
integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
"@emotion/react@^11.10.5":
version "11.14.0"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d"
integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.13.5"
"@emotion/cache" "^11.14.0"
"@emotion/serialize" "^1.3.3"
"@emotion/use-insertion-effect-with-fallbacks" "^1.2.0"
"@emotion/utils" "^1.4.2"
"@emotion/weak-memoize" "^0.4.0"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8"
integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==
dependencies:
"@emotion/hash" "^0.9.2"
"@emotion/memoize" "^0.9.0"
"@emotion/unitless" "^0.10.0"
"@emotion/utils" "^1.4.2"
csstype "^3.0.2"
"@emotion/sheet@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
"@emotion/styled@^11.10.5":
version "11.14.1"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.14.1.tgz#8c34bed2948e83e1980370305614c20955aacd1c"
integrity sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.13.5"
"@emotion/is-prop-valid" "^1.3.0"
"@emotion/serialize" "^1.3.3"
"@emotion/use-insertion-effect-with-fallbacks" "^1.2.0"
"@emotion/utils" "^1.4.2"
"@emotion/unitless@^0.10.0":
version "0.10.0"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745"
integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==
"@emotion/use-insertion-effect-with-fallbacks@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf"
integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==
"@emotion/utils@^1.4.2":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52"
integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==
"@emotion/weak-memoize@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
"@jridgewell/gen-mapping@^0.3.12":
version "0.3.12"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b"
integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.10"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz#1cad974c8478e644c5cbce2a4b738137bb64bd4f"
@@ -46,6 +257,14 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.28":
version "0.3.29"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc"
integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@js-joda/core@3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273"
@@ -56,6 +275,18 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==
"@r2wc/core@^1.0.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@r2wc/core/-/core-1.2.0.tgz#a86cd2f732f499a4de8fa23b037053323f3749ed"
integrity sha512-vAfiuS5KywtV54SRzc4maEHcpdgeUyJzln+ATpNCOkO+ArIuOkTXd92b5YauVAd0A8B2rV/y9OeVW19vb73bUQ==
"@r2wc/react-to-web-component@2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@r2wc/react-to-web-component/-/react-to-web-component-2.0.4.tgz#a2b001b938ad4c3af8a64f520a73eb6028d60304"
integrity sha512-g1dtTTEGETNUimYldTW+2hxY3mmJZjzPEca0vqCutUht2GHmpK9mT5r/urmEI7uSbOkn6HaymosgVy26lvU1JQ==
dependencies:
"@r2wc/core" "^1.0.0"
"@socket.io/component-emitter@~3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
@@ -178,6 +409,11 @@
dependencies:
undici-types "~7.8.0"
"@types/parse-json@^4.0.0":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
"@types/qs@*":
version "6.14.0"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5"
@@ -474,6 +710,15 @@ array-flatten@1.1.1:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==
dependencies:
"@babel/runtime" "^7.12.5"
cosmiconfig "^7.0.0"
resolve "^1.19.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -583,6 +828,11 @@ call-bound@^1.0.2, call-bound@^1.0.3:
call-bind-apply-helpers "^1.0.2"
get-intrinsic "^1.3.0"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camelcase@^6.0.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
@@ -718,6 +968,11 @@ content-type@~1.0.4, content-type@~1.0.5:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
convert-source-map@^1.5.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -746,6 +1001,17 @@ cors@~2.8.5:
object-assign "^4"
vary "^1"
cosmiconfig@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
dependencies:
"@types/parse-json" "^4.0.0"
import-fresh "^3.2.1"
parse-json "^5.0.0"
path-type "^4.0.0"
yaml "^1.10.0"
cross-spawn@^7.0.3:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
@@ -755,6 +1021,11 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@@ -772,7 +1043,7 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@^4.1.0, debug@^4.3.4, debug@^4.3.5:
debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.3.5:
version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
@@ -927,6 +1198,13 @@ envinfo@^7.7.3:
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.14.0.tgz#26dac5db54418f2a4c1159153a0b2ae980838aae"
integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
dependencies:
is-arrayish "^0.2.1"
es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
@@ -1121,6 +1399,11 @@ finalhandler@1.3.1:
statuses "2.0.1"
unpipe "~1.0.0"
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
find-up@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@@ -1309,6 +1592,13 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hoist-non-react-statics@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
hpack.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
@@ -1394,6 +1684,14 @@ iconv-lite@^0.6.3:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
import-fresh@^3.2.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-local@^3.0.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260"
@@ -1435,6 +1733,11 @@ ipaddr.js@^2.0.1:
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -1549,6 +1852,11 @@ jest-worker@^27.4.5:
merge-stream "^2.0.0"
supports-color "^8.0.0"
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
@@ -1556,7 +1864,12 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
json-parse-even-better-errors@^2.3.1:
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
@@ -1658,6 +1971,11 @@ launch-editor@^2.6.0:
picocolors "^1.0.0"
shell-quote "^1.8.1"
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
loader-runner@^4.2.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
@@ -1701,6 +2019,13 @@ log4js@^6.4.1:
rfdc "^1.3.0"
streamroller "^3.1.5"
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
@@ -1990,6 +2315,23 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
dependencies:
callsites "^3.0.0"
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
dependencies:
"@babel/code-frame" "^7.0.0"
error-ex "^1.3.1"
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -2020,6 +2362,11 @@ path-to-regexp@0.1.12:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
picocolors@^1.0.0, picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@@ -2094,6 +2441,41 @@ raw-body@2.5.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-dom@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
react@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
dependencies:
loose-envify "^1.1.0"
readable-stream@^2.0.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
@@ -2152,12 +2534,17 @@ resolve-cwd@^3.0.0:
dependencies:
resolve-from "^5.0.0"
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-from@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve@^1.20.0:
resolve@^1.19.0, resolve@^1.20.0:
version "1.22.10"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
@@ -2207,6 +2594,13 @@ safe-regex-test@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
scheduler@^0.23.0, scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies:
loose-envify "^1.1.0"
schema-utils@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
@@ -2426,6 +2820,11 @@ source-map-support@0.5.21, source-map-support@~0.5.20:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
source-map@^0.6.0, source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
@@ -2513,6 +2912,11 @@ strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -2836,6 +3240,11 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@8.18.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
ws@^8.13.0:
version "8.18.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
@@ -2851,6 +3260,11 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^20.2.2, yargs-parser@^20.2.9:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+4 -4
View File
@@ -30,18 +30,18 @@ kotlin {
implementation(libs.exposed.core)
implementation(libs.exposed.dao)
implementation(libs.exposed.jdbc)
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:0.44.1")
implementation(libs.postgresql.driver)
}
jsMain.dependencies {
// Kotlin React dependencies with explicit versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:${libs.versions.kotlinWrappers.get()}")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:${libs.versions.kotlinWrappers.get()}")
// Kotlin React dependencies with explicit stable versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:11.10.5-pre.467")
// NPM dependencies
implementation(npm("react", "18.2.0"))
implementation(npm("react-dom", "18.2.0"))
implementation(npm("react-to-web-component", "2.0.2"))
}
}
}
@@ -1,6 +1,7 @@
package at.mocode.masterdata.infrastructure.api
import at.mocode.dto.base.BaseDto
import at.mocode.dto.base.ApiResponse
import at.mocode.masterdata.application.usecase.CreateCountryUseCase
import at.mocode.masterdata.application.usecase.GetCountryUseCase
import at.mocode.masterdata.domain.model.LandDefinition
@@ -90,9 +91,9 @@ class CountryController(
val orderBySortierung = call.request.queryParameters["orderBySortierung"]?.toBoolean() ?: true
val countries = getCountryUseCase.getAllActive(orderBySortierung)
val countryDtos = countries.map { it.toDto() }
call.respond(HttpStatusCode.OK, BaseDto.success(countryDtos))
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<List<CountryDto>>("Failed to retrieve countries: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to retrieve countries: ${e.message}"))
}
}
@@ -100,16 +101,16 @@ class CountryController(
get("/{id}") {
try {
val countryId = call.parameters["id"]?.let { uuidFrom(it) }
?: return@get call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>("Invalid country ID"))
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Invalid country ID"))
val country = getCountryUseCase.getById(countryId)
if (country != null) {
call.respond(HttpStatusCode.OK, BaseDto.success(country.toDto()))
call.respond(HttpStatusCode.OK, ApiResponse.success(country.toDto()))
} else {
call.respond(HttpStatusCode.NotFound, BaseDto.error<CountryDto>("Country not found"))
call.respond(HttpStatusCode.NotFound, ApiResponse.error<CountryDto>("Country not found"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<CountryDto>("Failed to retrieve country: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to retrieve country: ${e.message}"))
}
}
@@ -117,18 +118,18 @@ class CountryController(
get("/iso2/{code}") {
try {
val isoCode = call.parameters["code"]
?: return@get call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>("ISO code is required"))
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("ISO code is required"))
val country = getCountryUseCase.getByIsoAlpha2Code(isoCode)
if (country != null) {
call.respond(HttpStatusCode.OK, BaseDto.success(country.toDto()))
call.respond(HttpStatusCode.OK, ApiResponse.success(country.toDto()))
} else {
call.respond(HttpStatusCode.NotFound, BaseDto.error<CountryDto>("Country not found"))
call.respond(HttpStatusCode.NotFound, ApiResponse.error<CountryDto>("Country not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>(e.message ?: "Invalid ISO code"))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>(e.message ?: "Invalid ISO code"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<CountryDto>("Failed to retrieve country: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to retrieve country: ${e.message}"))
}
}
@@ -136,18 +137,18 @@ class CountryController(
get("/iso3/{code}") {
try {
val isoCode = call.parameters["code"]
?: return@get call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>("ISO code is required"))
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("ISO code is required"))
val country = getCountryUseCase.getByIsoAlpha3Code(isoCode)
if (country != null) {
call.respond(HttpStatusCode.OK, BaseDto.success(country.toDto()))
call.respond(HttpStatusCode.OK, ApiResponse.success(country.toDto()))
} else {
call.respond(HttpStatusCode.NotFound, BaseDto.error<CountryDto>("Country not found"))
call.respond(HttpStatusCode.NotFound, ApiResponse.error<CountryDto>("Country not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>(e.message ?: "Invalid ISO code"))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>(e.message ?: "Invalid ISO code"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<CountryDto>("Failed to retrieve country: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to retrieve country: ${e.message}"))
}
}
@@ -155,17 +156,17 @@ class CountryController(
get("/search") {
try {
val searchTerm = call.request.queryParameters["q"]
?: return@get call.respond(HttpStatusCode.BadRequest, BaseDto.error<List<CountryDto>>("Search term 'q' is required"))
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<CountryDto>>("Search term 'q' is required"))
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 50
val countries = getCountryUseCase.searchByName(searchTerm, limit)
val countryDtos = countries.map { it.toDto() }
call.respond(HttpStatusCode.OK, BaseDto.success(countryDtos))
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<List<CountryDto>>(e.message ?: "Invalid search parameters"))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<CountryDto>>(e.message ?: "Invalid search parameters"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<List<CountryDto>>("Failed to search countries: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to search countries: ${e.message}"))
}
}
@@ -174,9 +175,9 @@ class CountryController(
try {
val countries = getCountryUseCase.getEuMembers()
val countryDtos = countries.map { it.toDto() }
call.respond(HttpStatusCode.OK, BaseDto.success(countryDtos))
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<List<CountryDto>>("Failed to retrieve EU countries: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to retrieve EU countries: ${e.message}"))
}
}
@@ -185,9 +186,9 @@ class CountryController(
try {
val countries = getCountryUseCase.getEwrMembers()
val countryDtos = countries.map { it.toDto() }
call.respond(HttpStatusCode.OK, BaseDto.success(countryDtos))
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<List<CountryDto>>("Failed to retrieve EWR countries: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to retrieve EWR countries: ${e.message}"))
}
}
@@ -209,13 +210,13 @@ class CountryController(
)
val result = createCountryUseCase.createCountry(request)
if (result.isValid) {
call.respond(HttpStatusCode.Created, BaseDto.success(result.data!!.toDto()))
if (result.success) {
call.respond(HttpStatusCode.Created, ApiResponse.success(result.country!!.toDto()))
} else {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>("Validation failed", result.errors))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Validation failed: ${result.errors.joinToString(", ")}"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<CountryDto>("Failed to create country: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to create country: ${e.message}"))
}
}
@@ -223,7 +224,7 @@ class CountryController(
put("/{id}") {
try {
val countryId = call.parameters["id"]?.let { uuidFrom(it) }
?: return@put call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>("Invalid country ID"))
?: return@put call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Invalid country ID"))
val updateDto = call.receive<UpdateCountryDto>()
val request = CreateCountryUseCase.UpdateCountryRequest(
@@ -241,13 +242,13 @@ class CountryController(
)
val result = createCountryUseCase.updateCountry(request)
if (result.isValid) {
call.respond(HttpStatusCode.OK, BaseDto.success(result.data!!.toDto()))
if (result.success) {
call.respond(HttpStatusCode.OK, ApiResponse.success(result.country!!.toDto()))
} else {
call.respond(HttpStatusCode.BadRequest, BaseDto.error<CountryDto>("Validation failed", result.errors))
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Validation failed: ${result.errors.joinToString(", ")}"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<CountryDto>("Failed to update country: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to update country: ${e.message}"))
}
}
@@ -255,16 +256,16 @@ class CountryController(
delete("/{id}") {
try {
val countryId = call.parameters["id"]?.let { uuidFrom(it) }
?: return@delete call.respond(HttpStatusCode.BadRequest, BaseDto.error<Unit>("Invalid country ID"))
?: return@delete call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Unit>("Invalid country ID"))
val result = createCountryUseCase.deleteCountry(countryId)
if (result.isValid) {
if (result.success) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.NotFound, BaseDto.error<Unit>("Country not found", result.errors))
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("Country not found: ${result.errors.joinToString(", ")}"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, BaseDto.error<Unit>("Failed to delete country: ${e.message}"))
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Unit>("Failed to delete country: ${e.message}"))
}
}
}
@@ -7,6 +7,7 @@ import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.SortOrder
/**
* PostgreSQL implementation of LandRepository using Exposed ORM.
@@ -50,9 +51,9 @@ class LandRepositoryImpl : LandRepository {
val query = LandTable.select { LandTable.isActive eq true }
return if (orderBySortierung) {
query.orderBy(LandTable.sortierReihenfolge, LandTable.nameGerman)
query.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC)
} else {
query.orderBy(LandTable.nameGerman)
query.orderBy(LandTable.nameGerman to SortOrder.ASC)
}.map { it.toLandDefinition() }
}
@@ -60,7 +61,7 @@ class LandRepositoryImpl : LandRepository {
return LandTable.select {
(LandTable.isActive eq true) and (LandTable.isEuMember eq true)
}
.orderBy(LandTable.sortierReihenfolge, LandTable.nameGerman)
.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC)
.map { it.toLandDefinition() }
}
@@ -68,7 +69,7 @@ class LandRepositoryImpl : LandRepository {
return LandTable.select {
(LandTable.isActive eq true) and (LandTable.isEwrMember eq true)
}
.orderBy(LandTable.sortierReihenfolge, LandTable.nameGerman)
.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC)
.map { it.toLandDefinition() }
}
@@ -81,39 +82,39 @@ class LandRepositoryImpl : LandRepository {
return if (existingRecord != null) {
// Update existing record
LandTable.update({ LandTable.id eq land.landId }) {
it[isoAlpha2Code] = land.isoAlpha2Code
it[isoAlpha3Code] = land.isoAlpha3Code
it[isoNumericCode] = land.isoNumerischerCode
it[nameGerman] = land.nameDeutsch
it[nameEnglish] = land.nameEnglisch
it[nameLocal] = land.nameEnglisch // Using English as local fallback
it[isActive] = land.istAktiv
it[isEuMember] = land.istEuMitglied ?: false
it[isEwrMember] = land.istEwrMitglied ?: false
it[sortierReihenfolge] = land.sortierReihenfolge ?: 999
it[flagIcon] = land.wappenUrl
it[updatedAt] = now
it[notes] = null // Could be extended later
it[LandTable.isoAlpha2Code] = land.isoAlpha2Code
it[LandTable.isoAlpha3Code] = land.isoAlpha3Code
it[LandTable.isoNumericCode] = land.isoNumerischerCode
it[LandTable.nameGerman] = land.nameDeutsch
it[LandTable.nameEnglish] = land.nameEnglisch
it[LandTable.nameLocal] = land.nameEnglisch // Using English as local fallback
it[LandTable.isActive] = land.istAktiv
it[LandTable.isEuMember] = land.istEuMitglied ?: false
it[LandTable.isEwrMember] = land.istEwrMitglied ?: false
it[LandTable.sortierReihenfolge] = land.sortierReihenfolge ?: 999
it[LandTable.flagIcon] = land.wappenUrl
it[LandTable.updatedAt] = now
it[LandTable.notes] = null // Could be extended later
}
land.copy(updatedAt = now)
} else {
// Insert new record
LandTable.insert {
it[id] = land.landId
it[isoAlpha2Code] = land.isoAlpha2Code
it[isoAlpha3Code] = land.isoAlpha3Code
it[isoNumericCode] = land.isoNumerischerCode
it[nameGerman] = land.nameDeutsch
it[nameEnglish] = land.nameEnglisch
it[nameLocal] = land.nameEnglisch // Using English as local fallback
it[isActive] = land.istAktiv
it[isEuMember] = land.istEuMitglied ?: false
it[isEwrMember] = land.istEwrMitglied ?: false
it[sortierReihenfolge] = land.sortierReihenfolge ?: 999
it[flagIcon] = land.wappenUrl
it[createdAt] = land.createdAt
it[updatedAt] = now
it[notes] = null
it[LandTable.id] = land.landId
it[LandTable.isoAlpha2Code] = land.isoAlpha2Code
it[LandTable.isoAlpha3Code] = land.isoAlpha3Code
it[LandTable.isoNumericCode] = land.isoNumerischerCode
it[LandTable.nameGerman] = land.nameDeutsch
it[LandTable.nameEnglish] = land.nameEnglisch
it[LandTable.nameLocal] = land.nameEnglisch // Using English as local fallback
it[LandTable.isActive] = land.istAktiv
it[LandTable.isEuMember] = land.istEuMitglied ?: false
it[LandTable.isEwrMember] = land.istEwrMitglied ?: false
it[LandTable.sortierReihenfolge] = land.sortierReihenfolge ?: 999
it[LandTable.flagIcon] = land.wappenUrl
it[LandTable.createdAt] = land.createdAt
it[LandTable.updatedAt] = now
it[LandTable.notes] = null
}
land.copy(updatedAt = now)
}
@@ -18,7 +18,7 @@ object LandTable : UUIDTable("land_definition") {
// Names
val nameGerman = varchar("name_german", 100)
val nameEnglish = varchar("name_english", 100)
val nameEnglish = varchar("name_english", 100).nullable()
val nameLocal = varchar("name_local", 100).nullable()
// Status and Membership
+3 -8
View File
@@ -18,10 +18,6 @@ kotlin {
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.datetime)
implementation(libs.uuid)
implementation(libs.exposed.core)
implementation(libs.exposed.dao)
implementation(libs.exposed.jdbc)
implementation(libs.exposed.kotlinDatetime)
}
commonTest.dependencies {
@@ -39,14 +35,13 @@ kotlin {
}
jsMain.dependencies {
// Kotlin React dependencies with explicit versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:${libs.versions.kotlinWrappers.get()}")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:${libs.versions.kotlinWrappers.get()}")
// Kotlin React dependencies with explicit stable versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:11.10.5-pre.467")
// NPM dependencies
implementation(npm("react", "18.2.0"))
implementation(npm("react-dom", "18.2.0"))
implementation(npm("react-to-web-component", "2.0.2"))
}
}
}
@@ -123,6 +123,10 @@ class CreateBerechtigungUseCase(
errors.add(ValidationError("aktion", "Aktion must not exceed 50 characters"))
}
return ValidationResult(errors)
return if (errors.isEmpty()) {
ValidationResult.Valid
} else {
ValidationResult.Invalid(errors)
}
}
}
@@ -1,20 +0,0 @@
package at.mocode.members.infrastructure.repository
import at.mocode.enums.BerechtigungE
import org.jetbrains.exposed.dao.id.UUIDTable
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
/**
* Database table definition for permissions (Berechtigungen).
*/
object BerechtigungTable : UUIDTable("berechtigung") {
val berechtigungTyp = enumerationByName("berechtigung_typ", 50, BerechtigungE::class)
val name = varchar("name", 100)
val beschreibung = text("beschreibung").nullable()
val ressource = varchar("ressource", 50)
val aktion = varchar("aktion", 50)
val istAktiv = bool("ist_aktiv").default(true)
val istSystemBerechtigung = bool("ist_system_berechtigung").default(false)
val createdAt = datetime("created_at")
val updatedAt = datetime("updated_at")
}
@@ -1,60 +0,0 @@
package at.mocode.members.infrastructure.repository
import at.mocode.enums.DatenQuelleE
import at.mocode.enums.GeschlechtE
import org.jetbrains.exposed.dao.id.UUIDTable
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
import org.jetbrains.exposed.sql.kotlin.datetime.date
/**
* Exposed table definition for Person entities.
*
* This table represents the database schema for storing person data
* in the member management bounded context.
*/
object PersonTable : UUIDTable("persons") {
// Basic person information
val oepsSatzNr = varchar("oeps_satz_nr", 6).nullable().uniqueIndex()
val nachname = varchar("nachname", 100)
val vorname = varchar("vorname", 100)
val titel = varchar("titel", 50).nullable()
// Personal details
val geburtsdatum = date("geburtsdatum").nullable()
val geschlecht = enumerationByName("geschlecht", 10, GeschlechtE::class).nullable()
val nationalitaetLandId = uuid("nationalitaet_land_id").nullable()
val feiId = varchar("fei_id", 20).nullable()
// Contact information
val telefon = varchar("telefon", 50).nullable()
val email = varchar("email", 100).nullable()
// Address information
val strasse = varchar("strasse", 200).nullable()
val plz = varchar("plz", 10).nullable()
val ort = varchar("ort", 100).nullable()
val adresszusatzZusatzinfo = varchar("adresszusatz_zusatzinfo", 200).nullable()
// Club membership
val stammVereinId = uuid("stamm_verein_id").nullable()
val mitgliedsNummerBeiStammVerein = varchar("mitglieds_nummer_bei_stamm_verein", 50).nullable()
// Status and restrictions
val istGesperrt = bool("ist_gesperrt").default(false)
val sperrGrund = varchar("sperr_grund", 500).nullable()
// OEPS specific data
val altersklasseOepsCodeRaw = varchar("altersklasse_oeps_code_raw", 10).nullable()
val istJungerReiterOepsFlag = bool("ist_junger_reiter_oeps_flag").default(false)
val kaderStatusOepsRaw = varchar("kader_status_oeps_raw", 10).nullable()
// Metadata
val datenQuelle = enumerationByName("daten_quelle", 20, DatenQuelleE::class).default(DatenQuelleE.MANUELL)
val istAktiv = bool("ist_aktiv").default(true)
val notizenIntern = text("notizen_intern").nullable()
// Audit fields
val createdAt = datetime("created_at")
val updatedAt = datetime("updated_at")
}
@@ -1,32 +0,0 @@
package at.mocode.members.infrastructure.repository
import at.mocode.enums.RolleE
import org.jetbrains.exposed.dao.id.UUIDTable
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
/**
* Exposed table definition for Rolle entities.
*
* This table represents the database schema for storing role data
* in the member management bounded context.
*/
object RolleTable : UUIDTable("rollen") {
// Role identification
val rolleTyp = enumerationByName("rolle_typ", 20, RolleE::class)
val name = varchar("name", 100)
val beschreibung = text("beschreibung").nullable()
// Status flags
val istAktiv = bool("ist_aktiv").default(true)
val istSystemRolle = bool("ist_system_rolle").default(false)
// Audit fields
val createdAt = datetime("created_at")
val updatedAt = datetime("updated_at")
// Unique constraint on rolle_typ to ensure each role type exists only once
init {
uniqueIndex(rolleTyp)
}
}
@@ -10,6 +10,12 @@ import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
// Import table definition and extension functions
import at.mocode.members.infrastructure.repository.BerechtigungTable
import at.mocode.members.infrastructure.repository.insertOrUpdate
import at.mocode.members.infrastructure.repository.toLocalDateTime
import at.mocode.members.infrastructure.repository.toInstant
/**
* Exposed-based implementation of BerechtigungRepository.
*
@@ -31,8 +37,8 @@ class BerechtigungRepositoryImpl : BerechtigungRepository {
it[aktion] = berechtigung.aktion
it[istAktiv] = berechtigung.istAktiv
it[istSystemBerechtigung] = berechtigung.istSystemBerechtigung
it[createdAt] = berechtigung.createdAt.toJavaInstant()
it[updatedAt] = updatedBerechtigung.updatedAt.toJavaInstant()
it[createdAt] = berechtigung.createdAt.toLocalDateTime()
it[updatedAt] = updatedBerechtigung.updatedAt.toLocalDateTime()
}
return updatedBerechtigung
@@ -80,7 +86,7 @@ class BerechtigungRepositoryImpl : BerechtigungRepository {
val now = Clock.System.now()
val updatedRows = BerechtigungTable.update({ BerechtigungTable.id eq berechtigungId }) {
it[istAktiv] = false
it[updatedAt] = now.toJavaInstant()
it[updatedAt] = now.toLocalDateTime()
}
return updatedRows > 0
}
@@ -114,8 +120,8 @@ class BerechtigungRepositoryImpl : BerechtigungRepository {
aktion = row[BerechtigungTable.aktion],
istAktiv = row[BerechtigungTable.istAktiv],
istSystemBerechtigung = row[BerechtigungTable.istSystemBerechtigung],
createdAt = row[BerechtigungTable.createdAt].toKotlinInstant(),
updatedAt = row[BerechtigungTable.updatedAt].toKotlinInstant()
createdAt = row[BerechtigungTable.createdAt].toInstant(),
updatedAt = row[BerechtigungTable.updatedAt].toInstant()
)
}
}
@@ -0,0 +1,57 @@
package at.mocode.members.infrastructure.repository
import kotlinx.datetime.*
import kotlinx.datetime.toJavaInstant as kotlinxToJavaInstant
import kotlinx.datetime.toKotlinInstant as javaToKotlinInstant
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement
import org.jetbrains.exposed.sql.transactions.TransactionManager
/**
* Extension function to convert Kotlin Instant to LocalDateTime for database storage.
*/
fun Instant.toLocalDateTime(): LocalDateTime = this.toLocalDateTime(TimeZone.UTC)
/**
* Extension function to convert LocalDateTime to Kotlin Instant.
*/
fun LocalDateTime.toInstant(): Instant = this.toInstant(TimeZone.UTC)
/**
* Extension function for upsert (insert or update) operation on tables.
* If a record with the given key exists, it updates it; otherwise, it inserts a new record.
*/
fun <T : Table> T.insertOrUpdate(
vararg keys: Column<*>,
body: T.(InsertStatement<Number>) -> Unit
) = InsertOrUpdate<Number>(this, keys = keys).apply {
body(this)
}.execute(this)
/**
* Custom InsertOrUpdate statement implementation for PostgreSQL.
*/
class InsertOrUpdate<Key : Any>(
table: Table,
isIgnore: Boolean = false,
private vararg val keys: Column<*>
) : InsertStatement<Key>(table, isIgnore) {
override fun prepareSQL(transaction: Transaction, prepared: Boolean): String {
val tm = TransactionManager.current()
val updateSetter = (table.columns - keys.toSet()).joinToString { "${tm.identity(it)} = EXCLUDED.${tm.identity(it)}" }
val keyColumns = keys.joinToString { tm.identity(it) }
val insertSQL = super.prepareSQL(transaction, prepared)
return "$insertSQL ON CONFLICT ($keyColumns) DO UPDATE SET $updateSetter"
}
}
/**
* Extension function to execute the InsertOrUpdate statement.
*/
fun InsertOrUpdate<*>.execute(table: Table): InsertOrUpdate<*> {
TransactionManager.current().exec(this)
return this
}
@@ -10,6 +10,12 @@ import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
// Import table definition and extension functions
import at.mocode.members.infrastructure.repository.PersonTable
import at.mocode.members.infrastructure.repository.insertOrUpdate
import at.mocode.members.infrastructure.repository.toLocalDateTime
import at.mocode.members.infrastructure.repository.toInstant
/**
* Exposed-based implementation of PersonRepository.
*
@@ -81,8 +87,8 @@ class PersonRepositoryImpl : PersonRepository {
it[datenQuelle] = person.datenQuelle
it[istAktiv] = person.istAktiv
it[notizenIntern] = person.notizenIntern
it[createdAt] = person.createdAt.toJavaInstant()
it[updatedAt] = updatedPerson.updatedAt.toJavaInstant()
it[createdAt] = person.createdAt.toLocalDateTime()
it[updatedAt] = updatedPerson.updatedAt.toLocalDateTime()
}
return updatedPerson
@@ -133,8 +139,8 @@ class PersonRepositoryImpl : PersonRepository {
datenQuelle = row[PersonTable.datenQuelle],
istAktiv = row[PersonTable.istAktiv],
notizenIntern = row[PersonTable.notizenIntern],
createdAt = row[PersonTable.createdAt].toKotlinInstant(),
updatedAt = row[PersonTable.updatedAt].toKotlinInstant()
createdAt = row[PersonTable.createdAt].toInstant(),
updatedAt = row[PersonTable.updatedAt].toInstant()
)
}
}
@@ -9,6 +9,12 @@ import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
// Import table definition and extension functions
import at.mocode.members.infrastructure.repository.VereinTable
import at.mocode.members.infrastructure.repository.insertOrUpdate
import at.mocode.members.infrastructure.repository.toLocalDateTime
import at.mocode.members.infrastructure.repository.toInstant
/**
* Exposed-based implementation of VereinRepository.
*
@@ -85,8 +91,8 @@ class VereinRepositoryImpl : VereinRepository {
it[datenQuelle] = verein.datenQuelle
it[istAktiv] = verein.istAktiv
it[notizenIntern] = verein.notizenIntern
it[createdAt] = verein.createdAt.toJavaInstant()
it[updatedAt] = updatedVerein.updatedAt.toJavaInstant()
it[createdAt] = verein.createdAt.toLocalDateTime()
it[updatedAt] = updatedVerein.updatedAt.toLocalDateTime()
}
return updatedVerein
@@ -134,8 +140,8 @@ class VereinRepositoryImpl : VereinRepository {
datenQuelle = row[VereinTable.datenQuelle],
istAktiv = row[VereinTable.istAktiv],
notizenIntern = row[VereinTable.notizenIntern],
createdAt = row[VereinTable.createdAt].toKotlinInstant(),
updatedAt = row[VereinTable.updatedAt].toKotlinInstant()
createdAt = row[VereinTable.createdAt].toInstant(),
updatedAt = row[VereinTable.updatedAt].toInstant()
)
}
}
+3 -4
View File
@@ -23,14 +23,13 @@ kotlin {
}
jsMain.dependencies {
// Kotlin React dependencies with explicit versions (for shared components)
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:${libs.versions.kotlinWrappers.get()}")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:${libs.versions.kotlinWrappers.get()}")
// Kotlin React dependencies with explicit stable versions (for shared components)
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:11.10.5-pre.467")
// NPM dependencies
implementation(npm("react", "18.2.0"))
implementation(npm("react-dom", "18.2.0"))
implementation(npm("react-to-web-component", "2.0.2"))
}
}
}