diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a203661 --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2d9c389..a86cc79 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index cc4c701..257f56b 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -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) diff --git a/server/src/main/kotlin/de/krisenvorrat/server/db/DatabaseFactory.kt b/server/src/main/kotlin/de/krisenvorrat/server/db/DatabaseFactory.kt index bd62b62..2c9562e 100644 --- a/server/src/main/kotlin/de/krisenvorrat/server/db/DatabaseFactory.kt +++ b/server/src/main/kotlin/de/krisenvorrat/server/db/DatabaseFactory.kt @@ -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) ) { - Database.connect(jdbcUrl, driver) + 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 }