From e76db7e924de77fa825b7423a14dc810bc31a206 Mon Sep 17 00:00:00 2001 From: stefan Date: Sat, 19 Jul 2025 11:26:09 +0200 Subject: [PATCH] (fix) Umbau zu SCS --- composeApp/build.gradle.kts | 1 + .../kotlin/at/mocode/di/AppDependencies.kt | 88 +++- .../mocode/ui/screens/CreatePersonScreen.kt | 4 +- .../ui/viewmodel/PersonListViewModel.kt | 2 +- .../ui/viewmodel/CreatePersonViewModelTest.kt | 94 +++- .../ui/viewmodel/PersonListViewModelTest.kt | 33 +- event-management/build.gradle.kts | 15 +- .../api/VeranstaltungController.kt | 0 event-management/src/jsMain/kotlin/Main.kt | 24 + .../ui/components/VeranstaltungsListe.kt | 176 ++++++++ .../events/ui/{ => utils}/EventComponent.kt | 9 +- .../repository/VeranstaltungRepositoryImpl.kt | 1 + .../repository/VeranstaltungTable.kt | 0 gradle.properties | 3 + gradle/libs.versions.toml | 8 +- horse-registry/build.gradle.kts | 7 +- .../infrastructure/api/HorseController.kt | 93 ++-- .../repository/HorseRepositoryImpl.kt | 84 ++-- .../infrastructure/repository/HorseTable.kt | 0 kotlin-js-store/yarn.lock | 420 +++++++++++++++++- master-data/build.gradle.kts | 8 +- .../infrastructure/api/CountryController.kt | 75 ++-- .../repository/LandRepositoryImpl.kt | 65 +-- .../infrastructure/repository/LandTable.kt | 2 +- member-management/build.gradle.kts | 11 +- .../usecase/CreateBerechtigungUseCase.kt | 6 +- .../repository/BerechtigungTable.kt | 20 - .../infrastructure/repository/PersonTable.kt | 60 --- .../infrastructure/repository/RolleTable.kt | 32 -- .../repository/BerechtigungRepositoryImpl.kt | 16 +- .../repository/DatabaseExtensions.kt | 57 +++ .../repository/PersonRepositoryImpl.kt | 14 +- .../repository/PersonRolleRepositoryImpl.kt | 0 .../RolleBerechtigungRepositoryImpl.kt | 0 .../repository/RolleRepositoryImpl.kt | 0 .../repository/UserRepositoryImpl.kt | 0 .../repository/VereinRepositoryImpl.kt | 14 +- .../infrastructure/repository/VereinTable.kt | 0 shared-kernel/build.gradle.kts | 7 +- 39 files changed, 1107 insertions(+), 342 deletions(-) delete mode 100644 event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/api/VeranstaltungController.kt create mode 100644 event-management/src/jsMain/kotlin/Main.kt create mode 100644 event-management/src/jsMain/kotlin/at/mocode/events/ui/components/VeranstaltungsListe.kt rename event-management/src/jsMain/kotlin/at/mocode/events/ui/{ => utils}/EventComponent.kt (86%) rename event-management/src/{commonMain => jvmMain}/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungRepositoryImpl.kt (99%) rename event-management/src/{commonMain => jvmMain}/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungTable.kt (100%) rename horse-registry/src/{commonMain => jvmMain}/kotlin/at/mocode/horses/infrastructure/api/HorseController.kt (74%) rename horse-registry/src/{commonMain => jvmMain}/kotlin/at/mocode/horses/infrastructure/repository/HorseRepositoryImpl.kt (76%) rename horse-registry/src/{commonMain => jvmMain}/kotlin/at/mocode/horses/infrastructure/repository/HorseTable.kt (100%) rename master-data/src/{commonMain => jvmMain}/kotlin/at/mocode/masterdata/infrastructure/api/CountryController.kt (76%) rename master-data/src/{commonMain => jvmMain}/kotlin/at/mocode/masterdata/infrastructure/repository/LandRepositoryImpl.kt (67%) rename master-data/src/{commonMain => jvmMain}/kotlin/at/mocode/masterdata/infrastructure/repository/LandTable.kt (95%) delete mode 100644 member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungTable.kt delete mode 100644 member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonTable.kt delete mode 100644 member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleTable.kt rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/BerechtigungRepositoryImpl.kt (87%) create mode 100644 member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/DatabaseExtensions.kt rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/PersonRepositoryImpl.kt (90%) rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/PersonRolleRepositoryImpl.kt (100%) rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/RolleBerechtigungRepositoryImpl.kt (100%) rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/RolleRepositoryImpl.kt (100%) rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/UserRepositoryImpl.kt (100%) rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/VereinRepositoryImpl.kt (89%) rename member-management/src/{commonMain => jvmMain}/kotlin/at/mocode/members/infrastructure/repository/VereinTable.kt (100%) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 69a1cf2f..f0a9bee8 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -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) diff --git a/composeApp/src/commonMain/kotlin/at/mocode/di/AppDependencies.kt b/composeApp/src/commonMain/kotlin/at/mocode/di/AppDependencies.kt index 147cd67b..22153f2a 100644 --- a/composeApp/src/commonMain/kotlin/at/mocode/di/AppDependencies.kt +++ b/composeApp/src/commonMain/kotlin/at/mocode/di/AppDependencies.kt @@ -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 { + return emptyList() // Mock implementation + } + + override suspend fun findByName(searchTerm: String, limit: Int): List { + return emptyList() // Mock implementation + } + + override suspend fun findAllActive(limit: Int, offset: Int): List { + 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 { - 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 { + override suspend fun findByName(searchTerm: String, limit: Int): List { return emptyList() // Mock implementation } + + override suspend fun findByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): List { + return emptyList() // Mock implementation + } + + override suspend fun findByLandId(landId: com.benasher44.uuid.Uuid): List { + return emptyList() // Mock implementation + } + + override suspend fun findAllActive(limit: Int, offset: Int): List { + return emptyList() // Mock implementation + } + + override suspend fun findByLocation(searchTerm: String, limit: Int): List { + 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 { + return emptyList() // Mock implementation + } + + override suspend fun getStatesByCountry(countryId: com.benasher44.uuid.Uuid): List { + return emptyList() // Mock implementation + } } // Use case instances diff --git a/composeApp/src/commonMain/kotlin/at/mocode/ui/screens/CreatePersonScreen.kt b/composeApp/src/commonMain/kotlin/at/mocode/ui/screens/CreatePersonScreen.kt index 8ab2191a..53336cf1 100644 --- a/composeApp/src/commonMain/kotlin/at/mocode/ui/screens/CreatePersonScreen.kt +++ b/composeApp/src/commonMain/kotlin/at/mocode/ui/screens/CreatePersonScreen.kt @@ -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( diff --git a/composeApp/src/commonMain/kotlin/at/mocode/ui/viewmodel/PersonListViewModel.kt b/composeApp/src/commonMain/kotlin/at/mocode/ui/viewmodel/PersonListViewModel.kt index 6c80b5b0..3ae6ae3e 100644 --- a/composeApp/src/commonMain/kotlin/at/mocode/ui/viewmodel/PersonListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/at/mocode/ui/viewmodel/PersonListViewModel.kt @@ -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 { diff --git a/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/CreatePersonViewModelTest.kt b/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/CreatePersonViewModelTest.kt index 4d617260..68314c5e 100644 --- a/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/CreatePersonViewModelTest.kt +++ b/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/CreatePersonViewModelTest.kt @@ -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() 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 { + return persons.filter { it.stammVereinId == vereinId } + } + + override suspend fun findByName(searchTerm: String, limit: Int): List { + 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 { + 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 { - 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 { + return emptyList() + } + + override suspend fun findByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): List { + return emptyList() + } + + override suspend fun findByLandId(landId: com.benasher44.uuid.Uuid): List { + return emptyList() + } + + override suspend fun findAllActive(limit: Int, offset: Int): List { + return emptyList() + } + + override suspend fun findByLocation(searchTerm: String, limit: Int): List { + 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 { - 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 { + return emptyList() + } + + override suspend fun getStatesByCountry(countryId: com.benasher44.uuid.Uuid): List { + return emptyList() + } } createPersonUseCase = CreatePersonUseCase( diff --git a/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/PersonListViewModelTest.kt b/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/PersonListViewModelTest.kt index 72b1816b..c9e56408 100644 --- a/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/PersonListViewModelTest.kt +++ b/composeApp/src/commonTest/kotlin/at/mocode/ui/viewmodel/PersonListViewModelTest.kt @@ -26,29 +26,44 @@ class PersonListViewModelTest { private val persons = mutableListOf() 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 { + return persons.filter { it.stammVereinId == vereinId } + } + + override suspend fun findByName(searchTerm: String, limit: Int): List { + 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 { + 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 { - 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, diff --git a/event-management/build.gradle.kts b/event-management/build.gradle.kts index ccf77bfb..ba328613 100644 --- a/event-management/build.gradle.kts +++ b/event-management/build.gradle.kts @@ -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")) } } } diff --git a/event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/api/VeranstaltungController.kt b/event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/api/VeranstaltungController.kt deleted file mode 100644 index e69de29b..00000000 diff --git a/event-management/src/jsMain/kotlin/Main.kt b/event-management/src/jsMain/kotlin/Main.kt new file mode 100644 index 00000000..9737f762 --- /dev/null +++ b/event-management/src/jsMain/kotlin/Main.kt @@ -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 in your HTML") +} diff --git a/event-management/src/jsMain/kotlin/at/mocode/events/ui/components/VeranstaltungsListe.kt b/event-management/src/jsMain/kotlin/at/mocode/events/ui/components/VeranstaltungsListe.kt new file mode 100644 index 00000000..95b1f3fa --- /dev/null +++ b/event-management/src/jsMain/kotlin/at/mocode/events/ui/components/VeranstaltungsListe.kt @@ -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 { _ -> + // State management with useState + var events by useState>(emptyList()) + var loading by useState(true) + var error by useState(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 = 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() + 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" + } + } + } + } + } + } + } + } +} diff --git a/event-management/src/jsMain/kotlin/at/mocode/events/ui/EventComponent.kt b/event-management/src/jsMain/kotlin/at/mocode/events/ui/utils/EventComponent.kt similarity index 86% rename from event-management/src/jsMain/kotlin/at/mocode/events/ui/EventComponent.kt rename to event-management/src/jsMain/kotlin/at/mocode/events/ui/utils/EventComponent.kt index 6fbfa58f..1bad600b 100644 --- a/event-management/src/jsMain/kotlin/at/mocode/events/ui/EventComponent.kt +++ b/event-management/src/jsMain/kotlin/at/mocode/events/ui/utils/EventComponent.kt @@ -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") -} diff --git a/event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungRepositoryImpl.kt b/event-management/src/jvmMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungRepositoryImpl.kt similarity index 99% rename from event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungRepositoryImpl.kt rename to event-management/src/jvmMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungRepositoryImpl.kt index 9a8297a4..7f7780d2 100644 --- a/event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungRepositoryImpl.kt +++ b/event-management/src/jvmMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungRepositoryImpl.kt @@ -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. diff --git a/event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungTable.kt b/event-management/src/jvmMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungTable.kt similarity index 100% rename from event-management/src/commonMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungTable.kt rename to event-management/src/jvmMain/kotlin/at/mocode/events/infrastructure/repository/VeranstaltungTable.kt diff --git a/gradle.properties b/gradle.properties index bd7d5b3f..3d1e1b88 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,3 +41,6 @@ org.gradle.vfs.watch=true # Build-Reports minimieren fr sauberen Build-Process org.gradle.logging.level=lifecycle kotlin.build.report.single_file=false + +# Compose Experimental Features +org.jetbrains.compose.experimental.jscanvas.enabled=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fc663c69..3942ad5f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/horse-registry/build.gradle.kts b/horse-registry/build.gradle.kts index 565cb125..e2b9681d 100644 --- a/horse-registry/build.gradle.kts +++ b/horse-registry/build.gradle.kts @@ -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")) } } } diff --git a/horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/api/HorseController.kt b/horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/api/HorseController.kt similarity index 74% rename from horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/api/HorseController.kt rename to horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/api/HorseController.kt index 1507bd89..7cb781c4 100644 --- a/horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/api/HorseController.kt +++ b/horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/api/HorseController.kt @@ -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("Failed to retrieve horses: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Horse not found")) + call.respond(HttpStatusCode.NotFound, ApiResponse.error("Horse not found")) } } catch (e: IllegalArgumentException) { - call.respond(HttpStatusCode.BadRequest, BaseDto.error("Invalid horse ID format")) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Invalid horse ID format")) } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to retrieve horse: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Horse with life number '$lebensnummer' not found")) + call.respond(HttpStatusCode.NotFound, ApiResponse.error("Horse with life number '$lebensnummer' not found")) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to search horse: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Horse with chip number '$chipNummer' not found")) + call.respond(HttpStatusCode.NotFound, ApiResponse.error("Horse with chip number '$chipNummer' not found")) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to search horse: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Failed to retrieve OEPS horses: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Failed to retrieve FEI horses: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Failed to retrieve statistics: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Validation failed", response.errors)) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Validation failed")) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to create horse: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Update failed", response.errors)) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Update failed: ${response.errors.joinToString(", ")}")) } } catch (e: IllegalArgumentException) { - call.respond(HttpStatusCode.BadRequest, BaseDto.error("Invalid horse ID format")) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Invalid horse ID format")) } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to update horse: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Delete failed", response.errors)) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Delete failed: ${response.errors.joinToString(", ")}")) } } catch (e: IllegalArgumentException) { - call.respond(HttpStatusCode.BadRequest, BaseDto.error("Invalid horse ID format")) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Invalid horse ID format")) } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to delete horse: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Soft delete failed", response.errors)) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Soft delete failed: ${response.errors.joinToString(", ")}")) } } catch (e: IllegalArgumentException) { - call.respond(HttpStatusCode.BadRequest, BaseDto.error("Invalid horse ID format")) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Invalid horse ID format")) } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to soft delete horse: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Failed to batch delete horses: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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, + val horseIds: List<@Contextual Uuid>, val forceDelete: Boolean = false ) diff --git a/horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/repository/HorseRepositoryImpl.kt b/horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/repository/HorseRepositoryImpl.kt similarity index 76% rename from horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/repository/HorseRepositoryImpl.kt rename to horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/repository/HorseRepositoryImpl.kt index 2cbe16b1..1282f8e2 100644 --- a/horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/repository/HorseRepositoryImpl.kt +++ b/horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/repository/HorseRepositoryImpl.kt @@ -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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 } diff --git a/horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/repository/HorseTable.kt b/horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/repository/HorseTable.kt similarity index 100% rename from horse-registry/src/commonMain/kotlin/at/mocode/horses/infrastructure/repository/HorseTable.kt rename to horse-registry/src/jvmMain/kotlin/at/mocode/horses/infrastructure/repository/HorseTable.kt diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 511c851a..746aef4d 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -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" diff --git a/master-data/build.gradle.kts b/master-data/build.gradle.kts index 63df6b38..6e8fe5ea 100644 --- a/master-data/build.gradle.kts +++ b/master-data/build.gradle.kts @@ -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")) } } } diff --git a/master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/api/CountryController.kt b/master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/api/CountryController.kt similarity index 76% rename from master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/api/CountryController.kt rename to master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/api/CountryController.kt index 48a79ae0..55c9fbf0 100644 --- a/master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/api/CountryController.kt +++ b/master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/api/CountryController.kt @@ -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>("Failed to retrieve countries: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error>("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("Invalid country ID")) + ?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error("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("Country not found")) + call.respond(HttpStatusCode.NotFound, ApiResponse.error("Country not found")) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to retrieve country: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("ISO code is required")) + ?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error("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("Country not found")) + call.respond(HttpStatusCode.NotFound, ApiResponse.error("Country not found")) } } catch (e: IllegalArgumentException) { - call.respond(HttpStatusCode.BadRequest, BaseDto.error(e.message ?: "Invalid ISO code")) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error(e.message ?: "Invalid ISO code")) } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to retrieve country: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("ISO code is required")) + ?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error("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("Country not found")) + call.respond(HttpStatusCode.NotFound, ApiResponse.error("Country not found")) } } catch (e: IllegalArgumentException) { - call.respond(HttpStatusCode.BadRequest, BaseDto.error(e.message ?: "Invalid ISO code")) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error(e.message ?: "Invalid ISO code")) } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to retrieve country: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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>("Search term 'q' is required")) + ?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error>("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>(e.message ?: "Invalid search parameters")) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error>(e.message ?: "Invalid search parameters")) } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error>("Failed to search countries: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error>("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>("Failed to retrieve EU countries: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error>("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>("Failed to retrieve EWR countries: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error>("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("Validation failed", result.errors)) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Validation failed: ${result.errors.joinToString(", ")}")) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to create country: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Invalid country ID")) + ?: return@put call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Invalid country ID")) val updateDto = call.receive() 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("Validation failed", result.errors)) + call.respond(HttpStatusCode.BadRequest, ApiResponse.error("Validation failed: ${result.errors.joinToString(", ")}")) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to update country: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("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("Invalid country ID")) + ?: return@delete call.respond(HttpStatusCode.BadRequest, ApiResponse.error("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("Country not found", result.errors)) + call.respond(HttpStatusCode.NotFound, ApiResponse.error("Country not found: ${result.errors.joinToString(", ")}")) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, BaseDto.error("Failed to delete country: ${e.message}")) + call.respond(HttpStatusCode.InternalServerError, ApiResponse.error("Failed to delete country: ${e.message}")) } } } diff --git a/master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandRepositoryImpl.kt b/master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandRepositoryImpl.kt similarity index 67% rename from master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandRepositoryImpl.kt rename to master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandRepositoryImpl.kt index 7a9eb7f0..6c294f48 100644 --- a/master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandRepositoryImpl.kt +++ b/master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandRepositoryImpl.kt @@ -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) } diff --git a/master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandTable.kt b/master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandTable.kt similarity index 95% rename from master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandTable.kt rename to master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandTable.kt index de997f71..5ff4da35 100644 --- a/master-data/src/commonMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandTable.kt +++ b/master-data/src/jvmMain/kotlin/at/mocode/masterdata/infrastructure/repository/LandTable.kt @@ -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 diff --git a/member-management/build.gradle.kts b/member-management/build.gradle.kts index 276472b5..7f55f3ba 100644 --- a/member-management/build.gradle.kts +++ b/member-management/build.gradle.kts @@ -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")) } } } diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/application/usecase/CreateBerechtigungUseCase.kt b/member-management/src/commonMain/kotlin/at/mocode/members/application/usecase/CreateBerechtigungUseCase.kt index ec5bc3cc..fad89b32 100644 --- a/member-management/src/commonMain/kotlin/at/mocode/members/application/usecase/CreateBerechtigungUseCase.kt +++ b/member-management/src/commonMain/kotlin/at/mocode/members/application/usecase/CreateBerechtigungUseCase.kt @@ -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) + } } } diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungTable.kt b/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungTable.kt deleted file mode 100644 index a0829d66..00000000 --- a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungTable.kt +++ /dev/null @@ -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") -} diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonTable.kt b/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonTable.kt deleted file mode 100644 index bbe99c9f..00000000 --- a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonTable.kt +++ /dev/null @@ -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") -} diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleTable.kt b/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleTable.kt deleted file mode 100644 index c20e1470..00000000 --- a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleTable.kt +++ /dev/null @@ -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) - } -} diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungRepositoryImpl.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungRepositoryImpl.kt similarity index 87% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungRepositoryImpl.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungRepositoryImpl.kt index cb050106..b2ea6179 100644 --- a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungRepositoryImpl.kt +++ b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/BerechtigungRepositoryImpl.kt @@ -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() ) } } diff --git a/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/DatabaseExtensions.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/DatabaseExtensions.kt new file mode 100644 index 00000000..d2f95d71 --- /dev/null +++ b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/DatabaseExtensions.kt @@ -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.insertOrUpdate( + vararg keys: Column<*>, + body: T.(InsertStatement) -> Unit +) = InsertOrUpdate(this, keys = keys).apply { + body(this) +}.execute(this) + +/** + * Custom InsertOrUpdate statement implementation for PostgreSQL. + */ +class InsertOrUpdate( + table: Table, + isIgnore: Boolean = false, + private vararg val keys: Column<*> +) : InsertStatement(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 +} diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonRepositoryImpl.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/PersonRepositoryImpl.kt similarity index 90% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonRepositoryImpl.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/PersonRepositoryImpl.kt index 6cbb5486..a9d80a8b 100644 --- a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonRepositoryImpl.kt +++ b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/PersonRepositoryImpl.kt @@ -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() ) } } diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonRolleRepositoryImpl.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/PersonRolleRepositoryImpl.kt similarity index 100% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/PersonRolleRepositoryImpl.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/PersonRolleRepositoryImpl.kt diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleBerechtigungRepositoryImpl.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/RolleBerechtigungRepositoryImpl.kt similarity index 100% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleBerechtigungRepositoryImpl.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/RolleBerechtigungRepositoryImpl.kt diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleRepositoryImpl.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/RolleRepositoryImpl.kt similarity index 100% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/RolleRepositoryImpl.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/RolleRepositoryImpl.kt diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/UserRepositoryImpl.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/UserRepositoryImpl.kt similarity index 100% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/UserRepositoryImpl.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/UserRepositoryImpl.kt diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/VereinRepositoryImpl.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/VereinRepositoryImpl.kt similarity index 89% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/VereinRepositoryImpl.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/VereinRepositoryImpl.kt index c3d9035d..7b3a6532 100644 --- a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/VereinRepositoryImpl.kt +++ b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/VereinRepositoryImpl.kt @@ -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() ) } } diff --git a/member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/VereinTable.kt b/member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/VereinTable.kt similarity index 100% rename from member-management/src/commonMain/kotlin/at/mocode/members/infrastructure/repository/VereinTable.kt rename to member-management/src/jvmMain/kotlin/at/mocode/members/infrastructure/repository/VereinTable.kt diff --git a/shared-kernel/build.gradle.kts b/shared-kernel/build.gradle.kts index 512ce647..df9285fb 100644 --- a/shared-kernel/build.gradle.kts +++ b/shared-kernel/build.gradle.kts @@ -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")) } } }