refactor(build): enable Wasm by default and refactor modules for improved KMP compatibility
Enabled Wasm target across all relevant modules and removed conditional enablement logic. Refactored `core:core-utils` to move JVM-specific code to a new `backend:infrastructure:persistence` module for strict KMP compliance. Updated dependencies, adjusted Gradle configurations, and resolved circular dependencies.
This commit is contained in:
@@ -20,6 +20,12 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
// Wasm support enabled?
|
||||
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
// Opt-in to experimental Kotlin UUID API across all source sets
|
||||
all {
|
||||
|
||||
@@ -1,90 +1,39 @@
|
||||
// Dieses Modul stellt gemeinsame technische Hilfsfunktionen bereit,
|
||||
// wie z.B. Konfigurations-Management, Datenbank-Verbindungen und Service Discovery.
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Toolchain is now handled centrally in the root build.gradle.kts
|
||||
|
||||
// Target platforms
|
||||
jvm {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
jvm()
|
||||
js {
|
||||
browser()
|
||||
}
|
||||
}
|
||||
|
||||
js(IR) {
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.optIn("kotlin.uuid.ExperimentalUuidApi")
|
||||
// Wasm support enabled?
|
||||
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
}
|
||||
|
||||
commonMain.dependencies {
|
||||
// Domain models and types (core-utils depends on core-domain, not vice versa)
|
||||
api(projects.core.coreDomain)
|
||||
|
||||
api(libs.kotlinx.serialization.json)
|
||||
api(libs.kotlinx.datetime)
|
||||
// Async support (available for all platforms)
|
||||
api(libs.kotlinx.coroutines.core)
|
||||
// Utilities (multiplatform compatible)
|
||||
api(libs.bignum)
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation(projects.core.coreDomain)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
// Removed Exposed dependencies to make this module KMP compatible
|
||||
// implementation(libs.exposed.core)
|
||||
// implementation(libs.exposed.jdbc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
jvmMain.dependencies {
|
||||
// JVM-specific dependencies - access to central catalog
|
||||
api(projects.platform.platformDependencies)
|
||||
|
||||
// Database Management (JVM-specific)
|
||||
// Exposed dependencies restored for backend compatibility
|
||||
api(libs.exposed.core)
|
||||
api(libs.exposed.dao)
|
||||
api(libs.exposed.jdbc)
|
||||
api(libs.exposed.kotlin.datetime)
|
||||
|
||||
api(libs.flyway.core)
|
||||
api(libs.flyway.postgresql)
|
||||
|
||||
api(libs.hikari.cp)
|
||||
// Service Discovery (JVM-specific)
|
||||
api(libs.spring.cloud.starter.consul.discovery)
|
||||
// Logging (JVM-specific)
|
||||
api(libs.kotlin.logging.jvm)
|
||||
// Jakarta Annotation API
|
||||
api(libs.jakarta.annotation.api)
|
||||
// JSON Processing
|
||||
api(libs.jackson.module.kotlin)
|
||||
api(libs.jackson.datatype.jsr310)
|
||||
}
|
||||
jvmTest.dependencies {
|
||||
// Testing (JVM-specific)
|
||||
implementation(projects.platform.platformTesting)
|
||||
implementation(libs.junit.jupiter.api)
|
||||
implementation(libs.junit.jupiter.engine)
|
||||
implementation(libs.junit.jupiter.params)
|
||||
implementation(libs.junit.platform.launcher)
|
||||
implementation(libs.mockk)
|
||||
implementation(libs.assertj.core)
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
|
||||
runtimeOnly(libs.postgresql.driver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<Test>("jvmTest") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
@@ -1,225 +1,10 @@
|
||||
package at.mocode.core.utils
|
||||
|
||||
// import at.mocode.core.domain.model.ErrorCodes
|
||||
// import at.mocode.core.domain.model.ErrorDto
|
||||
// import at.mocode.core.domain.model.PagedResponse
|
||||
// import org.jetbrains.exposed.sql.*
|
||||
// import org.jetbrains.exposed.sql.statements.BatchInsertStatement
|
||||
// import org.jetbrains.exposed.sql.transactions.transaction
|
||||
// import java.sql.SQLException
|
||||
// import java.sql.SQLTimeoutException
|
||||
|
||||
/**
|
||||
* JVM-specific database utilities for the Core module.
|
||||
* Provides common database operations and configurations.
|
||||
*
|
||||
* DEPRECATED / DISABLED:
|
||||
* This file contains Exposed-specific code which is not compatible with the KMP frontend.
|
||||
* It has been commented out to allow the frontend build to succeed.
|
||||
* If backend services need this, it should be moved to a backend-specific module (e.g. :backend:common).
|
||||
* MOVED: The content of this file has been moved to :backend:infrastructure:persistence
|
||||
* to resolve KMP compatibility issues with the frontend.
|
||||
*
|
||||
* Please use `at.mocode.backend.infrastructure.persistence.DatabaseUtils` instead.
|
||||
*/
|
||||
|
||||
/*
|
||||
inline fun <T> transactionResult(
|
||||
database: Database? = null,
|
||||
crossinline block: Transaction.() -> T
|
||||
): Result<T> {
|
||||
return try {
|
||||
val result = transaction(database) { block() }
|
||||
Result.success(result)
|
||||
} catch (e: SQLTimeoutException) {
|
||||
Result.failure(
|
||||
ErrorDto(
|
||||
code = ErrorCodes.DATABASE_TIMEOUT,
|
||||
message = "Datenbank-Operation wegen Timeout fehlgeschlagen"
|
||||
)
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
// Robustere Fehlerbehandlung über SQLSTATE (Postgres)
|
||||
val mapped = when (e.sqlState) {
|
||||
// unique_violation
|
||||
"23505" -> ErrorCodes.DUPLICATE_ENTRY
|
||||
// foreign_key_violation
|
||||
"23503" -> ErrorCodes.FOREIGN_KEY_VIOLATION
|
||||
// check_violation
|
||||
"23514" -> ErrorCodes.CHECK_VIOLATION
|
||||
else -> ErrorCodes.DATABASE_ERROR
|
||||
}
|
||||
|
||||
Result.failure(
|
||||
ErrorDto(
|
||||
code = mapped,
|
||||
message = "Datenbank-Operation fehlgeschlagen"
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(
|
||||
ErrorDto(
|
||||
code = ErrorCodes.TRANSACTION_ERROR,
|
||||
message = "Transaktion fehlgeschlagen"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> writeTransaction(
|
||||
database: Database? = null,
|
||||
crossinline block: Transaction.() -> T
|
||||
): Result<T> = transactionResult(database, block)
|
||||
|
||||
inline fun <T> readTransaction(
|
||||
database: Database? = null,
|
||||
crossinline block: Transaction.() -> T
|
||||
): Result<T> = transactionResult(database, block)
|
||||
|
||||
fun Query.paginate(page: Int, size: Int): Query {
|
||||
require(page >= 0) { "Page number must be non-negative" }
|
||||
require(size > 0) { "Page size must be positive" }
|
||||
|
||||
return limit(size).offset(start = (page * size).toLong())
|
||||
}
|
||||
|
||||
fun <T> Query.toPagedResponse(
|
||||
page: Int,
|
||||
size: Int,
|
||||
transform: (ResultRow) -> T
|
||||
): PagedResponse<T> {
|
||||
// Validate input parameters
|
||||
require(page >= 0) { "Page number must be non-negative" }
|
||||
require(size > 0) { "Page size must be positive" }
|
||||
|
||||
// Calculate the total count first (executes a COUNT query)
|
||||
val totalCount = this.count()
|
||||
|
||||
// If there are no results, return an empty page
|
||||
if (totalCount == 0L) {
|
||||
return PagedResponse.create(
|
||||
content = emptyList(),
|
||||
page = page,
|
||||
size = size,
|
||||
totalElements = 0,
|
||||
totalPages = 0,
|
||||
hasNext = false,
|
||||
hasPrevious = page > 0
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate total pages - use ceil division to ensure we round up
|
||||
val totalPages = ((totalCount + size - 1) / size).toInt()
|
||||
|
||||
// Ensure the requested page exists (if page is beyond available pages, return the last page)
|
||||
val adjustedPage = if (page >= totalPages) (totalPages - 1).coerceAtLeast(0) else page
|
||||
|
||||
// Then apply pagination and transform results
|
||||
val content = this.paginate(adjustedPage, size).map(transform)
|
||||
|
||||
return PagedResponse.create(
|
||||
content = content,
|
||||
page = adjustedPage,
|
||||
size = size,
|
||||
totalElements = totalCount,
|
||||
totalPages = totalPages,
|
||||
hasNext = adjustedPage < totalPages - 1,
|
||||
hasPrevious = adjustedPage > 0
|
||||
)
|
||||
}
|
||||
|
||||
object DatabaseUtils {
|
||||
|
||||
fun tableExists(tableName: String, database: Database? = null): Boolean {
|
||||
return try {
|
||||
transaction(database) {
|
||||
// Postgres-spezifischer, robuster Ansatz über to_regclass
|
||||
val valid = tableName.trim()
|
||||
if (!valid.matches(Regex("^[A-Za-z_][A-Za-z0-9_]*$"))) return@transaction false
|
||||
exec("SELECT to_regclass('$valid')") { rs ->
|
||||
if (rs.next()) rs.getString(1) else null
|
||||
} != null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@JvmName("createIndexIfNotExistsArray")
|
||||
fun createIndexIfNotExists(
|
||||
tableName: String,
|
||||
indexName: String,
|
||||
columns: Array<String>,
|
||||
unique: Boolean = false,
|
||||
database: Database? = null
|
||||
): Result<Unit> = createIndexIfNotExists(tableName, indexName, *columns, unique = unique, database = database)
|
||||
|
||||
@JvmName("createIndexIfNotExistsVararg")
|
||||
fun createIndexIfNotExists(
|
||||
tableName: String,
|
||||
indexName: String,
|
||||
vararg columns: String,
|
||||
unique: Boolean = false,
|
||||
database: Database? = null
|
||||
): Result<Unit> {
|
||||
return transactionResult(database) {
|
||||
// Einfache Sanitization + Quoting der Identifier
|
||||
fun quoteIdent(name: String): String {
|
||||
require(name.matches(Regex("^[A-Za-z_][A-Za-z0-9_]*$"))) { "Ungültiger Identifier: $name" }
|
||||
return "\"$name\""
|
||||
}
|
||||
|
||||
val uniqueStr = if (unique) "UNIQUE" else ""
|
||||
val qTable = quoteIdent(tableName)
|
||||
val qIndex = quoteIdent(indexName)
|
||||
val cols = columns.map { quoteIdent(it) }.joinToString(", ")
|
||||
val sql = "CREATE $uniqueStr INDEX IF NOT EXISTS $qIndex ON $qTable ($cols)"
|
||||
exec(sql)
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun executeRawSql(sql: String, database: Database? = null): Result<Unit> = transactionResult(database) {
|
||||
exec(sql)
|
||||
Unit
|
||||
}
|
||||
|
||||
fun executeUpdate(sql: String, database: Database? = null): Result<Int> = transactionResult(database) {
|
||||
// Nutzt Exposed PreparedStatementApi, kein AutoCloseable
|
||||
val ps = this.connection.prepareStatement(sql, false)
|
||||
ps.executeUpdate()
|
||||
}
|
||||
|
||||
inline fun <T> batchInsert(
|
||||
table: Table,
|
||||
data: Iterable<T>,
|
||||
crossinline body: BatchInsertStatement.(T) -> Unit
|
||||
): Result<List<ResultRow>> {
|
||||
return transactionResult {
|
||||
table.batchInsert(data) { item ->
|
||||
body(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> ResultRow.getOrNull(column: Column<T>): T? {
|
||||
return try {
|
||||
this[column]
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun ResultRow.toMap(): Map<String, Any?> {
|
||||
val result = mutableMapOf<String, Any?>()
|
||||
this.fieldIndex.forEach { (expression, _) ->
|
||||
try {
|
||||
when (expression) {
|
||||
is Column<*> -> result[expression.name] = this[expression]
|
||||
else -> result[expression.toString()] = this[expression]
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Ignore columns that can't be read and log the error if needed
|
||||
// You could add logging here in a production environment
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user