(fix) Datenbank-Setup Umbau zu SCS

This commit is contained in:
stefan
2025-07-19 13:33:07 +02:00
parent 3c0cf9ce43
commit edf19188b8
13 changed files with 769 additions and 0 deletions
+10
View File
@@ -22,6 +22,16 @@ kotlin {
implementation(libs.kotlin.test)
}
jvmMain.dependencies {
// Datenbankabhängigkeiten
implementation("com.zaxxer:HikariCP:5.0.1")
implementation(libs.exposed.core)
implementation(libs.exposed.dao)
implementation(libs.exposed.jdbc)
implementation(libs.exposed.kotlinDatetime)
implementation(libs.postgresql.driver)
}
jsMain.dependencies {
// Kotlin React dependencies with explicit stable versions (for shared components)
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
@@ -0,0 +1,33 @@
package at.mocode.shared.database
/**
* Konfiguration für die Datenbankverbindung.
* Parameter werden aus Umgebungsvariablen gelesen oder Standardwerte verwendet.
*/
data class DatabaseConfig(
val jdbcUrl: String,
val username: String,
val password: String,
val driverClassName: String = "org.postgresql.Driver",
val maxPoolSize: Int = 10
) {
companion object {
/**
* Erstellt eine Datenbank-Konfiguration aus Umgebungsvariablen.
* Wenn keine Umgebungsvariablen gefunden werden, werden Standardwerte für die Entwicklung verwendet.
*/
fun fromEnv(): DatabaseConfig {
val host = System.getenv("DB_HOST") ?: "localhost"
val port = System.getenv("DB_PORT") ?: "5432"
val database = System.getenv("DB_NAME") ?: "meldestelle_db"
val username = System.getenv("DB_USER") ?: "meldestelle_user"
val password = System.getenv("DB_PASSWORD") ?: "secure_password_change_me"
return DatabaseConfig(
jdbcUrl = "jdbc:postgresql://$host:$port/$database",
username = username,
password = password
)
}
}
}
@@ -0,0 +1,55 @@
package at.mocode.shared.database
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
/**
* Factory-Klasse für die Datenbankverbindung.
* Stellt eine Verbindung zur Datenbank her und konfiguriert den Connection Pool.
*/
object DatabaseFactory {
private var dataSource: HikariDataSource? = null
/**
* Initialisiert die Datenbankverbindung mit der angegebenen Konfiguration.
* @param config Die Datenbankkonfiguration
*/
fun init(config: DatabaseConfig) {
if (dataSource != null) {
close()
}
val hikariConfig = HikariConfig().apply {
driverClassName = config.driverClassName
jdbcUrl = config.jdbcUrl
username = config.username
password = config.password
maximumPoolSize = config.maxPoolSize
isAutoCommit = false
transactionIsolation = "TRANSACTION_REPEATABLE_READ"
validate()
}
dataSource = HikariDataSource(hikariConfig)
Database.connect(dataSource!!)
}
/**
* Führt eine Datenbankoperation in einer Transaktion aus.
* @param block Der Code, der in der Transaktion ausgeführt werden soll
* @return Das Ergebnis der Transaktion
*/
suspend fun <T> dbQuery(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) { block() }
/**
* Schließt die Datenbankverbindung.
*/
fun close() {
dataSource?.close()
dataSource = null
}
}
@@ -0,0 +1,100 @@
package at.mocode.shared.database
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.kotlin.datetime.CurrentTimestamp
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
/**
* Führt Datenbankmigrationen durch.
* Diese Klasse verwaltet und führt alle notwendigen Datenbankmigrationen aus.
*/
object DatabaseMigrator {
private val migrations = mutableListOf<Migration>()
private val executedMigrations = mutableSetOf<String>()
/**
* Registriert eine Migration.
* @param migration Die zu registrierende Migration
*/
fun register(migration: Migration) {
migrations.add(migration)
}
/**
* Registriert mehrere Migrationen auf einmal.
* @param migrations Die zu registrierenden Migrationen
*/
fun registerAll(vararg migrations: Migration) {
this.migrations.addAll(migrations)
}
/**
* Führt alle registrierten Migrationen aus, die noch nicht ausgeführt wurden.
*/
fun migrate() {
// Erstelle die Migrationstabelle, wenn sie nicht existiert
transaction {
SchemaUtils.create(MigrationTable)
// Lade bereits ausgeführte Migrationen
MigrationTable.selectAll().forEach {
executedMigrations.add(it[MigrationTable.id])
}
// Sortiere Migrationen nach Version
val sortedMigrations = migrations.sortedBy { it.version }
// Führe noch nicht ausgeführte Migrationen aus
for (migration in sortedMigrations) {
if (!executedMigrations.contains(migration.id)) {
println("Ausführen der Migration: ${migration.id}")
try {
migration.up()
// Markiere Migration als ausgeführt
MigrationTable.insert {
it[id] = migration.id
it[version] = migration.version
it[description] = migration.description
}
commit()
println("Migration erfolgreich: ${migration.id}")
} catch (e: Exception) {
rollback()
println("Migration fehlgeschlagen: ${migration.id} - ${e.message}")
throw e
}
}
}
}
}
}
/**
* Tabelle zur Verfolgung ausgeführter Migrationen.
*/
object MigrationTable : Table("_migrations") {
val id = varchar("id", 100)
val version = long("version")
val description = varchar("description", 255)
val executedAt = timestamp("executed_at").defaultExpression(CurrentTimestamp)
override val primaryKey = PrimaryKey(id)
}
/**
* Basisklasse für Datenbankmigrationen.
*/
abstract class Migration(val version: Long, val description: String) {
/**
* Eindeutige ID der Migration, bestehend aus Version und Beschreibung.
*/
val id: String = "V${version}_${description.replace("\\s+".toRegex(), "_")}"
/**
* Führt die Migration aus.
*/
abstract fun up()
}