#4 DB-Integration in Server - Gerüst mit Exposed+HikariCP+Postgres steht

This commit is contained in:
Hartmut Bauer committed 2026-02-08 01:44:33 +01:00
1 parent 60f70ece4a
commit 0e46bf6cf9
14 files changed
+194 -48

No files matched your search

@@ -4,13 +4,13 @@ import net.keinekekse.version.Version
object VERSION: Version {
override val version = "1.0.0"
override val fullVersion = "1.0.0+build.221"
override val fullVersion = "1.0.0+build.222"
override val major = 1
override val minor = 0
override val patch = 0
override val preRelease = ""
override val buildMetadata = "build.221"
override val buildDate = "2026-02-06T22:31:36.863331Z"
override val buildMetadata = "build.222"
override val buildDate = "2026-02-07T16:26:28.842311Z"
override val displayName = "Books App"
override val copyright = "Copyright (c) 2026 Hartmut Bauer"
override val name = "books"
+13 -8
View File
@@ -8,19 +8,20 @@ composeAppVersion = "1.0.0"
androidx-lifecycle = "2.9.6"
composeHotReload = "1.0.0"
composeMultiplatform = "1.10.0"
exposed = "1.0.0"
hikari = "7.0.2"
junit = "4.13.2"
kotlin = "2.3.0"
kotlin-wrappers-css = "2025.6.4"
kotlinx-coroutines = "1.10.2"
kotlinx-html = "0.12.0"
koin-bom = "4.2.0-RC1"
kotlinLogging = "7.0.14"
koin-plugin = "0.3.0"
ktor = "3.4.0"
logback = "1.5.26"
kotlinLogging = "7.0.14"
logback = "1.5.28"
material3 = "1.10.0-alpha05"
settings = "1.3.0"
dataStore = "1.2.0"
postgres = "42.7.9"
uiDesktop = "1.7.0"
[libraries]
@@ -55,6 +56,9 @@ ktor-serverHtmlBuilder = { module = "io.ktor:ktor-server-html-builder", version.
ktor-serverHtmx = { module = "io.ktor:ktor-server-htmx", version.ref = "ktor" }
ktor-htmxHtml = { module = "io.ktor:ktor-htmx-html", version.ref = "ktor" }
ktor-serverContentNegotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common-jvm", version.ref = "ktor" }
ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages-jvm", version.ref = "ktor" }
ktor-serverTestHost = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor" }
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-clientAuth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
@@ -84,10 +88,11 @@ koin-test = { module = "io.insert-koin:koin-test" }
#koin-compose-viewmodel-navigation3 = { module = "io.insert-koin:koin-compose-viewmodel-navigation3" }
# ----
# Settings vorerst ohne externe Hilfe
#multiplatformSettings = { module = "com.russhwolf:multiplatform-settings", version.ref = "settings" }
#androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "dataStore" }
#androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "dataStore" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgres" }
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikari" }
[bundles]
kotlin-compose = [
+8
View File
@@ -31,6 +31,8 @@ dependencies {
implementation(libs.ktor.serverAuthJwt)
implementation(libs.ktor.serverContentNegotiation)
implementation(libs.ktor.serializationJson)
implementation(libs.ktor.server.host.common)
implementation(libs.ktor.server.status.pages)
implementation(project.dependencies.platform(libs.koin.bom))
implementation(libs.koin.logger.slf4j)
@@ -38,6 +40,12 @@ dependencies {
implementation(libs.koin.ktor)
// implementation("io.insert-koin:koin-logger-slf4j:4.1.2-Beta1")
implementation(libs.postgresql)
implementation(libs.exposed.core)
implementation(libs.exposed.jdbc)
implementation(libs.exposed.dao)
implementation(libs.hikaricp)
testImplementation(libs.ktor.serverTestHost)
testImplementation(libs.kotlin.testJunit)
}
+2 -2
View File
@@ -1,3 +1,3 @@
#auto generated
#Sat Feb 07 20:02:00 CET 2026
build.number=27
#Sun Feb 08 01:38:22 CET 2026
build.number=35
@@ -3,9 +3,13 @@ package net.keinekekse.app
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.server.application.*
import net.keinekekse.app.server.VERSION
import net.keinekekse.app.server.config.configureDatabase
import net.keinekekse.app.server.config.configureKoin
import net.keinekekse.app.server.config.configureRouting
import net.keinekekse.app.server.config.configureSecurity
import net.keinekekse.app.server.testdb.db.BookTable
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
private val logger = KotlinLogging.logger {}
@@ -26,4 +30,10 @@ fun Application.module() {
configureKoin()
configureSecurity()
configureRouting()
configureDatabase()
// create if not exists
transaction {
SchemaUtils.create(BookTable)
}
}
@@ -3,14 +3,14 @@ package net.keinekekse.app.server
import net.keinekekse.version.Version
object VERSION: Version {
override val version = "0.1.0-beta.1+build.27"
override val fullVersion = "0.1.0-beta.1+build.27"
override val version = "0.1.0-beta.1+build.35"
override val fullVersion = "0.1.0-beta.1+build.35"
override val major = 0
override val minor = 1
override val patch = 0
override val preRelease = "beta.1"
override val buildMetadata = "build.27"
override val buildDate = "2026-02-07T19:02:00.909145Z"
override val buildMetadata = "build.35"
override val buildDate = "2026-02-08T00:38:22.418116Z"
override val displayName = "kAS"
override val copyright = "Copyright (c) 2026 Hartmut Bauer"
override val name = "server"
@@ -0,0 +1,47 @@
package net.keinekekse.app.server.config
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import io.ktor.server.application.*
import org.jetbrains.exposed.v1.core.DatabaseConfig
import org.jetbrains.exposed.v1.jdbc.Database
fun Application.configureDatabase() {
val config = HikariConfig().apply {
// jdbcUrl = "jdbc:mysql://$host:$port/$db"
dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
username = getKeyFromConfiguration("postgres.user")
password = getKeyFromConfiguration("postgres.password")
addDataSourceProperty("serverName", getKeyFromConfiguration("postgres.host", "localhost"))
addDataSourceProperty("portNumber", getKeyFromConfiguration("postgres.port", "5432"))
addDataSourceProperty(
"databaseName",
getKeyFromConfiguration("postgres.database", username)
)
maximumPoolSize = 6
isReadOnly = false
transactionIsolation = "TRANSACTION_SERIALIZABLE"
}
val dataSource = HikariDataSource(config)
Database.connect(
datasource = dataSource,
databaseConfig = DatabaseConfig {
// set other parameters here
}
)
// Class.forName("org.postgresql.Driver")
// val host = getKeyFromConfiguration("postgres.host", "localhost")
// val port = getKeyFromConfiguration("postgres.port", "5432")
// val user = getKeyFromConfiguration("postgres.user")
// val password = getKeyFromConfiguration("postgres.password")
// val database = getKeyFromConfiguration("postgres.database", user)
//
// // "jdbc:postgresql://host:5432/database",
// return DriverManager.getConnection("jdbc:postgresql://$host:$port/$database", user, password)
}
@@ -0,0 +1,10 @@
package net.keinekekse.app.server.config
import io.ktor.server.application.*
fun Application.getKeyFromConfiguration(key: String, default: String? = null): String {
val value = environment.config.propertyOrNull(key)?.getString() ?: default
// ohne die Konfig-Params geht nix, hier ist nur noch Abbruch möglich
return value
?: throw IllegalStateException("Configuration key '$key' not found, server start aborted.")
}
@@ -6,7 +6,6 @@ import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.config.*
import io.ktor.server.response.*
import java.net.URI
@@ -14,25 +13,13 @@ private val logger = KotlinLogging.logger {}
fun Application.configureSecurity() {
fun getFromConfig(key: String): String {
try {
return environment.config.property("ktor.conf.$key").getString()
} catch (e: ApplicationConfigurationException) {
// ohne die Konfig-Params geht nix (und defaults sind hier sinnlos), hier ist nur noch Abbruch möglich
throw IllegalStateException(
"Configuration key '$key' not found, server start aborted.",
e
)
}
}
// Kommunikation funktioniert nur im internen Containernetzwerk,
// d.h. Container- bzw. Pod-Name + interner Port
val keycloakInternalUrl = getFromConfig("keycloakInternalUrl")
val keycloakUrl = getFromConfig("keycloakUrl")
val keycloakRealm = getFromConfig("keycloakRealm")
val keycloakClientId = getFromConfig("keycloakClientId")
val keycloakCertUrl = getFromConfig("keycloakCertUrl")
val keycloakInternalUrl = getKeyFromConfiguration("keycloak.internalUrl")
val keycloakUrl = getKeyFromConfiguration("keycloak.url")
val keycloakRealm = getKeyFromConfiguration("keycloak.realm")
val keycloakClientId = getKeyFromConfiguration("keycloak.clientId")
val keycloakCertUrl = getKeyFromConfiguration("keycloak.certUrl")
val certUrl = "$keycloakInternalUrl/$keycloakRealm/$keycloakCertUrl"
val issuer = "$keycloakUrl/$keycloakRealm"
@@ -0,0 +1,25 @@
package net.keinekekse.app.server.testdb.db
import net.keinekekse.app.server.testdb.model.Book
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.LongIdTable
import org.jetbrains.exposed.v1.dao.LongEntity
import org.jetbrains.exposed.v1.dao.LongEntityClass
object BookTable : LongIdTable("book") {
var title = varchar("title", 50)
var author = varchar("author", 50)
var series = varchar("series", 50)
var seriesIndex = integer("seriesIndex")
}
class BookDAO(id: EntityID<Long>) : LongEntity(id) {
companion object : LongEntityClass<BookDAO>(BookTable)
var title by BookTable.title
var author by BookTable.author
var series by BookTable.series
var seriesIndex by BookTable.seriesIndex
}
fun daoToModel(dao: BookDAO) = Book(dao.title, dao.author, dao.series, dao.seriesIndex)
@@ -0,0 +1,11 @@
package net.keinekekse.app.server.testdb.model
import kotlinx.serialization.Serializable
@Serializable
data class Book(
var title: String = "",
var author: String = "",
var series: String = "",
var seriesIndex: Int = 0,
)
@@ -0,0 +1,11 @@
package net.keinekekse.app.server.testdb.model
interface BookRepository {
suspend fun allBooks(): List<Book>
suspend fun bookById(id: Long): Book?
suspend fun booksBySeries(series: String): List<Book>
suspend fun bookByTitle(title: String): List<Book>
suspend fun bookByAuthor(author: String): List<Book>
suspend fun addTBook(book: Book)
suspend fun removeBook(id: Long): Boolean
}
@@ -0,0 +1,31 @@
package net.keinekekse.app.server.testdb.model
class BookRepositoryImpl : BookRepository {
override suspend fun allBooks(): List<Book> {
TODO("Not yet implemented")
}
override suspend fun bookById(id: Long): Book? {
TODO("Not yet implemented")
}
override suspend fun booksBySeries(series: String): List<Book> {
TODO("Not yet implemented")
}
override suspend fun bookByTitle(title: String): List<Book> {
TODO("Not yet implemented")
}
override suspend fun bookByAuthor(author: String): List<Book> {
TODO("Not yet implemented")
}
override suspend fun addTBook(book: Book) {
TODO("Not yet implemented")
}
override suspend fun removeBook(id: Long): Boolean {
TODO("Not yet implemented")
}
}
+14 -13
View File
@@ -18,18 +18,19 @@ ktor {
# ApplicationKt.module() übernimmt die Konfiguration
modules = [ net.keinekekse.app.ApplicationKt.module ]
}
conf {
# eigene Parameter -> ktor.conf.<name>
#
keycloakInternalUrl = "http://kec:8080/realms"
keycloakUrl = "https://kec.keinekekse.net/realms"
keycloakRealm = "app-server-realm"
keycloakClientId = "pkce-book-app"
keycloakCertUrl = "protocol/openid-connect/certs"
}
keycloak {
internalUrl = "http://kec:8080/realms"
url = "https://kec.keinekekse.net/realms"
realm = "app-server-realm"
clientId = "pkce-book-app"
certUrl = "protocol/openid-connect/certs"
}
postgres {
# DB-Params werden vom Container als env vars gesetzt
dbHost = ${DB_HOST}
dbPort = ${DB_PORT}
dbUser = ${DB_USER}
dbPassword = ${DB_PASSWORD}
}
host = ${DB_HOST}
port = ${DB_PORT}
database = ${DB_DATABASE}
user = ${DB_USER}
password = ${DB_PASSWORD}
}