fix(server, shared): TODO
This commit is contained in:
@@ -1,89 +0,0 @@
|
||||
package at.mocode
|
||||
|
||||
import at.mocode.plugins.configureDatabase
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.netty.*
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
EngineMain.main(args)
|
||||
}
|
||||
|
||||
fun Application.module() {
|
||||
|
||||
// Als Erstes die Datenbank konfigurieren:
|
||||
configureDatabase()
|
||||
|
||||
// Danach deine anderen Konfigurationen (Routing etc.):
|
||||
// routing {
|
||||
// get("/") {
|
||||
// // 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
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 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],
|
||||
//
|
||||
// )
|
||||
// } // 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("/")
|
||||
// }
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package at.mocode.plugins
|
||||
|
||||
import at.mocode.tables.*
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
fun configureDatabase() {
|
||||
val log = LoggerFactory.getLogger("DatabaseInitialization")
|
||||
var connectionSuccessful = false // Flag: Wurde irgendeine Verbindung hergestellt?
|
||||
|
||||
// Prüfen, ob wir in einer Testumgebung sind (z.B. über System Property)
|
||||
val isTestEnvironment = System.getProperty("isTestEnvironment")?.toBoolean() ?: false
|
||||
|
||||
if (isTestEnvironment) {
|
||||
log.info("Test environment detected, using in-memory H2 database (test)...")
|
||||
try {
|
||||
// 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("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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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,
|
||||
// z.B. innerhalb einer Transaktion:
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
VereineTable,
|
||||
PersonenTable,
|
||||
PferdeTable,
|
||||
VeranstaltungenTable, // NEU
|
||||
TurniereTable,
|
||||
ArtikelTable,
|
||||
PlaetzeTable // NEU
|
||||
// ... weitere Tabellen ...
|
||||
)
|
||||
}
|
||||
// ------------------------------------
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package at.mocode.server
|
||||
|
||||
import at.mocode.server.plugins.configureDatabase
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Main entry point for the application.
|
||||
* Uses Ktor's EngineMain to start the server with configuration from application.yaml
|
||||
*/
|
||||
fun main(args: Array<String>) {
|
||||
EngineMain.main(args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Application module configuration.
|
||||
* This is where all server plugins and routes are configured.
|
||||
*/
|
||||
fun Application.module() {
|
||||
val log = LoggerFactory.getLogger("Application")
|
||||
|
||||
log.info("Initializing application...")
|
||||
|
||||
// Configure database
|
||||
configureDatabase()
|
||||
|
||||
// Configure plugins
|
||||
// configurePlugins()
|
||||
|
||||
// Configure routing
|
||||
configureRouting()
|
||||
|
||||
log.info("Application initialized successfully")
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures all Ktor plugins for the application
|
||||
*/
|
||||
//private fun Application.configurePlugins() {
|
||||
// val log = LoggerFactory.getLogger("ApplicationPlugins")
|
||||
// // Add default headers to all responses
|
||||
// install(DefaultHeaders) {
|
||||
// header("X-Engine", "Ktor")
|
||||
// header("X-Content-Type-Options", "nosniff")
|
||||
// }
|
||||
//
|
||||
// // Configure call logging
|
||||
// install(CallLogging) {
|
||||
// level = Level.INFO
|
||||
// }
|
||||
//
|
||||
// // Configure content negotiation with JSON
|
||||
// install(ContentNegotiation) {
|
||||
// json(Json {
|
||||
// prettyPrint = true
|
||||
// isLenient = true
|
||||
// ignoreUnknownKeys = true
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// // Configure CORS
|
||||
// install(CORS) {
|
||||
// // Default CORS configuration
|
||||
// anyHost()
|
||||
// allowMethod(HttpMethod.Options)
|
||||
// allowMethod(HttpMethod.Get)
|
||||
// allowMethod(HttpMethod.Post)
|
||||
// allowMethod(HttpMethod.Put)
|
||||
// allowMethod(HttpMethod.Delete)
|
||||
// allowHeader(HttpHeaders.ContentType)
|
||||
// allowHeader(HttpHeaders.Authorization)
|
||||
//
|
||||
// // Try to read from config to override defaults
|
||||
// try {
|
||||
// val appEnv = this@configurePlugins.environment.config
|
||||
// if (appEnv.propertyOrNull("cors") != null) {
|
||||
// val corsConfig = appEnv.config("cors")
|
||||
//
|
||||
// // Clear default anyHost if we have specific hosts
|
||||
// if (corsConfig.propertyOrNull("allowedHosts") != null) {
|
||||
// val hosts = corsConfig.property("allowedHosts").getList()
|
||||
// if (hosts.isNotEmpty()) {
|
||||
// hosts.forEach { host ->
|
||||
// allowHost(host)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Allow credentials if configured
|
||||
// if (corsConfig.propertyOrNull("allowCredentials") != null) {
|
||||
// allowCredentials = corsConfig.property("allowCredentials").getString().toBoolean()
|
||||
// }
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// // Log the error but continue with default configuration
|
||||
// this@configurePlugins.log.warn("Failed to configure CORS from config, using defaults: ${e.message}")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Configure status pages for error handling
|
||||
// install(StatusPages) {
|
||||
// exception<Throwable> { call, cause ->
|
||||
// call.respondText(
|
||||
// text = "500: ${cause.message ?: "Internal Server Error"}",
|
||||
// status = HttpStatusCode.InternalServerError
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// status(HttpStatusCode.NotFound) { call, _ ->
|
||||
// call.respondText(
|
||||
// text = "404: Page Not Found",
|
||||
// status = HttpStatusCode.NotFound
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/**
|
||||
* Configures all routes for the application
|
||||
*/
|
||||
private fun Application.configureRouting() {
|
||||
routing {
|
||||
// Health check endpoint
|
||||
get("/health") {
|
||||
call.respondText("OK")
|
||||
}
|
||||
|
||||
// Root endpoint with basic information
|
||||
get("/") {
|
||||
// Read application info from config if available
|
||||
val appName = application.environment.config.propertyOrNull("application.name")?.getString() ?: "Meldestelle API Server"
|
||||
val appVersion = application.environment.config.propertyOrNull("application.version")?.getString() ?: "1.0.0"
|
||||
val appEnv = application.environment.config.propertyOrNull("application.environment")?.getString() ?: "development"
|
||||
|
||||
call.respondText("$appName v$appVersion - Running in $appEnv mode")
|
||||
}
|
||||
|
||||
// API routes can be organized in separate files and included here
|
||||
// Example: registerUserRoutes()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package at.mocode.server.plugins
|
||||
|
||||
import at.mocode.server.tables.ArtikelTable
|
||||
import at.mocode.server.tables.LizenzenTable
|
||||
import at.mocode.server.tables.PersonenTable
|
||||
import at.mocode.server.tables.PferdeTable
|
||||
import at.mocode.server.tables.PlaetzeTable
|
||||
import at.mocode.server.tables.TurniereTable
|
||||
import at.mocode.server.tables.VeranstaltungenTable
|
||||
import at.mocode.server.tables.VereineTable
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.config.*
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Configures the database connection based on the environment.
|
||||
* Supports three environments:
|
||||
* 1. Test environment - Uses in-memory H2 database
|
||||
* 2. Development environment - Uses in-memory H2 database
|
||||
* 3. Production environment - Uses PostgreSQL database
|
||||
*
|
||||
* @param application The Ktor application instance to read configuration from
|
||||
*/
|
||||
fun Application.configureDatabase() {
|
||||
val log = LoggerFactory.getLogger("DatabaseInitialization")
|
||||
var connectionSuccessful = false
|
||||
|
||||
// Environment detection
|
||||
val isTestEnvironment = System.getProperty("isTestEnvironment")?.toBoolean() ?: false
|
||||
val dbHostFromEnv = System.getenv("DB_HOST")
|
||||
val isIdeaEnvironment = (dbHostFromEnv == null)
|
||||
|
||||
// Get database configuration from application.yaml if available
|
||||
val dbConfig = try {
|
||||
environment.config.config("database")
|
||||
} catch (e: ApplicationConfigurationException) {
|
||||
log.warn("No database configuration found in application.yaml, using environment variables")
|
||||
null
|
||||
}
|
||||
|
||||
when {
|
||||
isTestEnvironment -> {
|
||||
configureTestDatabase(log)
|
||||
connectionSuccessful = true
|
||||
}
|
||||
isIdeaEnvironment -> {
|
||||
configureDevelopmentDatabase(log)
|
||||
connectionSuccessful = true
|
||||
}
|
||||
else -> {
|
||||
connectionSuccessful = configureProductionDatabase(log, dbConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize schema if connection was successful
|
||||
if (connectionSuccessful) {
|
||||
initializeSchema(log, isTestEnvironment, isIdeaEnvironment)
|
||||
} else {
|
||||
log.error("No database connection established. Schema initialization skipped.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an in-memory H2 database for testing
|
||||
*/
|
||||
private fun configureTestDatabase(log: org.slf4j.Logger): Boolean {
|
||||
log.info("Test environment detected, using in-memory H2 database (test)...")
|
||||
return try {
|
||||
Database.connect(
|
||||
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
|
||||
driver = "org.h2.Driver",
|
||||
user = "sa",
|
||||
password = ""
|
||||
)
|
||||
log.info("Connected to H2 (test) successfully.")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to connect to H2 (test)!", e)
|
||||
throw e // Rethrow to fail the test
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an in-memory H2 database for development
|
||||
*/
|
||||
private fun configureDevelopmentDatabase(log: org.slf4j.Logger): Boolean {
|
||||
log.info("Development environment detected, using in-memory H2 database (dev)...")
|
||||
return try {
|
||||
Database.connect(
|
||||
url = "jdbc:h2:mem:dev;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
|
||||
driver = "org.h2.Driver",
|
||||
user = "sa",
|
||||
password = ""
|
||||
)
|
||||
log.info("Connected to H2 (dev) successfully.")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to connect to H2 (dev)!", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a PostgreSQL database for production
|
||||
*/
|
||||
private fun configureProductionDatabase(log: org.slf4j.Logger, dbConfig: ApplicationConfig?): Boolean {
|
||||
log.info("Production environment detected, connecting to PostgreSQL...")
|
||||
|
||||
// Get database configuration from application.yaml or environment variables
|
||||
val dbHost = dbConfig?.propertyOrNull("host")?.getString() ?: System.getenv("DB_HOST")
|
||||
?: error("Database host not configured")
|
||||
val dbPort = dbConfig?.propertyOrNull("port")?.getString() ?: System.getenv("DB_PORT") ?: "5432"
|
||||
val dbName = dbConfig?.propertyOrNull("name")?.getString() ?: System.getenv("DB_NAME")
|
||||
?: error("Database name not configured")
|
||||
val dbUser = dbConfig?.propertyOrNull("user")?.getString() ?: System.getenv("DB_USER")
|
||||
?: error("Database user not configured")
|
||||
val dbPassword = dbConfig?.propertyOrNull("password")?.getString() ?: System.getenv("DB_PASSWORD")
|
||||
?: error("Database password not configured")
|
||||
|
||||
// Connection pool configuration
|
||||
val maxPoolSize = dbConfig?.propertyOrNull("pool.maxSize")?.getString()?.toIntOrNull()
|
||||
?: System.getenv("DB_POOL_SIZE")?.toIntOrNull() ?: 10
|
||||
val minIdle = dbConfig?.propertyOrNull("pool.minIdle")?.getString()?.toIntOrNull() ?: 2
|
||||
val idleTimeout = dbConfig?.propertyOrNull("pool.idleTimeout")?.getString()?.toLongOrNull() ?: 10000L
|
||||
val connectionTimeout = dbConfig?.propertyOrNull("pool.connectionTimeout")?.getString()?.toLongOrNull() ?: 5000L
|
||||
val maxLifetime = dbConfig?.propertyOrNull("pool.maxLifetime")?.getString()?.toLongOrNull() ?: 1800000L
|
||||
|
||||
val jdbcURL = "jdbc:postgresql://$dbHost:$dbPort/$dbName"
|
||||
log.info("Attempting to connect to PostgreSQL at URL: {}", jdbcURL)
|
||||
|
||||
return try {
|
||||
val hikariConfig = HikariConfig().apply {
|
||||
driverClassName = "org.postgresql.Driver"
|
||||
jdbcUrl = jdbcURL
|
||||
username = dbUser
|
||||
password = dbPassword
|
||||
maximumPoolSize = maxPoolSize
|
||||
minimumIdle = minIdle
|
||||
this.idleTimeout = idleTimeout
|
||||
this.connectionTimeout = connectionTimeout
|
||||
this.maxLifetime = maxLifetime
|
||||
|
||||
// Additional security and performance settings
|
||||
addDataSourceProperty("cachePrepStmts", "true")
|
||||
addDataSourceProperty("prepStmtCacheSize", "250")
|
||||
addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
||||
addDataSourceProperty("useServerPrepStmts", "true")
|
||||
|
||||
// Connection validation
|
||||
connectionTestQuery = "SELECT 1"
|
||||
validationTimeout = TimeUnit.SECONDS.toMillis(5)
|
||||
|
||||
// Leak detection
|
||||
leakDetectionThreshold = TimeUnit.SECONDS.toMillis(60)
|
||||
|
||||
validate()
|
||||
}
|
||||
|
||||
val dataSource = HikariDataSource(hikariConfig)
|
||||
Database.connect(dataSource)
|
||||
log.info("PostgreSQL connection pool initialized successfully!")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize PostgreSQL connection pool!", e)
|
||||
throw e // Rethrow in production
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database schema
|
||||
*/
|
||||
private fun initializeSchema(log: org.slf4j.Logger, isTestEnvironment: Boolean, isIdeaEnvironment: Boolean) {
|
||||
transaction {
|
||||
log.info("Initializing/Verifying database schema...")
|
||||
try {
|
||||
// Create all tables if they don't exist
|
||||
SchemaUtils.create(
|
||||
VereineTable,
|
||||
PersonenTable,
|
||||
PferdeTable,
|
||||
VeranstaltungenTable,
|
||||
TurniereTable,
|
||||
ArtikelTable,
|
||||
PlaetzeTable,
|
||||
LizenzenTable
|
||||
// Add more tables here if needed
|
||||
)
|
||||
log.info("Database schema initialized successfully.")
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize database schema!", e)
|
||||
// In production, a schema initialization failure is critical
|
||||
if (!isTestEnvironment && !isIdeaEnvironment) {
|
||||
throw e
|
||||
}
|
||||
// In test/development, just log the error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Optimized version of ArtikelTable
|
||||
* Changes:
|
||||
* - Changed unique index on bezeichnung to non-unique
|
||||
* - Added init block for defining indexes
|
||||
*/
|
||||
object ArtikelTable : Table(name = "artikel") {
|
||||
val id = uuid(name = "id")
|
||||
val bezeichnung = varchar(name = "bezeichnung", length = 255)
|
||||
val preis = varchar(name = "preis", length = 50)
|
||||
val einheit = varchar(name = "einheit", length = 50)
|
||||
val istVerbandsabgabe = bool(name = "ist_verbandsabgabe").default(defaultValue = false)
|
||||
val createdAt = timestamp(name = "created_at")
|
||||
val updatedAt = timestamp(name = "updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, bezeichnung)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import at.mocode.server.enums.LizenzTyp
|
||||
import at.mocode.server.enums.Sparte
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date
|
||||
|
||||
/**
|
||||
* Optimized version of LizenzenTable
|
||||
* Changes:
|
||||
* - Added proper imports for enums
|
||||
* - Uncommented the sparte field
|
||||
* - Added index for lizenzTyp and gueltigBisJahr
|
||||
*/
|
||||
object LizenzenTable : Table(name = "lizenzen") {
|
||||
val id = uuid(name = "id")
|
||||
val personId = uuid(name = "person_id").references(PersonenTable.id)
|
||||
val lizenzTyp = enumerationByName(name = "lizenz_typ", length = 50, klass = LizenzTyp::class)
|
||||
val stufe = varchar(name = "stufe", 20).nullable()
|
||||
val sparte = enumerationByName(name = "sparte", length = 50, klass = Sparte::class).nullable()
|
||||
val gueltigBisJahr = integer(name = "gueltig_bis_jahr").nullable()
|
||||
val ausgestelltAm = date(name = "ausgestellt_am").nullable()
|
||||
|
||||
override val primaryKey = PrimaryKey(firstColumn = id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, personId)
|
||||
index(isUnique = false, lizenzTyp, gueltigBisJahr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import at.mocode.server.enums.Geschlecht
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Optimized version of PersonenTable
|
||||
* Changes:
|
||||
* - Added proper imports for enums
|
||||
* - Replaced inline comments with KDoc
|
||||
* - Fixed the unique index on nachname+vorname to be non-unique
|
||||
* - Added indexes for email and stammVereinId for common queries
|
||||
*/
|
||||
object PersonenTable : Table(name = "personen") {
|
||||
val id = uuid(name = "id")
|
||||
val oepsSatzNr = varchar(name = "oeps_satz_nr", length = 10).uniqueIndex().nullable()
|
||||
val nachname = varchar(name = "nachname", length = 100)
|
||||
val vorname = varchar(name = "vorname", length = 100)
|
||||
val titel = varchar(name = "titel", length = 50).nullable()
|
||||
val geburtsdatum = date(name = "geburtsdatum").nullable()
|
||||
val geschlecht = enumerationByName(name = "geschlecht", length = 10, klass = Geschlecht::class).nullable()
|
||||
val nationalitaet = varchar(name = "nationalitaet", length = 3).nullable()
|
||||
val email = varchar(name = "email", length = 255).nullable()
|
||||
val telefon = varchar(name = "telefon", length = 50).nullable()
|
||||
val adresse = varchar(name = "adresse", length = 255).nullable()
|
||||
val plz = varchar(name = "plz", length = 10).nullable()
|
||||
val ort = varchar(name = "ort", length = 100).nullable()
|
||||
val stammVereinId = uuid(name = "stamm_verein_id").references(ref = VereineTable.id).nullable()
|
||||
val mitgliedsNummerIntern = varchar(name = "mitglieds_nr_intern", length = 50).nullable()
|
||||
val letzteZahlungJahr = integer(name = "letzte_zahlung_jahr").nullable()
|
||||
val feiId = varchar(name = "fei_id", length = 20).nullable()
|
||||
val istGesperrt = bool(name = "ist_gesperrt").default(defaultValue = false)
|
||||
val sperrGrund = text(name = "sperr_grund").nullable()
|
||||
val rollenCsv = text(name = "rollen_csv").nullable()
|
||||
val qualifikationenRichterCsv = text(name = "qualifikationen_richter_csv").nullable()
|
||||
val qualifikationenParcoursbauerCsv = text(name = "qualifikationen_parcoursbauer_csv").nullable()
|
||||
val istAktiv = bool(name = "ist_aktiv").default(true)
|
||||
val createdAt = timestamp(name = "created_at")
|
||||
val updatedAt = timestamp(name = "updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(firstColumn = id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, nachname, vorname)
|
||||
index(isUnique = false, nachname)
|
||||
index(isUnique = false, email)
|
||||
index(isUnique = false, stammVereinId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import at.mocode.server.enums.GeschlechtPferd
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Optimized version of PferdeTable
|
||||
* Changes:
|
||||
* - Added proper imports for enums
|
||||
* - Added indexes for foreign key fields
|
||||
* - Added index for common search fields (name, rasse)
|
||||
*/
|
||||
object PferdeTable : Table(name = "pferde") {
|
||||
val id = uuid(name = "id")
|
||||
val oepsKopfNr = varchar(name = "oeps_kopf_nr", length = 10).uniqueIndex().nullable()
|
||||
val oepsSatzNr = varchar(name = "oeps_satz_nr", length = 15).uniqueIndex().nullable()
|
||||
val name = varchar(name = "name", length = 255)
|
||||
val lebensnummer = varchar(name = "lebensnummer", length = 20).nullable()
|
||||
val feiPassNr = varchar(name = "fei_pass_nr", length = 20).nullable()
|
||||
val geschlecht = enumerationByName(name = "geschlecht", length = 10, klass = GeschlechtPferd::class).nullable()
|
||||
val geburtsjahr = integer(name = "geburtsjahr").nullable()
|
||||
val rasse = varchar(name = "rasse", length = 100).nullable()
|
||||
val farbe = varchar(name = "farbe", length = 50).nullable()
|
||||
val vaterName = varchar(name = "vater_name", length = 255).nullable()
|
||||
val mutterName = varchar(name = "mutter_name", length = 255).nullable()
|
||||
val mutterVaterName = varchar(name = "mutter_vater_name", length = 255).nullable()
|
||||
val besitzerId = uuid(name = "besitzer_id").references(ref = PersonenTable.id).nullable()
|
||||
val verantwortlichePersonId = uuid(name = "verantwortliche_person_id").references(ref = PersonenTable.id).nullable()
|
||||
val heimatVereinId = uuid(name = "heimat_verein_id").references(ref = VereineTable.id).nullable()
|
||||
val letzteZahlungJahrOeps = integer(name = "letzte_zahlung_jahr_oeps").nullable()
|
||||
val stockmassCm = integer(name = "stockmass_cm").nullable()
|
||||
val istAktiv = bool(name = "ist_aktiv").default(defaultValue = true)
|
||||
val createdAt = timestamp(name = "created_at")
|
||||
val updatedAt = timestamp(name = "updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(firstColumn = id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, name)
|
||||
index(isUnique = false, rasse)
|
||||
index(isUnique = false, besitzerId)
|
||||
index(isUnique = false, verantwortlichePersonId)
|
||||
index(isUnique = false, heimatVereinId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import at.mocode.server.enums.PlatzTyp
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
/**
|
||||
* Optimized version of PlaetzeTable
|
||||
* Changes:
|
||||
* - Added proper imports for enums
|
||||
* - Added index for name field
|
||||
*/
|
||||
object PlaetzeTable : Table(name = "plaetze") {
|
||||
val id = uuid(name = "id")
|
||||
val turnierId = uuid(name = "turnier_id").references(ref = TurniereTable.id)
|
||||
val name = varchar(name = "name", length = 100)
|
||||
val dimension = varchar(name = "dimension", length = 50).nullable()
|
||||
val boden = varchar(name = "boden", length = 100).nullable()
|
||||
val typ = enumerationByName(name = "typ", length = 20, klass = PlatzTyp::class)
|
||||
|
||||
override val primaryKey = PrimaryKey(firstColumn = id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, turnierId)
|
||||
index(isUnique = false, name)
|
||||
index(isUnique = false, typ)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date // Für kotlinx-datetime LocalDate
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.datetime // Für kotlinx-datetime LocalDateTime
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp // Für kotlinx-datetime Instant
|
||||
|
||||
/**
|
||||
* Optimized version of TurniereTable
|
||||
* Changes:
|
||||
* - Added proper imports for enums
|
||||
* - Added indexes for foreign key fields and common search fields
|
||||
* - Added init block for defining indexes
|
||||
*/
|
||||
object TurniereTable : Table(name = "turniere") { // Name der Tabelle in PostgreSQL
|
||||
val id = uuid(name = "id")
|
||||
val veranstaltungId = uuid(name = "veranstaltung_id").references(ref = VeranstaltungenTable.id)
|
||||
val oepsTurnierNr = varchar(name = "oeps_turnier_nr", length = 15).uniqueIndex()
|
||||
val titel = varchar(name = "titel", length = 255)
|
||||
val untertitel = varchar(name = "untertitel", length = 500).nullable()
|
||||
val datumVon = date(name = "datum_von")
|
||||
val datumBis = date(name = "datum_bis")
|
||||
val nennungsschluss = datetime(name = "nennungsschluss").nullable()
|
||||
val nennungsArtCsv = text(name = "nennungs_art_csv").nullable()
|
||||
val nennungsHinweis = text(name = "nennungs_hinweis").nullable()
|
||||
val eigenesNennsystemUrl = varchar(name = "eigenes_nennsystem_url", length = 500).nullable()
|
||||
val nenngeld = varchar(name = "nenngeld", length = 50).nullable()
|
||||
val startgeldStandard = varchar(name = "startgeld_standard", length = 50).nullable()
|
||||
val turnierleiterId = uuid(name = "turnierleiter_id").references(ref = PersonenTable.id).nullable()
|
||||
val turnierbeauftragterId = uuid(name = "turnierbeauftragter_id").references(ref = PersonenTable.id).nullable()
|
||||
val richterIdsCsv = text(name = "richter_ids_csv").nullable()
|
||||
val parcoursbauerIdsCsv = text(name = "parcoursbauer_ids_csv").nullable()
|
||||
val parcoursAssistentIdsCsv = text(name = "parcours_assistent_ids_csv").nullable()
|
||||
val tierarztInfos = text(name = "tierarzt_infos").nullable()
|
||||
val hufschmiedInfo = text(name = "hufschmied_info").nullable()
|
||||
val meldestelleVerantwortlicherId = uuid(name = "meldestelle_verantwortlicher_id").references(ref = PersonenTable.id).nullable()
|
||||
val meldestelleTelefon = varchar(name = "meldestelle_telefon", length = 50).nullable()
|
||||
val meldestelleOeffnungszeiten = varchar(name = "meldestelle_oeffnungszeiten", length = 255).nullable()
|
||||
val ergebnislistenUrl = varchar(name = "ergebnislisten_url", length = 500).nullable()
|
||||
val createdAt = timestamp(name = "created_at")
|
||||
val updatedAt = timestamp(name = "updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(firstColumn = id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, veranstaltungId)
|
||||
index(isUnique = false, datumVon, datumBis)
|
||||
index(isUnique = false, titel)
|
||||
index(isUnique = false, turnierleiterId)
|
||||
index(isUnique = false, turnierbeauftragterId)
|
||||
index(isUnique = false, meldestelleVerantwortlicherId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import at.mocode.server.enums.VeranstalterTyp
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Optimized version of VeranstaltungenTable
|
||||
* Changes:
|
||||
* - Added proper imports for enums
|
||||
* - Added indexes for common search fields
|
||||
* - Added init block for defining indexes
|
||||
*/
|
||||
object VeranstaltungenTable : Table(name = "veranstaltungen") {
|
||||
val id = uuid(name = "id")
|
||||
val name = varchar(name = "name", length = 255)
|
||||
val datumVon = date(name = "datum_von")
|
||||
val datumBis = date(name = "datum_bis")
|
||||
val veranstalterName = varchar(name = "veranstalter_name", length = 255)
|
||||
val veranstalterOepsNummer = varchar(name = "veranstalter_oeps_nr", length = 10).nullable()
|
||||
val veranstalterTyp =
|
||||
enumerationByName(name = "veranstalter_typ", length = 20, klass = VeranstalterTyp::class).default(
|
||||
VeranstalterTyp.UNBEKANNT
|
||||
)
|
||||
val veranstaltungsortName = varchar(name = "veranstaltungsort_name", length = 255)
|
||||
val veranstaltungsortAdresse = varchar(name = "veranstaltungsort_adresse", length = 500)
|
||||
val kontaktpersonName = varchar(name = "kontaktperson_name", length = 200).nullable()
|
||||
val kontaktTelefon = varchar(name = "kontakt_telefon", length = 50).nullable()
|
||||
val kontaktEmail = varchar(name = "kontakt_email", length = 255).nullable()
|
||||
val webseite = varchar(name = "webseite", length = 500).nullable()
|
||||
val logoUrl = varchar(name = "logo_url", length = 500).nullable()
|
||||
val anfahrtsplanInfo = text(name = "anfahrtsplan_info").nullable()
|
||||
val sponsorInfosCsv = text(name = "sponsor_infos_csv").nullable()
|
||||
val dsgvoText = text(name = "dsgvo_text").nullable()
|
||||
val haftungsText = text(name = "haftungs_text").nullable()
|
||||
val sonstigeBesondereBestimmungen = text(name = "sonstige_bestimmungen").nullable()
|
||||
val createdAt = timestamp("created_at")
|
||||
val updatedAt = timestamp("updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, name)
|
||||
index(isUnique = false, datumVon, datumBis)
|
||||
index(isUnique = false, veranstalterName)
|
||||
index(isUnique = false, veranstaltungsortName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package at.mocode.server.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Optimized version of VereineTable
|
||||
* Changes:
|
||||
* - Added indexes for common search fields (name, bundesland)
|
||||
* - Added init block for defining indexes
|
||||
*/
|
||||
object VereineTable : Table(name = "vereine") {
|
||||
val id = uuid(name = "id")
|
||||
val oepsVereinsNr = varchar(name = "oeps_vereins_nr", length = 10).uniqueIndex()
|
||||
val name = varchar(name = "name", length = 255)
|
||||
val kuerzel = varchar(name = "kuerzel", length = 50).nullable()
|
||||
val bundesland = varchar(name = "bundesland", length = 10).nullable()
|
||||
val adresse = varchar(name = "adresse", length = 255).nullable()
|
||||
val plz = varchar(name = "plz", length = 10).nullable()
|
||||
val ort = varchar(name = "ort", length = 100).nullable()
|
||||
val email = varchar(name = "email", length = 255).nullable()
|
||||
val telefon = varchar(name = "telefon", length = 50).nullable()
|
||||
val webseite = varchar(name = "webseite", length = 500).nullable()
|
||||
val istAktiv = bool(name = "ist_aktiv").default(defaultValue = true)
|
||||
val createdAt = timestamp(name = "created_at")
|
||||
val updatedAt = timestamp(name = "updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(firstColumn = id)
|
||||
|
||||
init {
|
||||
index(isUnique = false, name)
|
||||
index(isUnique = false, bundesland)
|
||||
index(isUnique = false, ort)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
// --- Tabelle für Artikel (falls noch nicht vorhanden) ---
|
||||
object ArtikelTable : Table("artikel") {
|
||||
val id = uuid("id")
|
||||
val bezeichnung = varchar("bezeichnung", 255).uniqueIndex() // Bezeichnung sollte eindeutig sein?
|
||||
|
||||
// Preis als Varchar speichern wegen KMP BigDecimal
|
||||
val preis = varchar("preis", 50)
|
||||
val einheit = varchar("einheit", 50)
|
||||
val istVerbandsabgabe = bool("ist_verbandsabgabe").default(false)
|
||||
val createdAt = timestamp("created_at")
|
||||
val updatedAt = timestamp("updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import at.mocode.model.enums.LizenzTyp
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date
|
||||
|
||||
// --- Tabelle für Lizenzen (Beispiel für Normalisierung von List<LizenzInfo>) ---
|
||||
// Diese Tabelle wäre die "sauberere" Lösung für die Speicherung der Lizenzen aus Person.
|
||||
// Statt sie als JSON/Text in PersonenTable zu speichern.
|
||||
|
||||
object LizenzenTable : Table("lizenzen") {
|
||||
val id = uuid("id")
|
||||
val personId = uuid("person_id").references(PersonenTable.id) // FK zur Person
|
||||
val lizenzTyp = enumerationByName("lizenz_typ", 50, LizenzTyp::class)
|
||||
val stufe = varchar("stufe", 20).nullable()
|
||||
// val sparte = enumerationByName("sparte", 50, Sparte::class).nullable() // Sparte Enum nötig
|
||||
val gueltigBisJahr = integer("gueltig_bis_jahr").nullable()
|
||||
val ausgestelltAm = date("ausgestellt_am").nullable()
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
init {
|
||||
index(false, personId) // Index auf personId für schnelle Suche
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import at.mocode.model.enums.Geschlecht
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
// --- Tabelle für Personen (Reiter, Richter, Funktionäre etc.) ---
|
||||
object PersonenTable : Table("personen") {
|
||||
val id = uuid("id")
|
||||
val oepsSatzNr = varchar("oeps_satz_nr", 10).uniqueIndex().nullable() // OEPS SatzNr ist eindeutig, wenn vorhanden
|
||||
val nachname = varchar("nachname", 100)
|
||||
val vorname = varchar("vorname", 100)
|
||||
val titel = varchar("titel", 50).nullable()
|
||||
val geburtsdatum = date("geburtsdatum").nullable() // kotlinx.datetime.LocalDate
|
||||
// Speichert den Enum-Namen als String, max 10 Zeichen lang
|
||||
val geschlecht = enumerationByName("geschlecht", 10, Geschlecht::class).nullable()
|
||||
val nationalitaet = varchar("nationalitaet", 3).nullable() // AUT, GER, ...
|
||||
val email = varchar("email", 255).nullable()
|
||||
val telefon = varchar("telefon", 50).nullable()
|
||||
val adresse = varchar("adresse", 255).nullable()
|
||||
val plz = varchar("plz", 10).nullable()
|
||||
val ort = varchar("ort", 100).nullable()
|
||||
// Fremdschlüssel zur Vereine Tabelle für die Stamm-Mitgliedschaft
|
||||
val stammVereinId = uuid("stamm_verein_id").references(VereineTable.id).nullable()
|
||||
val mitgliedsNummerIntern = varchar("mitglieds_nr_intern", 50).nullable()
|
||||
val letzteZahlungJahr = integer("letzte_zahlung_jahr").nullable()
|
||||
val feiId = varchar("fei_id", 20).nullable()
|
||||
val istGesperrt = bool("ist_gesperrt").default(false)
|
||||
val sperrGrund = text("sperr_grund").nullable() // Längerer Text möglich
|
||||
|
||||
// Listen/Sets -> Als Text speichern für Einfachheit, später evtl. normalisieren
|
||||
// Rollen (Set<FunktionaerRolle>) -> CSV oder JSON in Textfeld
|
||||
val rollenCsv = text("rollen_csv").nullable()
|
||||
// Lizenzen (List<LizenzInfo>) -> Eigene Tabelle "LizenzenTable" wäre besser! Vorerst hier weglassen oder als JSONB.
|
||||
// val lizenzenJson = jsonb("lizenzen", ...) // Benötigt spezielle Exposed/Postgres Konfiguration
|
||||
// Qualifikationen (List<String>) -> CSV
|
||||
val qualifikationenRichterCsv = text("qualifikationen_richter_csv").nullable()
|
||||
val qualifikationenParcoursbauerCsv = text("qualifikationen_parcoursbauer_csv").nullable()
|
||||
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val createdAt = timestamp("created_at")
|
||||
val updatedAt = timestamp("updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
// Index für schnelles Suchen nach Namen
|
||||
init {
|
||||
index(true, nachname, vorname) // Eindeutiger Index auf Nachname+Vorname? Eher nicht. Normaler Index: index(false, ...)
|
||||
index(false, nachname) // Index auf Nachname allein
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import at.mocode.model.enums.GeschlechtPferd
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
// --- Tabelle für Pferde ---
|
||||
object PferdeTable : Table("pferde") {
|
||||
val id = uuid("id")
|
||||
val oepsKopfNr = varchar("oeps_kopf_nr", 10).uniqueIndex().nullable() // KopfNr sollte eindeutig sein, wenn vorhanden
|
||||
val oepsSatzNr = varchar("oeps_satz_nr", 15).uniqueIndex().nullable() // 10-stellige Nr, Puffer; Eindeutig wenn vorhanden
|
||||
val name = varchar("name", 255)
|
||||
val lebensnummer = varchar("lebensnummer", 20).nullable() // UELN etc.
|
||||
val feiPassNr = varchar("fei_pass_nr", 20).nullable()
|
||||
val geschlecht = enumerationByName("geschlecht", 10, GeschlechtPferd::class).nullable()
|
||||
val geburtsjahr = integer("geburtsjahr").nullable()
|
||||
val rasse = varchar("rasse", 100).nullable()
|
||||
val farbe = varchar("farbe", 50).nullable()
|
||||
val vaterName = varchar("vater_name", 255).nullable()
|
||||
val mutterName = varchar("mutter_name", 255).nullable()
|
||||
val mutterVaterName = varchar("mutter_vater_name", 255).nullable()
|
||||
// Fremdschlüssel zu Personen (Besitzer, Verantwortlicher) und Vereine (Heimatverein)
|
||||
val besitzerId = uuid("besitzer_id").references(PersonenTable.id).nullable()
|
||||
val verantwortlichePersonId = uuid("verantwortliche_person_id").references(PersonenTable.id).nullable()
|
||||
val heimatVereinId = uuid("heimat_verein_id").references(VereineTable.id).nullable()
|
||||
val letzteZahlungJahrOeps = integer("letzte_zahlung_jahr_oeps").nullable()
|
||||
val stockmassCm = integer("stockmass_cm").nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val createdAt = timestamp("created_at")
|
||||
val updatedAt = timestamp("updated_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
// Index für Pferdenamen
|
||||
init {
|
||||
index(false, name)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import at.mocode.model.enums.PlatzTyp
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
// --- Tabelle für Plätze (Austragungs- & Vorbereitungsplätze) ---
|
||||
// Wichtig: Ein Platz gehört immer zu einem spezifischen Turnier!
|
||||
object PlaetzeTable : Table("plaetze") {
|
||||
val id = uuid("id")
|
||||
|
||||
// Fremdschlüssel zur Turniere Tabelle
|
||||
val turnierId = uuid("turnier_id").references(TurniereTable.id) // Annahme: TurniereTable existiert
|
||||
|
||||
val name = varchar("name", 100) // z.B. "Sandplatz Austragung", "Halle Vorbereitung"
|
||||
val dimension = varchar("dimension", 50).nullable() // z.B. "20x40m", "50x100m"
|
||||
val boden = varchar("boden", 100).nullable() // z.B. "Sand", "Gras", "Sand/Vlies"
|
||||
|
||||
// Typ des Platzes (Austragung, Vorbereitung etc.)
|
||||
val typ = enumerationByName("typ", 20, PlatzTyp::class)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
index(false, turnierId) // Index auf turnierId für schnelle Abfragen pro Turnier
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date // Für kotlinx-datetime LocalDate
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.datetime // Für kotlinx-datetime LocalDateTime
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp // Für kotlinx-datetime Instant
|
||||
|
||||
// Annahme: Es gibt bereits oder wird geben:
|
||||
// object VeranstaltungenTable : Table("veranstaltungen") { val id = uuid("id") /* ... */ }
|
||||
// object PersonenTable : Table("personen") { val id = uuid("id") /* ... */ }
|
||||
// Diese sind für die Foreign Key Constraints notwendig.
|
||||
|
||||
/**
|
||||
* Exposed Table Definition für die Turnier-Entität.
|
||||
* Spiegelt die Struktur von shared/.../Turnier.kt wider.
|
||||
*/
|
||||
object TurniereTable : Table("turniere") { // Name der Tabelle in PostgreSQL
|
||||
|
||||
// Primärschlüssel (KMP Uuid -> DB UUID)
|
||||
val id = uuid("id") // Exposed bietet uuid() für UUIDs
|
||||
|
||||
// Foreign Key zur Veranstaltungstabelle
|
||||
val veranstaltungId = uuid("veranstaltung_id").references(VeranstaltungenTable.id)
|
||||
|
||||
// OEPS Turniernummer (kann Buchstaben enthalten? Besser Varchar)
|
||||
val oepsTurnierNr = varchar("oeps_turnier_nr", 15).uniqueIndex() // Eindeutig machen?
|
||||
|
||||
// Titel und Untertitel
|
||||
val titel = varchar("titel", 255)
|
||||
val untertitel = varchar("untertitel", 500).nullable()
|
||||
|
||||
// Datumswerte (kotlinx -> DB Date/Timestamp)
|
||||
val datumVon = date("datum_von")
|
||||
val datumBis = date("datum_bis")
|
||||
val nennungsschluss = datetime("nennungsschluss").nullable()
|
||||
|
||||
// NennungsArt Liste -> Einfache Speicherung als CSV-String für den Anfang
|
||||
// Bessere Lösung später: Eigene Zwischentabelle (TurnierNennungsArtMapping)
|
||||
val nennungsArtCsv = text("nennungs_art_csv").nullable() // Z.B. "EIGENES_ONLINE,DIREKT_VERANSTALTER_TELEFON"
|
||||
|
||||
val nennungsHinweis = text("nennungs_hinweis").nullable()
|
||||
val eigenesNennsystemUrl = varchar("eigenes_nennsystem_url", 500).nullable()
|
||||
|
||||
// Geldwerte (KMP BigDecimal -> DB Varchar)
|
||||
// Konvertierung muss im Code (Service-Schicht) erfolgen!
|
||||
// Alternative: decimal("nenngeld", 10, 2).nullable() - erfordert Konvertierungslogik KMP<->JVM BigDecimal
|
||||
val nenngeld = varchar("nenngeld", 50).nullable()
|
||||
val startgeldStandard = varchar("startgeld_standard", 50).nullable()
|
||||
|
||||
// Plätze (List<Platz>) -> Besser in eigener Tabelle "PlaetzeTable" mit FK zu Turnier.
|
||||
// Hier *nicht* direkt speichern.
|
||||
|
||||
// Personen-Referenzen (FKs)
|
||||
val turnierleiterId = uuid("turnierleiter_id").references(PersonenTable.id).nullable()
|
||||
val turnierbeauftragterId = uuid("turnierbeauftragter_id").references(PersonenTable.id).nullable()
|
||||
|
||||
// Listen von Personen-IDs -> Einfache Speicherung als CSV-String für den Anfang
|
||||
// Bessere Lösung später: Eigene Zwischentabellen (TurnierRichterMapping, TurnierParcoursbauerMapping etc.)
|
||||
val richterIdsCsv = text("richter_ids_csv").nullable() // z.B. "uuid1,uuid2,uuid3"
|
||||
val parcoursbauerIdsCsv = text("parcoursbauer_ids_csv").nullable()
|
||||
val parcoursAssistentIdsCsv = text("parcours_assistent_ids_csv").nullable()
|
||||
|
||||
// Info-Texte
|
||||
val tierarztInfos = text("tierarzt_infos").nullable()
|
||||
val hufschmiedInfo = text("hufschmied_info").nullable()
|
||||
|
||||
// Meldestelle
|
||||
val meldestelleVerantwortlicherId = uuid("meldestelle_verantwortlicher_id").references(PersonenTable.id).nullable()
|
||||
val meldestelleTelefon = varchar("meldestelle_telefon", 50).nullable()
|
||||
val meldestelleOeffnungszeiten = varchar("meldestelle_oeffnungszeiten", 255).nullable()
|
||||
val ergebnislistenUrl = varchar("ergebnislisten_url", 500).nullable()
|
||||
|
||||
// Komplexe Listen -> Besser eigene Tabellen oder JSONB (PostgreSQL)
|
||||
// Hier *nicht* direkt speichern:
|
||||
// - verfuegbareArtikel: List<Artikel> -> Eigene Tabelle TurnierArtikelMapping
|
||||
// - meisterschaftRefs: List<MeisterschaftReferenz> -> Eigene Tabelle TurnierMeisterschaftMapping
|
||||
|
||||
// Timestamps (kotlinx Instant -> DB Timestamp mit Zeitzone)
|
||||
val createdAt = timestamp("created_at")
|
||||
val updatedAt = timestamp("updated_at")
|
||||
|
||||
// Primärschlüssel definieren
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import at.mocode.model.enums.VeranstalterTyp
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.date
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
// --- Tabelle für Veranstaltungen ---
|
||||
object VeranstaltungenTable : Table("veranstaltungen") {
|
||||
val id = uuid("id") // KMP Uuid -> DB UUID
|
||||
val name = varchar("name", 255)
|
||||
val datumVon = date("datum_von") // kotlinx.datetime.LocalDate
|
||||
val datumBis = date("datum_bis") // kotlinx.datetime.LocalDate
|
||||
|
||||
// Veranstalter Infos
|
||||
val veranstalterName = varchar("veranstalter_name", 255)
|
||||
val veranstalterOepsNummer = varchar("veranstalter_oeps_nr", 10).nullable()
|
||||
val veranstalterTyp =
|
||||
enumerationByName("veranstalter_typ", 20, VeranstalterTyp::class).default(VeranstalterTyp.UNBEKANNT)
|
||||
|
||||
// Ort Infos
|
||||
val veranstaltungsortName = varchar("veranstaltungsort_name", 255)
|
||||
val veranstaltungsortAdresse = varchar("veranstaltungsort_adresse", 500)
|
||||
|
||||
// Kontakt Infos
|
||||
val kontaktpersonName = varchar("kontaktperson_name", 200).nullable()
|
||||
val kontaktTelefon = varchar("kontakt_telefon", 50).nullable()
|
||||
val kontaktEmail = varchar("kontakt_email", 255).nullable()
|
||||
|
||||
// Weitere Infos
|
||||
val webseite = varchar("webseite", 500).nullable()
|
||||
val logoUrl = varchar("logo_url", 500).nullable()
|
||||
val anfahrtsplanInfo = text("anfahrtsplan_info").nullable()
|
||||
|
||||
// Sponsoren als einfacher Text (CSV oder ähnlich)
|
||||
val sponsorInfosCsv = text("sponsor_infos_csv").nullable()
|
||||
|
||||
// Rechtliche Texte
|
||||
val dsgvoText = text("dsgvo_text").nullable()
|
||||
val haftungsText = text("haftungs_text").nullable()
|
||||
val sonstigeBesondereBestimmungen = text("sonstige_bestimmungen").nullable()
|
||||
|
||||
// Timestamps
|
||||
val createdAt = timestamp("created_at") // kotlinx.datetime.Instant
|
||||
val updatedAt = timestamp("updated_at") // kotlinx.datetime.Instant
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package at.mocode.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
|
||||
|
||||
// --- Tabelle für Vereine ---
|
||||
object VereineTable : Table("vereine") { // PostgreSQL Tabellenname
|
||||
val id = uuid("id") // KMP Uuid -> DB UUID
|
||||
val oepsVereinsNr = varchar("oeps_vereins_nr", 10).uniqueIndex() // Ist die OEPS Nummer eindeutig? Ja.
|
||||
val name = varchar("name", 255)
|
||||
val kuerzel = varchar("kuerzel", 50).nullable()
|
||||
val bundesland = varchar("bundesland", 10).nullable() // Kürzel wie NÖ, W, ST etc.
|
||||
val adresse = varchar("adresse", 255).nullable()
|
||||
val plz = varchar("plz", 10).nullable()
|
||||
val ort = varchar("ort", 100).nullable()
|
||||
val email = varchar("email", 255).nullable()
|
||||
val telefon = varchar("telefon", 50).nullable()
|
||||
val webseite = varchar("webseite", 500).nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val createdAt = timestamp("created_at") // kotlinx.datetime.Instant
|
||||
val updatedAt = timestamp("updated_at") // kotlinx.datetime.Instant
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,14 +1,73 @@
|
||||
# Grundkonfiguration für Ktor in YAML
|
||||
# Meldestelle Server Configuration
|
||||
ktor:
|
||||
deployment:
|
||||
# Der Port, auf dem der Server lauschen soll
|
||||
# Server port configuration
|
||||
port: 8081
|
||||
# port: ${PORT:8080} # Alternative: Nutzt Env-Variable PORT, sonst 8080
|
||||
# Optional für Entwicklung: Server bei Änderungen neu laden
|
||||
# watch:
|
||||
# - classes
|
||||
# - resources
|
||||
# Connection timeout in seconds
|
||||
connectionTimeout: 30
|
||||
# Maximum number of concurrent connections
|
||||
maxConnections: 1000
|
||||
# Enable development mode with hot-reload (only for development)
|
||||
watch:
|
||||
- classes
|
||||
- resources
|
||||
application:
|
||||
# Hier wird Ktor gesagt, welche Funktion die Konfiguration enthält
|
||||
modules:
|
||||
- at.mocode.ApplicationKt.module
|
||||
- at.mocode.server.ApplicationKt.module
|
||||
|
||||
# Database Configuration
|
||||
database:
|
||||
# Database driver (postgresql for production, h2 for development)
|
||||
driver: "${DB_DRIVER:postgresql}"
|
||||
# Database connection settings
|
||||
host: "${DB_HOST:localhost}"
|
||||
port: "${DB_PORT:5432}"
|
||||
name: "${DB_NAME:meldestelle}"
|
||||
user: "${DB_USER:postgres}"
|
||||
password: "${DB_PASSWORD:postgres}"
|
||||
# Connection pool settings
|
||||
pool:
|
||||
maxSize: "${DB_POOL_SIZE:10}"
|
||||
minIdle: "${DB_POOL_MIN_IDLE:2}"
|
||||
idleTimeout: 10000
|
||||
connectionTimeout: 5000
|
||||
maxLifetime: 1800000
|
||||
|
||||
# Security Configuration
|
||||
security:
|
||||
# JWT configuration
|
||||
jwt:
|
||||
issuer: "meldestelle-server"
|
||||
audience: "meldestelle-clients"
|
||||
realm: "meldestelle"
|
||||
# Secret should be set via environment variable in production
|
||||
secret: "${JWT_SECRET:dev-secret-key-change-in-production}"
|
||||
# Token validity duration in milliseconds (24 hours)
|
||||
validity: 86400000
|
||||
|
||||
# CORS Configuration
|
||||
cors:
|
||||
# Allow requests from these origins
|
||||
allowedHosts:
|
||||
- "localhost:3000"
|
||||
- "127.0.0.1:3000"
|
||||
- "meldestelle.mocode.at"
|
||||
# Allow these HTTP methods
|
||||
allowedMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- OPTIONS
|
||||
# Allow credentials (cookies, auth headers)
|
||||
allowCredentials: true
|
||||
|
||||
# Application-specific settings
|
||||
application:
|
||||
name: "Meldestelle Server"
|
||||
version: "1.0.0"
|
||||
environment: "${ENVIRONMENT:development}"
|
||||
# Feature flags
|
||||
features:
|
||||
enableRegistration: true
|
||||
enableEmailNotifications: "${ENABLE_EMAIL:false}"
|
||||
|
||||
@@ -1,12 +1,38 @@
|
||||
<configuration>
|
||||
<!-- Console appender configuration -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="trace">
|
||||
|
||||
<!-- File appender for important logs -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/meldestelle.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- daily rollover -->
|
||||
<fileNamePattern>logs/meldestelle.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- keep 30 days' worth of history -->
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Set default log level to INFO for production use -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
|
||||
<!-- Application-specific logger configuration -->
|
||||
<logger name="at.mocode" level="DEBUG"/>
|
||||
|
||||
<!-- Third-party library configurations -->
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="io.netty" level="INFO"/>
|
||||
</configuration>
|
||||
<logger name="org.hibernate.SQL" level="INFO"/>
|
||||
<logger name="com.zaxxer.hikari" level="INFO"/>
|
||||
<logger name="org.jetbrains.exposed" level="INFO"/>
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package at.mocode.server
|
||||
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Basic tests for the application
|
||||
*/
|
||||
class ApplicationTest {
|
||||
private val logger = LoggerFactory.getLogger(ApplicationTest::class.java)
|
||||
|
||||
@Test
|
||||
fun testEnvironmentSetup() {
|
||||
// Set test environment flag
|
||||
System.setProperty("isTestEnvironment", "true")
|
||||
|
||||
// Verify the flag is set correctly
|
||||
assertTrue(System.getProperty("isTestEnvironment").toBoolean())
|
||||
logger.info("Test environment flag set successfully")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApplicationFilesExist() {
|
||||
// Verify the Application.kt file exists
|
||||
val applicationFile = File("src/main/kotlin/at/mocode/server/Application.kt")
|
||||
assertTrue(applicationFile.exists() || File("server/" + applicationFile.path).exists(),
|
||||
"Application.kt file should exist")
|
||||
|
||||
// Verify the Database.kt file exists
|
||||
val databaseFile = File("src/main/kotlin/at/mocode/server/plugins/Database.kt")
|
||||
assertTrue(databaseFile.exists() || File("server/" + databaseFile.path).exists(),
|
||||
"Database.kt file should exist")
|
||||
|
||||
logger.info("Application files exist")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConfigurationFileExists() {
|
||||
// Verify the application.yaml file exists
|
||||
val configFile = File("src/main/resources/application.yaml")
|
||||
assertTrue(configFile.exists() || File("server/" + configFile.path).exists(),
|
||||
"application.yaml file should exist")
|
||||
|
||||
logger.info("Configuration file exists")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package at.mocode.server.plugins
|
||||
|
||||
import at.mocode.server.tables.*
|
||||
import io.ktor.server.config.*
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Tests for the Database.kt file
|
||||
*/
|
||||
class DatabaseTest {
|
||||
private val logger = LoggerFactory.getLogger(DatabaseTest::class.java)
|
||||
|
||||
// Create a temporary directory for test resources
|
||||
@TempDir
|
||||
lateinit var tempDir: File
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
// Clear any system properties that might affect the tests
|
||||
System.clearProperty("isTestEnvironment")
|
||||
|
||||
// Clear environment variables by setting them to null
|
||||
// Note: This is a workaround since we can't actually clear environment variables in Java
|
||||
System.getProperties().remove("DB_HOST")
|
||||
System.getProperties().remove("DB_NAME")
|
||||
System.getProperties().remove("DB_USER")
|
||||
System.getProperties().remove("DB_PASSWORD")
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
// Clear any system properties set during tests
|
||||
System.clearProperty("isTestEnvironment")
|
||||
System.getProperties().remove("DB_HOST")
|
||||
System.getProperties().remove("DB_NAME")
|
||||
System.getProperties().remove("DB_USER")
|
||||
System.getProperties().remove("DB_PASSWORD")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTestDatabaseConfiguration() {
|
||||
// Set test environment flag
|
||||
System.setProperty("isTestEnvironment", "true")
|
||||
|
||||
// Create a direct database connection for testing
|
||||
val db = Database.connect(
|
||||
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
|
||||
driver = "org.h2.Driver",
|
||||
user = "sa",
|
||||
password = ""
|
||||
)
|
||||
|
||||
// Verify that we can execute a simple query
|
||||
transaction(db) {
|
||||
// If this doesn't throw an exception, the connection is working
|
||||
exec("SELECT 1") { rs ->
|
||||
assertTrue(rs.next())
|
||||
assertEquals(1, rs.getInt(1))
|
||||
true
|
||||
}
|
||||
logger.info("Test database connection verified")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDevelopmentDatabaseConfiguration() {
|
||||
// Ensure test environment flag is not set
|
||||
System.clearProperty("isTestEnvironment")
|
||||
|
||||
// Create a direct database connection for testing
|
||||
val db = Database.connect(
|
||||
url = "jdbc:h2:mem:dev;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
|
||||
driver = "org.h2.Driver",
|
||||
user = "sa",
|
||||
password = ""
|
||||
)
|
||||
|
||||
// Verify that we can execute a simple query
|
||||
transaction(db) {
|
||||
// If this doesn't throw an exception, the connection is working
|
||||
exec("SELECT 1") { rs ->
|
||||
assertTrue(rs.next())
|
||||
assertEquals(1, rs.getInt(1))
|
||||
true
|
||||
}
|
||||
logger.info("Development database connection verified")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSchemaInitialization() {
|
||||
// Set test environment flag
|
||||
System.setProperty("isTestEnvironment", "true")
|
||||
|
||||
// Create a direct database connection for testing
|
||||
val db = Database.connect(
|
||||
url = "jdbc:h2:mem:test_schema;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
|
||||
driver = "org.h2.Driver",
|
||||
user = "sa",
|
||||
password = ""
|
||||
)
|
||||
|
||||
// Initialize schema
|
||||
transaction(db) {
|
||||
SchemaUtils.create(
|
||||
VereineTable,
|
||||
PersonenTable,
|
||||
PferdeTable,
|
||||
VeranstaltungenTable,
|
||||
TurniereTable,
|
||||
ArtikelTable,
|
||||
PlaetzeTable,
|
||||
LizenzenTable
|
||||
)
|
||||
}
|
||||
|
||||
// Verify that tables were created
|
||||
transaction(db) {
|
||||
// Check if tables exist by querying the H2 metadata
|
||||
val tables = listOf(
|
||||
VereineTable,
|
||||
PersonenTable,
|
||||
PferdeTable,
|
||||
VeranstaltungenTable,
|
||||
TurniereTable,
|
||||
ArtikelTable,
|
||||
PlaetzeTable,
|
||||
LizenzenTable
|
||||
)
|
||||
|
||||
for (table in tables) {
|
||||
val tableName = table.tableName.uppercase()
|
||||
val result = exec("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '$tableName'") { rs ->
|
||||
rs.next()
|
||||
rs.getInt(1)
|
||||
}
|
||||
assertEquals(1, result, "Table $tableName should exist")
|
||||
}
|
||||
|
||||
logger.info("Schema initialization verified")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorHandlingInTestEnvironment() {
|
||||
// Set test environment flag
|
||||
System.setProperty("isTestEnvironment", "true")
|
||||
|
||||
// Create a test application with a broken database URL
|
||||
try {
|
||||
// Use reflection to access the private function
|
||||
val method = this::class.java.classLoader
|
||||
.loadClass("at.mocode.server.plugins.DatabaseKt")
|
||||
.getDeclaredMethod("configureTestDatabase", org.slf4j.Logger::class.java)
|
||||
|
||||
method.isAccessible = true
|
||||
|
||||
// Create a mock Database object that throws an exception when connect is called
|
||||
val originalConnect = Database::class.java.getDeclaredMethod("connect",
|
||||
String::class.java, String::class.java, String::class.java, String::class.java)
|
||||
|
||||
// Store the original method
|
||||
val originalAccessible = originalConnect.canAccess(originalConnect)
|
||||
originalConnect.isAccessible = true
|
||||
|
||||
try {
|
||||
// Call the method with an invalid URL to trigger an exception
|
||||
assertThrows(Exception::class.java) {
|
||||
method.invoke(null, logger)
|
||||
}
|
||||
logger.info("Error handling in test environment verified")
|
||||
} finally {
|
||||
// Restore the original method
|
||||
originalConnect.isAccessible = originalAccessible
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// If we can't use reflection, just log a message
|
||||
logger.warn("Could not test error handling using reflection: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProductionDatabaseConfigurationValidation() {
|
||||
// Ensure test environment flag is not set
|
||||
System.clearProperty("isTestEnvironment")
|
||||
|
||||
// Set DB_HOST to trigger production configuration but leave other required variables unset
|
||||
System.setProperty("DB_HOST", "localhost")
|
||||
System.getProperties().remove("DB_NAME")
|
||||
System.getProperties().remove("DB_USER")
|
||||
System.getProperties().remove("DB_PASSWORD")
|
||||
|
||||
// Create a logger to pass to the function
|
||||
val log = LoggerFactory.getLogger("TestLogger")
|
||||
|
||||
// Call the production database configuration function directly
|
||||
val method = this::class.java.classLoader
|
||||
.loadClass("at.mocode.server.plugins.DatabaseKt")
|
||||
.getDeclaredMethod("configureProductionDatabase", org.slf4j.Logger::class.java, ApplicationConfig::class.java)
|
||||
|
||||
method.isAccessible = true
|
||||
|
||||
// This should throw an exception because we don't have all required environment variables
|
||||
try {
|
||||
method.invoke(null, log, null)
|
||||
fail("Expected an exception to be thrown")
|
||||
} catch (e: java.lang.reflect.InvocationTargetException) {
|
||||
// The actual exception is wrapped in an InvocationTargetException
|
||||
val cause = e.cause
|
||||
assertTrue(cause is IllegalStateException, "Expected IllegalStateException but got ${cause?.javaClass?.name}")
|
||||
logger.info("Production database configuration validation verified: ${cause?.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user