refactor(server): H2 durch PostgreSQL ersetzen

- DatabaseFactory: HikariCP Connection-Pool fuer PostgreSQL (10 Connections,
  REPEATABLE_READ), H2 weiterhin ohne Pool (fuer Tests)
- Dependencies: postgresql-Treiber + HikariCP hinzugefuegt, H2 nur noch
  testImplementation
- Migration-SQL: uppercase Tabellennamen auf lowercase normalisiert
  (dialect-agnostisch fuer H2 und PostgreSQL)
- docker-compose.yml: PostgreSQL 17 + Krisenvorrat-Server mit DB-Env-Vars
- Env-Var-Konfiguration: KRISENVORRAT_DB_URL, _DB_USER, _DB_PASSWORD,
  _DB_DRIVER (Defaults auf PostgreSQL localhost)
- Alle 554 Tests gruen (H2 in-memory fuer Tests beibehalten)

Closes #70
This commit is contained in:
Jens Reinemann 2026-05-17 02:35:08 +02:00
parent 033b0fae61
commit f792213b1e
4 changed files with 66 additions and 9 deletions

30
docker-compose.yml Normal file
View file

@ -0,0 +1,30 @@
services:
db:
image: postgres:17-alpine
container_name: krisenvorrat-db
restart: unless-stopped
environment:
POSTGRES_DB: krisenvorrat
POSTGRES_USER: krisenvorrat
POSTGRES_PASSWORD: krisenvorrat
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
krisenvorrat:
build: .
container_name: krisenvorrat-server
restart: unless-stopped
ports:
- "8080:8080"
environment:
- KRISENVORRAT_JWT_SECRET=sRKnyOBAgwkoDYptqixc9I26SlUWFhGXL5jaTM1vPbe78Q0r
- KRISENVORRAT_DB_URL=jdbc:postgresql://db:5432/krisenvorrat
- KRISENVORRAT_DB_USER=krisenvorrat
- KRISENVORRAT_DB_PASSWORD=krisenvorrat
depends_on:
- db
volumes:
pgdata:

View file

@ -20,6 +20,8 @@ ktor = "3.1.2"
logback = "1.5.18"
exposed = "0.58.0"
h2 = "2.3.232"
postgresql = "42.7.4"
hikaricp = "6.2.1"
jbcrypt = "0.4"
[libraries]
@ -71,6 +73,8 @@ logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.
exposed-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposed" }
exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposed" }
h2-database = { group = "com.h2database", name = "h2", version.ref = "h2" }
postgresql = { group = "org.postgresql", name = "postgresql", version.ref = "postgresql" }
hikaricp = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikaricp" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

View file

@ -41,8 +41,10 @@ dependencies {
implementation(libs.logback.classic)
implementation(libs.exposed.core)
implementation(libs.exposed.jdbc)
implementation(libs.h2.database)
implementation(libs.postgresql)
implementation(libs.hikaricp)
testImplementation(libs.h2.database)
testImplementation(libs.ktor.server.test.host)
testImplementation(libs.ktor.client.websockets)
testImplementation(libs.junit)

View file

@ -1,5 +1,7 @@
package de.krisenvorrat.server.db
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.insert
@ -15,11 +17,30 @@ private val logger = LoggerFactory.getLogger("DatabaseFactory")
internal object DatabaseFactory {
fun init(
jdbcUrl: String = "jdbc:h2:file:./data/krisenvorrat",
driver: String = "org.h2.Driver",
adminPassword: String? = System.getenv("KRISENVORRAT_ADMIN_PASSWORD")
jdbcUrl: String = System.getenv("KRISENVORRAT_DB_URL")
?: "jdbc:postgresql://localhost:5432/krisenvorrat",
driver: String = System.getenv("KRISENVORRAT_DB_DRIVER")
?: "org.postgresql.Driver",
dbUser: String = System.getenv("KRISENVORRAT_DB_USER") ?: "krisenvorrat",
dbPassword: String = System.getenv("KRISENVORRAT_DB_PASSWORD") ?: "krisenvorrat",
adminPassword: String? = System.getenv("KRISENVORRAT_ADMIN_PASSWORD"),
usePool: Boolean = !driver.contains("h2", ignoreCase = true)
) {
if (usePool) {
val config = HikariConfig().apply {
this.jdbcUrl = jdbcUrl
this.driverClassName = driver
this.username = dbUser
this.password = dbPassword
maximumPoolSize = 10
isAutoCommit = false
transactionIsolation = "TRANSACTION_REPEATABLE_READ"
validate()
}
Database.connect(HikariDataSource(config))
} else {
Database.connect(jdbcUrl, driver)
}
transaction {
SchemaUtils.create(Inventories, Users, Categories, Locations, Items, Settings, Messages)
SchemaUtils.createMissingTablesAndColumns(Inventories, Users, Categories, Locations, Items, Settings, Messages)
@ -51,10 +72,10 @@ internal object DatabaseFactory {
// The exec() calls are wrapped in try/catch because the user_id column may not
// exist in fresh databases (only in upgraded databases from older schema).
try {
exec("UPDATE ITEMS SET INVENTORY_ID = '$inventoryId' WHERE USER_ID = '$userId' AND INVENTORY_ID IS NULL")
exec("UPDATE CATEGORIES SET INVENTORY_ID = '$inventoryId' WHERE USER_ID = '$userId' AND INVENTORY_ID IS NULL")
exec("UPDATE LOCATIONS SET INVENTORY_ID = '$inventoryId' WHERE USER_ID = '$userId' AND INVENTORY_ID IS NULL")
exec("UPDATE SETTINGS SET INVENTORY_ID = '$inventoryId' WHERE USER_ID = '$userId' AND INVENTORY_ID IS NULL")
exec("UPDATE items SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
exec("UPDATE categories SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
exec("UPDATE locations SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
exec("UPDATE settings SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
} catch (_: Exception) {
// user_id column doesn't exist fresh database, nothing to migrate
}