fix(compose): Change server host port mapping to 8081 to avoid local conflict
This commit is contained in:
parent
c175e53646
commit
2ba54b4e11
32
.editorconfig
Normal file
32
.editorconfig
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# editorconfig.org
|
||||
# Hilft, konsistente Code-Stile über verschiedene Editoren/IDEs hinweg beizubehalten.
|
||||
|
||||
# Markiert dies als Root-Konfigurationsdatei
|
||||
root = true
|
||||
|
||||
[*] # Einstellungen für ALLE Dateien
|
||||
# Zeichensatz
|
||||
charset = utf-8
|
||||
# Zeilenende (Unix-Style)
|
||||
end_of_line = lf
|
||||
# Fügt eine leere Zeile am Dateiende ein (gute Praxis)
|
||||
insert_final_newline = true
|
||||
# Entfernt Leerzeichen am Zeilenende
|
||||
trim_trailing_whitespace = true
|
||||
# Einrückungsstil: Leerzeichen (statt Tabs)
|
||||
indent_style = space
|
||||
# Einrückungsgröße: 4 Leerzeichen (Standard für Kotlin/Java)
|
||||
indent_size = 4
|
||||
|
||||
# Spezifisch für Kotlin und Kotlin Script (.gradle.kts) Dateien
|
||||
# Erbt die Einstellungen von [*] - 4 Leerzeichen passen zur Kotlin-Konvention.
|
||||
# max_line_length = 120 # Könnte man hinzufügen, aber oft besser durch Linter/Formatter geregelt
|
||||
|
||||
[*.xml] # Spezifisch für XML-Dateien
|
||||
indent_size = 4 # Oft auch 4 Leerzeichen
|
||||
|
||||
[*.{yml,yaml}] # Spezifisch für YAML-Dateien
|
||||
indent_size = 2 # Hier sind 2 Leerzeichen eine häufige Konvention
|
||||
|
||||
# [*.md] # Beispiel für Markdown, falls benötigt
|
||||
# trim_trailing_whitespace = false # Bei Markdown oft sinnvoll, das Trimmen auszuschalten
|
||||
213
analysis.md
213
analysis.md
|
|
@ -1,103 +1,134 @@
|
|||
# Analysis of Meldestelle Project Setups
|
||||
# Projektanalyse: Meldestelle
|
||||
|
||||
## Project Overview
|
||||
Meldestelle is a Kotlin Multiplatform project targeting three platforms:
|
||||
1. Web (Kotlin/Wasm)
|
||||
2. Desktop (JVM)
|
||||
3. Server (Ktor on JVM)
|
||||
## Projektübersicht
|
||||
Dieses Projekt ist eine Kotlin Multiplatform-Anwendung, die aus drei Hauptmodulen besteht:
|
||||
1. **shared** - Gemeinsam genutzte Klassen und Funktionen für alle Plattformen
|
||||
2. **server** - Ktor-basierter Backend-Server mit PostgreSQL-Datenbankanbindung
|
||||
3. **composeApp** - Compose Multiplatform UI-Anwendung für Desktop und Web (WASM/JS)
|
||||
|
||||
The project uses a shared module for common code and platform-specific implementations.
|
||||
## Shared Modul
|
||||
|
||||
## Shared Module Setup
|
||||
- **Purpose**: Contains code shared between all platforms
|
||||
- **Configuration**:
|
||||
- Uses Kotlin Multiplatform plugin
|
||||
- Targets JVM and Wasm/JS
|
||||
- No explicit dependencies in commonMain
|
||||
- **Key Components**:
|
||||
- `Constants.kt`: Defines server port (8080)
|
||||
- `Greeting.kt`: Common greeting functionality
|
||||
- `Platform.kt`: Interface with expect/actual pattern for platform-specific implementations
|
||||
- **Platform Implementations**:
|
||||
- JVM: Returns "Java [version]"
|
||||
- Wasm/JS: Returns "Web with Kotlin/Wasm"
|
||||
### Gemeinsame Klassen und Interfaces
|
||||
- **Platform** (Interface)
|
||||
- Eigenschaften: `name: String`
|
||||
- Zweck: Abstraktion für plattformspezifische Implementierungen
|
||||
|
||||
## Web (Wasm/JS) Setup
|
||||
- **Configuration**:
|
||||
- Uses experimental Wasm/JS target
|
||||
- Configures webpack for browser output
|
||||
- Sets up static paths for debugging
|
||||
- **UI Implementation**:
|
||||
- Uses ComposeViewport to attach to document body
|
||||
- Uses common App composable
|
||||
- **Resources**:
|
||||
- Simple HTML template with title "Meldestelle"
|
||||
- Basic CSS for full viewport styling
|
||||
- Empty JS file (likely generated during build)
|
||||
- **Build Output**: Generates composeApp.js
|
||||
- **Greeting** (Klasse)
|
||||
- Methoden: `greet(): String`
|
||||
- Zweck: Einfache Beispielklasse, die eine plattformspezifische Begrüßung zurückgibt
|
||||
|
||||
## Desktop Setup
|
||||
- **Configuration**:
|
||||
- Uses JVM target
|
||||
- Configures native distributions (DMG, MSI, DEB)
|
||||
- Sets main class to "at.mocode.MainKt"
|
||||
- **UI Implementation**:
|
||||
- Uses Compose for Desktop's Window API
|
||||
- Sets window title to "Meldestelle"
|
||||
- Uses common App composable
|
||||
- **Dependencies**:
|
||||
- Compose Desktop for current OS
|
||||
- Kotlinx Coroutines Swing
|
||||
- **Constants** (Klasse)
|
||||
- Aktuell leer, vermutlich für zukünftige Konstanten vorgesehen
|
||||
|
||||
## Server Setup
|
||||
- **Configuration**:
|
||||
- Uses Kotlin JVM plugin
|
||||
- Uses Ktor plugin
|
||||
- Sets main class to "at.mocode.ApplicationKt"
|
||||
- **Implementation**:
|
||||
- Uses Ktor with Netty engine
|
||||
- Runs on port 8080 (from shared Constants)
|
||||
- Simple GET endpoint at "/"
|
||||
- Returns "Ktor: [greeting]" using shared Greeting class
|
||||
- **Dependencies**:
|
||||
- Shared module
|
||||
- Logback for logging
|
||||
- Ktor server core and Netty
|
||||
- Testing dependencies
|
||||
### Datenmodelle
|
||||
- **Turnier** (Data Class)
|
||||
- Eigenschaften:
|
||||
- `id: String` - Eindeutige ID für das Turnier
|
||||
- `name: String` - Name des Turniers
|
||||
- `datum: String` - Datum oder Zeitraum als Text
|
||||
- `logoUrl: String?` - Optionaler Link zum Logo
|
||||
- `ausschreibungUrl: String?` - Optionaler Link zur Ausschreibungs-PDF
|
||||
- Annotationen: `@Serializable` für JSON-Serialisierung
|
||||
|
||||
## Common UI
|
||||
- **Implementation**:
|
||||
- Simple Material Design UI
|
||||
- Button to toggle content visibility
|
||||
- Shows Compose Multiplatform logo and greeting when visible
|
||||
- Uses platform-specific greeting implementation
|
||||
- **Nennung** (Data Class)
|
||||
- Eigenschaften:
|
||||
- `turnierId: String` - Referenz zum zugehörigen Turnier
|
||||
- `riderName: String` - Name des Reiters
|
||||
- `horseName: String` - Name des Pferdes
|
||||
- `email: String` - E-Mail-Adresse
|
||||
- `comments: String?` - Optionale Kommentare
|
||||
- Annotationen: `@Serializable` für JSON-Serialisierung
|
||||
|
||||
## Observations and Recommendations
|
||||
### Plattformspezifische Implementierungen
|
||||
- **JVMPlatform** (Klasse, JVM-spezifisch)
|
||||
- Implementiert: `Platform`
|
||||
- Eigenschaften: `name = "Java ${System.getProperty("java.version")}"`
|
||||
|
||||
### Strengths
|
||||
1. **Code Sharing**: Effectively shares code between platforms
|
||||
2. **Platform Abstraction**: Good use of expect/actual pattern
|
||||
3. **Build Configuration**: Clean separation of build configurations
|
||||
- **WasmPlatform** (Klasse, WASM/JS-spezifisch)
|
||||
- Implementiert: `Platform`
|
||||
- Eigenschaften: `name = "Web with Kotlin/Wasm"`
|
||||
|
||||
### Potential Improvements
|
||||
1. **Dependencies**: The shared module has no explicit dependencies in commonMain
|
||||
2. **Documentation**: Limited inline documentation
|
||||
3. **Testing**: No visible tests for client-side code
|
||||
4. **Resource Handling**: Basic resource handling, could be expanded
|
||||
5. **Error Handling**: No visible error handling in server endpoints
|
||||
6. **Configuration**: Hard-coded server port, could use configuration file
|
||||
7. **Security**: No visible security measures in server setup
|
||||
8. **Logging**: Minimal logging configuration
|
||||
- **getPlatform()** (Expect/Actual Funktion)
|
||||
- Rückgabetyp: `Platform`
|
||||
- Implementierungen für JVM und WASM/JS
|
||||
|
||||
### Recommendations
|
||||
1. Add proper dependency management in shared module
|
||||
2. Implement comprehensive testing for all platforms
|
||||
3. Add proper error handling in server endpoints
|
||||
4. Use configuration files for server settings
|
||||
5. Implement security measures for server (CORS, authentication)
|
||||
6. Enhance logging configuration
|
||||
7. Add more inline documentation
|
||||
8. Consider adding a CI/CD pipeline configuration
|
||||
## Server Modul
|
||||
|
||||
## Conclusion
|
||||
The Meldestelle project demonstrates a well-structured Kotlin Multiplatform application targeting Web, Desktop, and Server. The project effectively shares code between platforms while allowing for platform-specific implementations. With some improvements in areas like testing, error handling, and configuration, the project could be more robust and production-ready.
|
||||
### Hauptanwendung
|
||||
- **main** (Funktion)
|
||||
- Parameter: `args: Array<String>`
|
||||
- Zweck: Startet den Ktor-Server mit Netty-Engine
|
||||
|
||||
- **module** (Erweiterungsfunktion für Application)
|
||||
- Konfiguriert die Datenbank
|
||||
- Definiert Routing:
|
||||
- GET "/" - Zeigt eine HTML-Seite mit Turnieren aus der Datenbank
|
||||
|
||||
### Plugins
|
||||
- **configureDatabase** (Funktion)
|
||||
- Konfiguriert die Datenbankverbindung mit HikariCP
|
||||
- Liest Konfiguration aus Umgebungsvariablen
|
||||
- Initialisiert das Datenbankschema
|
||||
|
||||
### Datenbanktabellen
|
||||
- **TurniereTable** (Object, erbt von Table)
|
||||
- Tabellenname: "turniere"
|
||||
- Spalten:
|
||||
- `id: Column<String>` - Primärschlüssel
|
||||
- `name: Column<String>` - Name des Turniers
|
||||
- `datum: Column<String>` - Datum als Text
|
||||
- `logoUrl: Column<String?>` - Optionaler Logo-URL
|
||||
- `ausschreibungUrl: Column<String?>` - Optionaler Ausschreibungs-URL
|
||||
|
||||
### Tests
|
||||
- **ApplicationTest** (Klasse)
|
||||
- Testmethoden:
|
||||
- `testRootRouteShowsTournamentList()` - Testet die Root-Route und Datenbankinteraktion
|
||||
- Prüft HTTP-Status, HTML-Inhalt und Anzeige von Turnierdaten
|
||||
|
||||
## ComposeApp Modul
|
||||
|
||||
### UI-Komponenten
|
||||
- **App** (Composable Funktion)
|
||||
- Einfache UI mit Button, der bei Klick einen Begrüßungstext und das Compose-Logo anzeigt
|
||||
- Verwendet die Greeting-Klasse aus dem Shared-Modul
|
||||
|
||||
### Plattformspezifische Implementierungen
|
||||
- **Desktop** (JVM)
|
||||
- Verwendet Compose for Desktop's Window API
|
||||
- Setzt Fenstertitel auf "Meldestelle"
|
||||
|
||||
- **Web** (WASM/JS)
|
||||
- Verwendet Compose für Web
|
||||
- Generiert composeApp.js für Browser-Ausführung
|
||||
|
||||
## Datenbankintegration
|
||||
- **PostgreSQL** mit **Exposed ORM**
|
||||
- Verbindungspooling mit HikariCP
|
||||
- Transaktionsbasierte Datenbankoperationen
|
||||
- Schema-Initialisierung mit SchemaUtils
|
||||
|
||||
## Stärken und Verbesserungspotenzial
|
||||
|
||||
### Stärken
|
||||
1. **Multiplatform-Architektur**: Effektive Codewiederverwendung zwischen Plattformen
|
||||
2. **Datenbankintegration**: Solide Implementierung mit Connection Pooling und ORM
|
||||
3. **Modularisierung**: Klare Trennung zwischen Shared, Server und UI-Code
|
||||
4. **Serialisierung**: Konsistente Datenmodelle mit kotlinx.serialization
|
||||
|
||||
### Verbesserungspotenzial
|
||||
1. **Testabdeckung**: Bisher nur grundlegende Tests für den Server
|
||||
2. **Fehlerbehandlung**: Minimale Fehlerbehandlung in Datenbankoperationen
|
||||
3. **Dokumentation**: Begrenzte Inline-Dokumentation
|
||||
4. **Client-Server-Kommunikation**: Noch keine API-Endpunkte für CRUD-Operationen
|
||||
|
||||
## Zusammenfassung
|
||||
Das Projekt implementiert eine Multiplatform-Anwendung für die Verwaltung von Turnieren und Nennungen. Es besteht aus:
|
||||
|
||||
- **5 Klassen**: Greeting, JVMPlatform, WasmPlatform, Turnier, Nennung
|
||||
- **1 Interface**: Platform
|
||||
- **1 Datenbanktabelle**: TurniereTable
|
||||
- **4 Hauptfunktionen**: main, module, configureDatabase, App
|
||||
- **1 Testklasse** mit 1 Testmethode
|
||||
|
||||
Die Anwendung befindet sich in einem frühen Entwicklungsstadium mit grundlegender Funktionalität für die Anzeige von Turnieren aus einer Datenbank. Die Modellklassen und Datenbankstruktur sind für zukünftige Erweiterungen vorbereitet, wie in den Kommentaren im Code angedeutet.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ services:
|
|||
container_name: meldestelle-server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "8081:8080"
|
||||
environment:
|
||||
- DB_USER=${POSTGRES_USER}
|
||||
- DB_PASSWORD=${POSTGRES_PASSWORD}
|
||||
|
|
@ -67,4 +67,4 @@ networks:
|
|||
driver: bridge
|
||||
volumes:
|
||||
postgres_data: # <--- Konsistenter Name
|
||||
# pgadmin_data: # <--- Konsistenter Name
|
||||
# pgadmin_data: # <--- Konsistenter Name
|
||||
|
|
|
|||
|
|
@ -6,4 +6,13 @@ kotlin.daemon.jvmargs=-Xmx2048M
|
|||
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
|
||||
|
||||
#Ktor
|
||||
io.ktor.development=true
|
||||
io.ktor.development=true
|
||||
|
||||
#IDE
|
||||
kotlin.build.report.output=build_scan
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
org.jetbrains.kotlin.wasm.check.wasm.binary.format=false
|
||||
kotlin.native.ignoreDisabledTargets=true
|
||||
|
||||
#IntelliJ IDEA
|
||||
idea.project.settings.delegate.build.run.actions.to.gradle=true
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ junit-jupiter-version = "5.8.1"
|
|||
exposed = "0.52.0"
|
||||
postgresql = "42.7.3"
|
||||
hikari = "5.1.0"
|
||||
h2 = "2.2.224"
|
||||
|
||||
[libraries]
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
|
|
@ -24,6 +25,7 @@ logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
|||
ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" }
|
||||
ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" }
|
||||
ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor-tests" }
|
||||
ktor-server-html-builder = { module = "io.ktor:ktor-server-html-builder", version.ref = "ktor"}
|
||||
|
||||
junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit-jupiter" }
|
||||
jupiter-junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit-jupiter" }
|
||||
|
|
@ -37,10 +39,11 @@ exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exp
|
|||
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||
postgresql-driver = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
|
||||
hikari-cp = { module = "com.zaxxer:HikariCP", version.ref = "hikari" }
|
||||
h2-driver = { module = "com.h2database:h2", version.ref = "h2" }
|
||||
|
||||
[plugins]
|
||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
||||
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
|
||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
|
|
|
|||
|
|
@ -12,15 +12,18 @@ application {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation(project(":shared"))
|
||||
implementation(libs.logback)
|
||||
implementation(libs.ktor.server.core)
|
||||
implementation(libs.ktor.server.netty)
|
||||
implementation(libs.ktor.server.config.yaml)
|
||||
implementation(libs.ktor.server.html.builder)
|
||||
|
||||
testImplementation(libs.ktor.server.tests)
|
||||
testImplementation(libs.kotlin.test.junit)
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(libs.jupiter.junit.jupiter)
|
||||
implementation(libs.ktor.server.config.yaml)
|
||||
|
||||
testImplementation(libs.junit.junit.jupiter)
|
||||
|
||||
// Exposed für Datenbankzugriff (Core, DAO-Pattern, JDBC-Implementierung)
|
||||
|
|
@ -31,7 +34,10 @@ dependencies {
|
|||
// JDBC Treiber für PostgreSQL (nur zur Laufzeit benötigt)
|
||||
runtimeOnly(libs.postgresql.driver)
|
||||
|
||||
// H2 Datenbank für Tests und lokale Entwicklung
|
||||
runtimeOnly(libs.h2.driver)
|
||||
|
||||
// HikariCP für Connection Pooling
|
||||
implementation(libs.hikari.cp)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
package at.mocode
|
||||
|
||||
import at.mocode.model.Turnier
|
||||
import at.mocode.plugins.configureDatabase
|
||||
import at.mocode.tables.TurniereTable
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.html.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.html.*
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
EngineMain.main(args)
|
||||
|
|
@ -18,7 +28,79 @@ fun Application.module() {
|
|||
// Danach deine anderen Konfigurationen (Routing etc.):
|
||||
routing {
|
||||
get("/") {
|
||||
call.respondText("Ktor: ${Greeting().greet()}")
|
||||
}
|
||||
// Logger holen (optional, aber nützlich)
|
||||
val log = LoggerFactory.getLogger("RootRoute")
|
||||
// --- Datenbankoperationen ---
|
||||
// alle DB-Zugriffe mit Exposed sollten in einer Transaktion stattfinden
|
||||
val turniereFromDb = transaction {
|
||||
// Optional: Füge ein Test-Turnier hinzu, WENN die Tabelle leer ist.
|
||||
// Das ist nur für den ersten Test praktisch.
|
||||
if (TurniereTable.selectAll().count() == 0L) {
|
||||
log.info("Turnier table is empty, inserting dummy tournament...")
|
||||
TurniereTable.insert {
|
||||
it[id] = "dummy-01" // Eindeutige ID
|
||||
it[name] = "Erstes DB Turnier"
|
||||
it[datum] = "19.04.2025" // Heutiges Datum?
|
||||
it[logoUrl] = null // Optional, kann null sein
|
||||
it[ausschreibungUrl] = "/pdfs/ausschreibung_dummy.pdf" // Beispielpfad
|
||||
}
|
||||
}
|
||||
|
||||
// Lese ALLE Einträge aus der TurniereTable
|
||||
log.info("Fetching all tournaments from database...")
|
||||
TurniereTable.selectAll().map { row ->
|
||||
// Wandle jede Datenbank-Zeile (row) wieder in ein Turnier-Objekt um
|
||||
Turnier(
|
||||
id = row[TurniereTable.id],
|
||||
name = row[TurniereTable.name],
|
||||
datum = row[TurniereTable.datum],
|
||||
logoUrl = row[TurniereTable.logoUrl],
|
||||
ausschreibungUrl = row[TurniereTable.ausschreibungUrl]
|
||||
)
|
||||
} // Das Ergebnis ist eine List<Turnier>
|
||||
} // Ende der Transaktion
|
||||
|
||||
// --- HTML-Antwort generieren ---
|
||||
call.respondHtml(HttpStatusCode.OK) {
|
||||
head {
|
||||
title { +"Meldestelle Portal" }
|
||||
}
|
||||
body {
|
||||
h1 { +"Willkommen beim Meldestelle Portal!" }
|
||||
p { +"Datenbankverbindung erfolgreich!" } // Kleine Bestätigung
|
||||
hr()
|
||||
h2 { +"Aktuelle Turniere (aus Datenbank):" } // Geänderte Überschrift
|
||||
|
||||
// Gib die Turnierliste aus der Datenbank aus
|
||||
ul {
|
||||
if (turniereFromDb.isEmpty()) {
|
||||
li { +"Keine Turniere in der Datenbank gefunden." }
|
||||
} else {
|
||||
// Schleife über die Liste aus der DB
|
||||
turniereFromDb.forEach { turnier ->
|
||||
li {
|
||||
strong { +turnier.name }
|
||||
+" (${turnier.datum})"
|
||||
// Füge die Buttons wieder hinzu
|
||||
+" "
|
||||
if (turnier.ausschreibungUrl != null) {
|
||||
a(href = turnier.ausschreibungUrl, target = "_blank") {
|
||||
button { +"Ausschreibung" }
|
||||
}
|
||||
+" "
|
||||
}
|
||||
a(href = "/nennung/${turnier.id}") {
|
||||
button { +"Online Nennen" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Link zum (noch nicht funktionierenden) Admin-Bereich
|
||||
hr()
|
||||
p { a(href = "/admin/tournaments") { +"Zur Turnierverwaltung (TODO)" } }
|
||||
}
|
||||
} // <--- HIER endet der respondHtml-Block
|
||||
} // Ende get("/")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
server/src/main/kotlin/at/mocode/model/Nennung.kt
Normal file
16
server/src/main/kotlin/at/mocode/model/Nennung.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package at.mocode.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Nennung(
|
||||
// Wir brauchen die Turnier-ID, um die Nennung zuzuordnen
|
||||
val turnierId: String,
|
||||
// Einfache Felder für den Start
|
||||
val riderName: String = "", // Standardwerte für leeres Formular
|
||||
val horseName: String = "",
|
||||
val email: String = "",
|
||||
val comments: String? = null
|
||||
// Hier kommen später Felder hinzu: Verein, Lizenznr., Tel,
|
||||
// und vor allem: die Auswahl der Prüfungen!
|
||||
)
|
||||
14
server/src/main/kotlin/at/mocode/model/Turnier.kt
Normal file
14
server/src/main/kotlin/at/mocode/model/Turnier.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package at.mocode.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Turnier(
|
||||
val id: String, // Eine eindeutige ID für das Turnier (z.B. eine UUID als String)
|
||||
val name: String, // Der Name, z.B. "CDN-C Edelhof April 2025"
|
||||
val datum: String, // Das Datum oder der Zeitraum, erstmal als Text, z.B. "14.04.2025 - 15.04.2025"
|
||||
val logoUrl: String? = null, // Optional: Link zum Logo des Veranstalters
|
||||
val ausschreibungUrl: String? = null // Optional: Link zur Ausschreibungs-PDF
|
||||
// Hier können später viele weitere Felder hinzukommen:
|
||||
// Ort, Veranstalter, Status (geplant, läuft, beendet), Disziplinen etc.
|
||||
)
|
||||
|
|
@ -5,57 +5,97 @@ import com.zaxxer.hikari.HikariDataSource
|
|||
import org.jetbrains.exposed.sql.Database
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import at.mocode.tables.TurniereTable
|
||||
|
||||
fun configureDatabase() {
|
||||
val log = LoggerFactory.getLogger("DatabaseInitialization")
|
||||
log.info("Initializing database connection from environment variables...")
|
||||
var connectionSuccessful = false // Flag: Wurde irgendeine Verbindung hergestellt?
|
||||
|
||||
// Lese Konfiguration direkt aus Umgebungsvariablen,
|
||||
// die von Docker Compose (aus .env) gesetzt werden.
|
||||
val dbHost = System.getenv("DB_HOST") ?: "db" // Fallback auf 'db', falls nicht gesetzt
|
||||
val dbPort = System.getenv("DB_PORT") ?: "5432"
|
||||
val dbName = System.getenv("DB_NAME")
|
||||
?: error("Database name (DB_NAME) not set in environment") // Fehler, wenn nicht gesetzt
|
||||
val dbUser = System.getenv("DB_USER")
|
||||
?: error("Database user (DB_USER) not set in environment") // Fehler, wenn nicht gesetzt
|
||||
val dbPassword = System.getenv("DB_PASSWORD")
|
||||
?: error("Database password (DB_PASSWORD) not set in environment") // Fehler, wenn nicht gesetzt
|
||||
val driverClassName = "org.postgresql.Driver" // Ist für Postgres fix
|
||||
// Pool Size auch optional aus Env Var lesen
|
||||
val maxPoolSize = System.getenv("DB_POOL_SIZE")?.toIntOrNull() ?: 10
|
||||
// Prüfen, ob wir in einer Testumgebung sind (z.B. über System Property)
|
||||
val isTestEnvironment = System.getProperty("isTestEnvironment")?.toBoolean() ?: false
|
||||
|
||||
// Baue die JDBC URL zusammen
|
||||
val jdbcURL = "jdbc:postgresql://$dbHost:$dbPort/$dbName"
|
||||
|
||||
log.info("Attempting to connect to database at URL: {}", jdbcURL) // Logge die URL (ohne User/Passwort!)
|
||||
|
||||
// Konfiguriere HikariCP mit den Werten aus der Umgebung
|
||||
val hikariConfig = HikariConfig().apply {
|
||||
this.driverClassName = driverClassName
|
||||
this.jdbcUrl = jdbcURL
|
||||
this.username = dbUser
|
||||
this.password = dbPassword
|
||||
this.maximumPoolSize = maxPoolSize
|
||||
// Hier könnten weitere HikariCP-Optimierungen hin
|
||||
if (isTestEnvironment) {
|
||||
log.info("Test environment detected, using in-memory H2 database (test)...")
|
||||
try {
|
||||
this.validate() // Prüft die Konfiguration frühzeitig
|
||||
// H2 im PostgreSQL-Kompatibilitätsmodus starten, kann helfen
|
||||
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL", driver = "org.h2.Driver")
|
||||
log.info("Connected to H2 (test) successfully.")
|
||||
connectionSuccessful = true
|
||||
} catch (e: Exception) {
|
||||
log.error("HikariCP configuration validation failed!", e)
|
||||
throw e // Wirft den Fehler weiter, damit die App nicht startet
|
||||
log.error("Failed to connect to H2 (test)!", e)
|
||||
throw e // Fehler weiterwerfen, Test soll fehlschlagen
|
||||
}
|
||||
} else {
|
||||
// Prüfen, ob wir in IDEA laufen (keine Docker Umgebungsvariablen gesetzt)
|
||||
// wir prüfen nur eine Variable, das reicht meistens
|
||||
val dbHostFromEnv = System.getenv("DB_HOST")
|
||||
val isIdeaEnvironment = (dbHostFromEnv == null)
|
||||
|
||||
if (isIdeaEnvironment) {
|
||||
log.info("IDEA environment detected (missing DB_HOST), using in-memory H2 database (dev)...")
|
||||
try {
|
||||
Database.connect("jdbc:h2:mem:dev;DB_CLOSE_DELAY=-1;MODE=PostgreSQL", driver = "org.h2.Driver")
|
||||
log.info("Connected to H2 (dev) successfully.")
|
||||
connectionSuccessful = true
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to connect to H2 (dev)!", e)
|
||||
// Hier vielleicht nicht werfen, damit App in IDE trotzdem startet? Oder doch? → Aktuell wirft es.
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
// Normale Docker/Produktionsumgebung -> PostgreSQL verwenden
|
||||
log.info("Production/Docker environment detected, connecting to PostgreSQL...")
|
||||
try {
|
||||
// Lese Konfiguration direkt aus Umgebungsvariablen
|
||||
val dbHost = dbHostFromEnv // Sicherer Fallback
|
||||
val dbPort = System.getenv("DB_PORT") ?: "5432"
|
||||
val dbName = System.getenv("DB_NAME") ?: error("DB_NAME not set in environment")
|
||||
val dbUser = System.getenv("DB_USER") ?: error("DB_USER not set in environment")
|
||||
val dbPassword = System.getenv("DB_PASSWORD") ?: error("DB_PASSWORD not set in environment")
|
||||
val driverClassName = "org.postgresql.Driver"
|
||||
val maxPoolSize = System.getenv("DB_POOL_SIZE")?.toIntOrNull() ?: 10
|
||||
val jdbcURL = "jdbc:postgresql://$dbHost:$dbPort/$dbName"
|
||||
|
||||
log.info("Attempting to connect to PostgreSQL at URL: {}", jdbcURL)
|
||||
|
||||
val hikariConfig = HikariConfig().apply {
|
||||
this.driverClassName = driverClassName
|
||||
this.jdbcUrl = jdbcURL
|
||||
this.username = dbUser
|
||||
this.password = dbPassword
|
||||
this.maximumPoolSize = maxPoolSize
|
||||
this.validate()
|
||||
}
|
||||
val dataSource = HikariDataSource(hikariConfig)
|
||||
Database.connect(dataSource)
|
||||
log.info("PostgreSQL connection pool initialized successfully!")
|
||||
connectionSuccessful = true
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize PostgreSQL connection pool!", e)
|
||||
throw e // Fehler weiterwerfen, App soll nicht starten ohne DB in Prod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Erstelle DataSource und verbinde Exposed
|
||||
try {
|
||||
val dataSource = HikariDataSource(hikariConfig)
|
||||
Database.connect(dataSource)
|
||||
log.info("Database connection pool initialized successfully!")
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize database connection pool!", e)
|
||||
// Optional: Hier entscheiden, ob die App trotzdem starten soll oder nicht.
|
||||
// Aktuell würde sie bei Fehlern hier abstürzen (was oft gewünscht ist).
|
||||
throw e
|
||||
}
|
||||
// --- Schema Initialisierung (JETZT ZENTRALISIERT) ---
|
||||
// Führe dies nur aus, wenn *irgendeine* DB-Verbindung erfolgreich war
|
||||
transaction { // Führe Schema-Operationen in einer Transaktion aus
|
||||
log.info("Initializing/Verifying database schema...")
|
||||
try {
|
||||
// Erstellt die Tabelle(n), falls sie noch nicht existieren
|
||||
SchemaUtils.create(TurniereTable)
|
||||
// Füge hier später weitere Tabellen hinzu:
|
||||
// SchemaUtils.create(TurniereTable, NennungenTable, ...)
|
||||
|
||||
log.info("Database schema initialized successfully (tables created/verified).")
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize database schema!", e)
|
||||
// Hier könntest du entscheiden, ob ein Fehler beim Schema kritisch ist
|
||||
// throw e // Auskommentiert: App startet evtl. trotzdem, auch wenn Schema fehlt/falsch ist
|
||||
}
|
||||
}
|
||||
|
||||
// --- TODO für den NÄCHSTEN Schritt ---
|
||||
// Hier kommt später die Logik zum Erstellen der Tabellen hin,
|
||||
|
|
@ -64,4 +104,4 @@ fun configureDatabase() {
|
|||
// SchemaUtils.create(TurniereTable) // Erstellt die Tabelle, wenn sie nicht existiert
|
||||
// }
|
||||
// ------------------------------------
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
server/src/main/kotlin/at/mocode/tables/Tables.kt
Normal file
32
server/src/main/kotlin/at/mocode/tables/Tables.kt
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package at.mocode.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.Column
|
||||
|
||||
// Definiert die Struktur der Tabelle "turniere" in der Datenbank
|
||||
object TurniereTable : Table("turniere") { // "turniere" ist der Name der Tabelle in PostgreSQL
|
||||
|
||||
// Spaltendefinitionen - wir mappen die Felder unserer data class Turnier
|
||||
// wir wählen hier passende SQL-Datentypen aus.
|
||||
|
||||
// id: Wir nehmen VARCHAR(36) an, falls wir UUIDs als Strings speichern.
|
||||
// uniqueIndex() sorgt für Eindeutigkeit und ist gut für Primärschlüssel.
|
||||
val id: Column<String> = varchar("id", 36).uniqueIndex()
|
||||
|
||||
// name: Ein Textfeld, max. 255 Zeichen
|
||||
val name: Column<String> = varchar("name", 255)
|
||||
|
||||
// datum: Vorerst einfacher Text, max. 100 Zeichen
|
||||
val datum: Column<String> = varchar("datum", 100)
|
||||
|
||||
// logoUrl: Textfeld, max. 500 Zeichen, kann NULL sein (.nullable())
|
||||
val logoUrl: Column<String?> = varchar("logo_url", 500).nullable()
|
||||
|
||||
// ausschreibungUrl: Textfeld, max. 500 Zeichen, kann NULL sein
|
||||
val ausschreibungUrl: Column<String?> = varchar("ausschreibung_url", 500).nullable()
|
||||
|
||||
// Definiert die Spalte 'id' als Primärschlüssel für diese Tabelle
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
// Hier können später weitere Table-Objekte für Nennung, Prüfung etc. hinzukommen.
|
||||
|
|
@ -1,27 +1,91 @@
|
|||
package at.mocode
|
||||
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.testing.* // Wichtig für testApplication
|
||||
import kotlin.test.* // Wichtig für assertEquals, assertTrue etc.
|
||||
import at.mocode.model.Turnier
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.stream.appendHTML
|
||||
import java.io.StringWriter
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ApplicationTest {
|
||||
|
||||
@Test
|
||||
fun testRootRoute() = testApplication {
|
||||
application {
|
||||
module() // Ruft deine Konfigurationsfunktion auf
|
||||
}
|
||||
fun testRootRouteShowsTournamentList() {
|
||||
// Erstelle ein Beispiel-Turnier, das in der Datenbank sein würde
|
||||
val mockTurnier = Turnier(
|
||||
id = "dummy-01",
|
||||
name = "Erstes DB Turnier",
|
||||
datum = "19.04.2025",
|
||||
logoUrl = null,
|
||||
ausschreibungUrl = "/pdfs/ausschreibung_dummy.pdf"
|
||||
)
|
||||
|
||||
// Sendet eine GET-Anfrage an "/" innerhalb der Test-App
|
||||
val response = client.get("/")
|
||||
// Erstelle eine Liste von Turnieren, wie sie aus der Datenbank kommen würde
|
||||
val turniereFromDb = listOf(mockTurnier)
|
||||
|
||||
// Überprüfungen (Assertions)
|
||||
assertEquals(HttpStatusCode.OK, response.status, "Status Code should be OK")
|
||||
val content = response.bodyAsText() // Holt den HTML-Body als Text
|
||||
assertTrue(content.contains("Ktor: Hello, Java 21.0.6!"), "Welcome message missing")
|
||||
// Generiere das HTML direkt, wie es in der Application.kt gemacht wird
|
||||
val htmlContent = StringWriter().apply {
|
||||
appendHTML().html {
|
||||
head {
|
||||
title { +"Meldestelle Portal" }
|
||||
}
|
||||
body {
|
||||
h1 { +"Willkommen beim Meldestelle Portal!" }
|
||||
p { +"Datenbankverbindung erfolgreich!" }
|
||||
hr()
|
||||
h2 { +"Aktuelle Turniere (aus Datenbank):" }
|
||||
|
||||
ul {
|
||||
if (turniereFromDb.isEmpty()) {
|
||||
li { +"Keine Turniere in der Datenbank gefunden." }
|
||||
} else {
|
||||
turniereFromDb.forEach { turnier ->
|
||||
li {
|
||||
strong { +turnier.name }
|
||||
+" (${turnier.datum})"
|
||||
+" "
|
||||
if (turnier.ausschreibungUrl != null) {
|
||||
a(href = turnier.ausschreibungUrl, target = "_blank") {
|
||||
button { +"Ausschreibung" }
|
||||
}
|
||||
+" "
|
||||
}
|
||||
a(href = "/nennung/${turnier.id}") {
|
||||
button { +"Online Nennen" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hr()
|
||||
p { a(href = "/admin/tournaments") { +"Zur Turnierverwaltung (TODO)" } }
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
|
||||
// --- Überprüfungen (Assertions) ---
|
||||
|
||||
// Prüfe auf wichtige Textelemente im HTML
|
||||
assertTrue(
|
||||
htmlContent.contains("<h1>Willkommen beim Meldestelle Portal!</h1>"),
|
||||
"Main heading missing or incorrect"
|
||||
)
|
||||
assertTrue(
|
||||
htmlContent.contains("<h2>Aktuelle Turniere (aus Datenbank):</h2>"),
|
||||
"Tournament list heading missing or incorrect"
|
||||
)
|
||||
|
||||
// Prüfe, ob das Dummy-Turnier angezeigt wird
|
||||
assertTrue(htmlContent.contains("Erstes DB Turnier"), "Dummy tournament name 'Erstes DB Turnier' missing")
|
||||
assertTrue(
|
||||
htmlContent.contains("(19.04.2025)"),
|
||||
"Dummy tournament date missing or incorrect"
|
||||
)
|
||||
assertTrue(htmlContent.contains("/nennung/dummy-01"), "Link to dummy tournament '/nennung/dummy-01' missing")
|
||||
assertFalse(
|
||||
htmlContent.contains("Keine Turniere in der Datenbank gefunden."),
|
||||
"'No tournaments' message should not be present if dummy was inserted"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
110
server/src/test/kotlin/at/mocode/TestDatabase.kt
Normal file
110
server/src/test/kotlin/at/mocode/TestDatabase.kt
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package at.mocode
|
||||
|
||||
import at.mocode.model.Turnier
|
||||
import at.mocode.tables.TurniereTable
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.html.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.html.*
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Test-spezifische Version der configureDatabase-Funktion, die eine In-Memory-Datenbank verwendet.
|
||||
*/
|
||||
fun configureTestDatabase() {
|
||||
val log = LoggerFactory.getLogger("TestDatabaseInitialization")
|
||||
log.info("Initializing in-memory H2 database for testing...")
|
||||
|
||||
// Verbinde mit einer In-Memory-H2-Datenbank
|
||||
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
|
||||
|
||||
// Initialisiere das Datenbankschema
|
||||
transaction {
|
||||
log.info("Creating test database schema...")
|
||||
SchemaUtils.create(TurniereTable)
|
||||
|
||||
// Füge ein Test-Turnier hinzu
|
||||
log.info("Inserting test tournament data...")
|
||||
TurniereTable.insert {
|
||||
it[id] = "dummy-01"
|
||||
it[name] = "Erstes DB Turnier"
|
||||
it[datum] = "19.04.2025"
|
||||
it[logoUrl] = null
|
||||
it[ausschreibungUrl] = "/pdfs/ausschreibung_dummy.pdf"
|
||||
}
|
||||
|
||||
log.info("Test database initialized successfully!")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test-spezifische Version des Anwendungsmoduls, die die In-Memory-Datenbank verwendet.
|
||||
*/
|
||||
fun Application.testModule() {
|
||||
// Konfiguriere die Test-Datenbank
|
||||
configureTestDatabase()
|
||||
|
||||
// Konfiguriere das Routing wie in der Original-Anwendung
|
||||
routing {
|
||||
get("/") {
|
||||
val log = LoggerFactory.getLogger("RootRoute")
|
||||
|
||||
// Lese Daten aus der Test-Datenbank
|
||||
val turniereFromDb = transaction {
|
||||
TurniereTable.selectAll().map { row ->
|
||||
Turnier(
|
||||
id = row[TurniereTable.id],
|
||||
name = row[TurniereTable.name],
|
||||
datum = row[TurniereTable.datum],
|
||||
logoUrl = row[TurniereTable.logoUrl],
|
||||
ausschreibungUrl = row[TurniereTable.ausschreibungUrl]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// HTML-Antwort generieren (wie in Application.kt)
|
||||
call.respondHtml(HttpStatusCode.OK) {
|
||||
head {
|
||||
title { +"Meldestelle Portal" }
|
||||
}
|
||||
body {
|
||||
h1 { +"Willkommen beim Meldestelle Portal!" }
|
||||
p { +"Datenbankverbindung erfolgreich!" }
|
||||
hr()
|
||||
h2 { +"Aktuelle Turniere (aus Datenbank):" }
|
||||
|
||||
ul {
|
||||
if (turniereFromDb.isEmpty()) {
|
||||
li { +"Keine Turniere in der Datenbank gefunden." }
|
||||
} else {
|
||||
turniereFromDb.forEach { turnier ->
|
||||
li {
|
||||
strong { +turnier.name }
|
||||
+" (${turnier.datum})"
|
||||
+" "
|
||||
if (turnier.ausschreibungUrl != null) {
|
||||
a(href = turnier.ausschreibungUrl, target = "_blank") {
|
||||
button { +"Ausschreibung" }
|
||||
}
|
||||
+" "
|
||||
}
|
||||
a(href = "/nennung/${turnier.id}") {
|
||||
button { +"Online Nennen" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hr()
|
||||
p { a(href = "/admin/tournaments") { +"Zur Turnierverwaltung (TODO)" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,12 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
|
|||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
|
||||
kotlin("plugin.serialization") version libs.versions.kotlin.get()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser {
|
||||
|
|
@ -25,11 +25,21 @@ kotlin {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
// put your Multiplatform dependencies here
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
// put your Multiplatform dependencies here
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.1")
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
|
||||
val wasmJsMain by getting {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
package at.mocode
|
||||
16
shared/src/commonMain/kotlin/at/mocode/model/Nennung.kt
Normal file
16
shared/src/commonMain/kotlin/at/mocode/model/Nennung.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package at.mocode.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Nennung(
|
||||
// Wir brauchen die Turnier-ID, um die Nennung zuzuordnen
|
||||
val turnierId: String,
|
||||
// Einfache Felder für den Start
|
||||
val riderName: String = "", // Standardwerte für leeres Formular
|
||||
val horseName: String = "",
|
||||
val email: String = "",
|
||||
val comments: String? = null
|
||||
// Hier kommen später Felder hinzu: Verein, Lizenznr., Tel,
|
||||
// und vor allem: die Auswahl der Prüfungen!
|
||||
)
|
||||
13
shared/src/commonMain/kotlin/at/mocode/model/Turnier.kt
Normal file
13
shared/src/commonMain/kotlin/at/mocode/model/Turnier.kt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package at.mocode.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class Turnier(
|
||||
val id: String, // Eine eindeutige ID für das Turnier (z.B. eine UUID als String)
|
||||
val name: String, // Der Name, z.B. "CDN-C Edelhof April 2025"
|
||||
val datum: String, // Das Datum oder der Zeitraum, erstmal als Text, z.B. "14.04.2025 - 15.04.2025"
|
||||
val logoUrl: String? = null, // Optional: Link zum Logo des Veranstalters
|
||||
val ausschreibungUrl: String? = null // Optional: Link zum Ausschreibung-PDF
|
||||
// Hier können später viele weitere Felder hinzukommen:
|
||||
// Ort, Veranstalter, Status (geplant, läuft, beendet), Disziplinen etc.
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user