#4 DB-Integration in Server - Gerüst mit Exposed+HikariCP+Postgres steht
This commit is contained in:
1 parent
60f70ece4a
commit
0e46bf6cf9
14 files changed
+195
-49
No files matched your search
@@ -4,13 +4,13 @@ import net.keinekekse.version.Version
|
|||||||
|
|
||||||
object VERSION: Version {
|
object VERSION: Version {
|
||||||
override val version = "1.0.0"
|
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 major = 1
|
||||||
override val minor = 0
|
override val minor = 0
|
||||||
override val patch = 0
|
override val patch = 0
|
||||||
override val preRelease = ""
|
override val preRelease = ""
|
||||||
override val buildMetadata = "build.221"
|
override val buildMetadata = "build.222"
|
||||||
override val buildDate = "2026-02-06T22:31:36.863331Z"
|
override val buildDate = "2026-02-07T16:26:28.842311Z"
|
||||||
override val displayName = "Books App"
|
override val displayName = "Books App"
|
||||||
override val copyright = "Copyright (c) 2026 Hartmut Bauer"
|
override val copyright = "Copyright (c) 2026 Hartmut Bauer"
|
||||||
override val name = "books"
|
override val name = "books"
|
||||||
|
|||||||
@@ -8,19 +8,20 @@ composeAppVersion = "1.0.0"
|
|||||||
androidx-lifecycle = "2.9.6"
|
androidx-lifecycle = "2.9.6"
|
||||||
composeHotReload = "1.0.0"
|
composeHotReload = "1.0.0"
|
||||||
composeMultiplatform = "1.10.0"
|
composeMultiplatform = "1.10.0"
|
||||||
|
exposed = "1.0.0"
|
||||||
|
hikari = "7.0.2"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlin = "2.3.0"
|
kotlin = "2.3.0"
|
||||||
kotlin-wrappers-css = "2025.6.4"
|
kotlin-wrappers-css = "2025.6.4"
|
||||||
kotlinx-coroutines = "1.10.2"
|
kotlinx-coroutines = "1.10.2"
|
||||||
kotlinx-html = "0.12.0"
|
kotlinx-html = "0.12.0"
|
||||||
koin-bom = "4.2.0-RC1"
|
koin-bom = "4.2.0-RC1"
|
||||||
|
kotlinLogging = "7.0.14"
|
||||||
koin-plugin = "0.3.0"
|
koin-plugin = "0.3.0"
|
||||||
ktor = "3.4.0"
|
ktor = "3.4.0"
|
||||||
logback = "1.5.26"
|
logback = "1.5.28"
|
||||||
kotlinLogging = "7.0.14"
|
|
||||||
material3 = "1.10.0-alpha05"
|
material3 = "1.10.0-alpha05"
|
||||||
settings = "1.3.0"
|
postgres = "42.7.9"
|
||||||
dataStore = "1.2.0"
|
|
||||||
uiDesktop = "1.7.0"
|
uiDesktop = "1.7.0"
|
||||||
|
|
||||||
[libraries]
|
[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-serverHtmx = { module = "io.ktor:ktor-server-htmx", version.ref = "ktor" }
|
||||||
ktor-htmxHtml = { module = "io.ktor:ktor-htmx-html", 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-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-serverTestHost = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor" }
|
||||||
ktor-client = { module = "io.ktor:ktor-client-core", 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" }
|
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" }
|
#koin-compose-viewmodel-navigation3 = { module = "io.insert-koin:koin-compose-viewmodel-navigation3" }
|
||||||
# ----
|
# ----
|
||||||
|
|
||||||
# Settings vorerst ohne externe Hilfe
|
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgres" }
|
||||||
#multiplatformSettings = { module = "com.russhwolf:multiplatform-settings", version.ref = "settings" }
|
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
|
||||||
#androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "dataStore" }
|
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||||
#androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "dataStore" }
|
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
|
||||||
|
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikari" }
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
kotlin-compose = [
|
kotlin-compose = [
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ dependencies {
|
|||||||
implementation(libs.ktor.serverAuthJwt)
|
implementation(libs.ktor.serverAuthJwt)
|
||||||
implementation(libs.ktor.serverContentNegotiation)
|
implementation(libs.ktor.serverContentNegotiation)
|
||||||
implementation(libs.ktor.serializationJson)
|
implementation(libs.ktor.serializationJson)
|
||||||
|
implementation(libs.ktor.server.host.common)
|
||||||
|
implementation(libs.ktor.server.status.pages)
|
||||||
|
|
||||||
implementation(project.dependencies.platform(libs.koin.bom))
|
implementation(project.dependencies.platform(libs.koin.bom))
|
||||||
implementation(libs.koin.logger.slf4j)
|
implementation(libs.koin.logger.slf4j)
|
||||||
@@ -38,6 +40,12 @@ dependencies {
|
|||||||
implementation(libs.koin.ktor)
|
implementation(libs.koin.ktor)
|
||||||
// implementation("io.insert-koin:koin-logger-slf4j:4.1.2-Beta1")
|
// 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.ktor.serverTestHost)
|
||||||
testImplementation(libs.kotlin.testJunit)
|
testImplementation(libs.kotlin.testJunit)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#auto generated
|
#auto generated
|
||||||
#Sat Feb 07 20:02:00 CET 2026
|
#Sun Feb 08 01:38:22 CET 2026
|
||||||
build.number=27
|
build.number=35
|
||||||
@@ -3,9 +3,13 @@ package net.keinekekse.app
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import net.keinekekse.app.server.VERSION
|
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.configureKoin
|
||||||
import net.keinekekse.app.server.config.configureRouting
|
import net.keinekekse.app.server.config.configureRouting
|
||||||
import net.keinekekse.app.server.config.configureSecurity
|
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 {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@@ -26,4 +30,10 @@ fun Application.module() {
|
|||||||
configureKoin()
|
configureKoin()
|
||||||
configureSecurity()
|
configureSecurity()
|
||||||
configureRouting()
|
configureRouting()
|
||||||
|
configureDatabase()
|
||||||
|
|
||||||
|
// create if not exists
|
||||||
|
transaction {
|
||||||
|
SchemaUtils.create(BookTable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,14 +3,14 @@ package net.keinekekse.app.server
|
|||||||
import net.keinekekse.version.Version
|
import net.keinekekse.version.Version
|
||||||
|
|
||||||
object VERSION: Version {
|
object VERSION: Version {
|
||||||
override val version = "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.27"
|
override val fullVersion = "0.1.0-beta.1+build.35"
|
||||||
override val major = 0
|
override val major = 0
|
||||||
override val minor = 1
|
override val minor = 1
|
||||||
override val patch = 0
|
override val patch = 0
|
||||||
override val preRelease = "beta.1"
|
override val preRelease = "beta.1"
|
||||||
override val buildMetadata = "build.27"
|
override val buildMetadata = "build.35"
|
||||||
override val buildDate = "2026-02-07T19:02:00.909145Z"
|
override val buildDate = "2026-02-08T00:38:22.418116Z"
|
||||||
override val displayName = "kAS"
|
override val displayName = "kAS"
|
||||||
override val copyright = "Copyright (c) 2026 Hartmut Bauer"
|
override val copyright = "Copyright (c) 2026 Hartmut Bauer"
|
||||||
override val name = "server"
|
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.application.*
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.server.auth.*
|
||||||
import io.ktor.server.auth.jwt.*
|
import io.ktor.server.auth.jwt.*
|
||||||
import io.ktor.server.config.*
|
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
@@ -14,25 +13,13 @@ private val logger = KotlinLogging.logger {}
|
|||||||
|
|
||||||
fun Application.configureSecurity() {
|
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,
|
// Kommunikation funktioniert nur im internen Containernetzwerk,
|
||||||
// d.h. Container- bzw. Pod-Name + interner Port
|
// d.h. Container- bzw. Pod-Name + interner Port
|
||||||
val keycloakInternalUrl = getFromConfig("keycloakInternalUrl")
|
val keycloakInternalUrl = getKeyFromConfiguration("keycloak.internalUrl")
|
||||||
val keycloakUrl = getFromConfig("keycloakUrl")
|
val keycloakUrl = getKeyFromConfiguration("keycloak.url")
|
||||||
val keycloakRealm = getFromConfig("keycloakRealm")
|
val keycloakRealm = getKeyFromConfiguration("keycloak.realm")
|
||||||
val keycloakClientId = getFromConfig("keycloakClientId")
|
val keycloakClientId = getKeyFromConfiguration("keycloak.clientId")
|
||||||
val keycloakCertUrl = getFromConfig("keycloakCertUrl")
|
val keycloakCertUrl = getKeyFromConfiguration("keycloak.certUrl")
|
||||||
|
|
||||||
val certUrl = "$keycloakInternalUrl/$keycloakRealm/$keycloakCertUrl"
|
val certUrl = "$keycloakInternalUrl/$keycloakRealm/$keycloakCertUrl"
|
||||||
val issuer = "$keycloakUrl/$keycloakRealm"
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,18 +18,19 @@ ktor {
|
|||||||
# ApplicationKt.module() übernimmt die Konfiguration
|
# ApplicationKt.module() übernimmt die Konfiguration
|
||||||
modules = [ net.keinekekse.app.ApplicationKt.module ]
|
modules = [ net.keinekekse.app.ApplicationKt.module ]
|
||||||
}
|
}
|
||||||
conf {
|
}
|
||||||
# eigene Parameter -> ktor.conf.<name>
|
keycloak {
|
||||||
#
|
internalUrl = "http://kec:8080/realms"
|
||||||
keycloakInternalUrl = "http://kec:8080/realms"
|
url = "https://kec.keinekekse.net/realms"
|
||||||
keycloakUrl = "https://kec.keinekekse.net/realms"
|
realm = "app-server-realm"
|
||||||
keycloakRealm = "app-server-realm"
|
clientId = "pkce-book-app"
|
||||||
keycloakClientId = "pkce-book-app"
|
certUrl = "protocol/openid-connect/certs"
|
||||||
keycloakCertUrl = "protocol/openid-connect/certs"
|
}
|
||||||
# DB-Params werden vom Container als env vars gesetzt
|
postgres {
|
||||||
dbHost = ${DB_HOST}
|
# DB-Params werden vom Container als env vars gesetzt
|
||||||
dbPort = ${DB_PORT}
|
host = ${DB_HOST}
|
||||||
dbUser = ${DB_USER}
|
port = ${DB_PORT}
|
||||||
dbPassword = ${DB_PASSWORD}
|
database = ${DB_DATABASE}
|
||||||
}
|
user = ${DB_USER}
|
||||||
|
password = ${DB_PASSWORD}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user