(fix) Konfiguration-Setup Umbau zu SCS

This commit is contained in:
stefan
2025-07-19 14:13:51 +02:00
parent b3f8624aa9
commit e38ab27fbe
18 changed files with 655 additions and 121 deletions
+173
View File
@@ -0,0 +1,173 @@
# Konfigurationsmanagement
Dieses Dokument beschreibt, wie die Konfiguration des Meldestelle-Projekts verwaltet wird.
## Übersicht
Das Projekt verwendet einen mehrschichtigen Konfigurationsansatz, um verschiedene Umgebungen (Entwicklung, Test, Staging, Produktion) zu unterstützen. Die Konfiguration kann über folgende Quellen bereitgestellt werden:
1. Umgebungsvariablen (höchste Priorität)
2. Umgebungsspezifische Konfigurationsdateien (.properties)
3. Basis-Konfigurationsdatei (application.properties)
4. Standardwerte im Code (niedrigste Priorität)
## Konfigurationsquellen
### Umgebungsvariablen
Umgebungsvariablen haben die höchste Priorität und überschreiben alle anderen Konfigurationen. Sie werden typischerweise verwendet, um sensible Informationen wie Passwörter oder umgebungsspezifische Werte zu setzen.
Beispiel:
```bash
# Umgebung festlegen
export APP_ENV=PRODUCTION
# Datenbank-Konfiguration
export DB_HOST=db.example.com
export DB_PORT=5432
export DB_NAME=meldestelle_db
export DB_USER=db_user
export DB_PASSWORD=secret_password
# Server-Konfiguration
export API_PORT=8081
```
### Konfigurationsdateien
Das Projekt verwendet .properties-Dateien im `/config`-Verzeichnis. Die folgenden Dateien werden geladen (in dieser Reihenfolge):
1. `application.properties` - Basiseinstellungen für alle Umgebungen
2. Umgebungsspezifische Datei - abhängig von `APP_ENV`:
- `application-dev.properties` - Entwicklungsumgebung (Standard)
- `application-test.properties` - Testumgebung
- `application-staging.properties` - Staging-Umgebung
- `application-prod.properties` - Produktionsumgebung
## Umgebungen
Das Projekt unterstützt folgende Umgebungen:
| Umgebung | Beschreibung | Typische Verwendung |
|----------|-------------|--------------------|
| DEVELOPMENT | Lokale Entwicklungsumgebung | Lokale Entwicklung, Debug-Modus aktiv |
| TEST | Testumgebung | Automatisierte Tests, Integrationstests |
| STAGING | Vorabproduktionsumgebung | Manuelle Tests, UAT, Demos |
| PRODUCTION | Produktionsumgebung | Live-System |
Die aktuelle Umgebung wird über die Umgebungsvariable `APP_ENV` festgelegt. Wenn diese Variable nicht gesetzt ist, wird standardmäßig `DEVELOPMENT` verwendet.
## Konfigurationsstruktur
Die Konfiguration ist in mehrere Kategorien unterteilt:
### AppInfo
Allgemeine Anwendungsinformationen:
```properties
app.name=Meldestelle
app.version=1.0.0
app.description=Pferdesport Meldestelle System
```
### Server
Server-Konfiguration:
```properties
server.port=8081
server.host=0.0.0.0
server.workers=4
server.cors.enabled=true
server.cors.allowedOrigins=*
```
### Datenbank
Datenbank-Konfiguration:
```properties
database.host=localhost
database.port=5432
database.name=meldestelle_db
database.username=meldestelle_user
database.password=secure_password_change_me
database.maxPoolSize=10
database.autoMigrate=true
```
### Sicherheit
Sicherheitseinstellungen (JWT, etc.):
```properties
security.jwt.secret=your-secret-key
security.jwt.issuer=meldestelle-api
security.jwt.audience=meldestelle-clients
security.jwt.realm=meldestelle
security.jwt.expirationInMinutes=1440
```
### Logging
Logging-Konfiguration:
```properties
logging.level=INFO
logging.requests=true
logging.responses=false
```
## Verwendung im Code
Die Konfiguration wird über die zentrale `AppConfig`-Klasse bereitgestellt:
```kotlin
import at.mocode.shared.config.AppConfig
// Verwendung der Konfiguration
fun example() {
// Umgebung prüfen
if (AppConfig.environment.isDevelopment()) {
println("Debug-Modus aktiv")
}
// Server-Port abrufen
val port = AppConfig.server.port
// Datenbank-Konfiguration
val dbConfig = AppConfig.database
// JWT-Secret
val jwtSecret = AppConfig.security.jwt.secret
}
```
## Konfiguration für Docker
Bei Verwendung von Docker werden Umgebungsvariablen in der `.env`-Datei und im `docker-compose.yml` definiert:
```yaml
services:
server:
environment:
- APP_ENV=PRODUCTION
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=${POSTGRES_DB}
- DB_USER=${POSTGRES_USER}
- DB_PASSWORD=${POSTGRES_PASSWORD}
```
## Beste Praktiken
1. **Sensible Daten**: Speichern Sie niemals sensible Daten wie Passwörter oder API-Schlüssel direkt in Konfigurationsdateien, die in die Versionskontrolle eingecheckt werden. Verwenden Sie stattdessen Umgebungsvariablen.
2. **Umgebungsspezifische Konfiguration**: Verwenden Sie umgebungsspezifische Konfigurationsdateien nur für Werte, die sich zwischen den Umgebungen unterscheiden.
3. **Standardwerte**: Geben Sie für alle Konfigurationsparameter sinnvolle Standardwerte an, damit die Anwendung auch funktioniert, wenn nicht alle Konfigurationen explizit gesetzt sind.
4. **Validierung**: Validieren Sie kritische Konfigurationen beim Anwendungsstart, um Fehler frühzeitig zu erkennen.
5. **Dokumentation**: Halten Sie die Dokumentation der Konfigurationsparameter aktuell, damit neue Teammitglieder die Anwendung leicht konfigurieren können.
@@ -1,38 +1,30 @@
package at.mocode.gateway package at.mocode.gateway
import at.mocode.gateway.config.MigrationSetup import at.mocode.gateway.config.MigrationSetup
import at.mocode.shared.config.AppConfig
import at.mocode.shared.database.DatabaseConfig import at.mocode.shared.database.DatabaseConfig
import at.mocode.shared.database.DatabaseFactory import at.mocode.shared.database.DatabaseFactory
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.engine.* import io.ktor.server.engine.*
import io.ktor.server.netty.* import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.routing.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.*
fun main() { fun main() {
// Konfiguration laden (wird automatisch beim ersten Zugriff auf AppConfig initialisiert)
val config = AppConfig
// Datenbank initialisieren // Datenbank initialisieren
val databaseConfig = DatabaseConfig.fromEnv() DatabaseFactory.init(config.database)
DatabaseFactory.init(databaseConfig)
// Migrationen ausführen // Migrationen ausführen
MigrationSetup.runMigrations() MigrationSetup.runMigrations()
// Server starten // Server starten
embeddedServer(Netty, port = System.getenv("API_PORT")?.toIntOrNull() ?: 8081) { embeddedServer(Netty, port = config.server.port, host = config.server.host) {
configureApplication() module()
}.start(wait = true) }.start(wait = true)
} }
fun Application.configureApplication() {
install(ContentNegotiation) {
json()
}
routing {
get("/health") {
call.respond(mapOf("status" to "OK"))
}
}
}
@@ -0,0 +1,53 @@
package at.mocode.gateway
import at.mocode.shared.config.AppConfig
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.module() {
val config = AppConfig
// ContentNegotiation installieren
install(ContentNegotiation) {
json()
}
// CORS installieren, wenn aktiviert
if (config.server.cors.enabled) {
install(CORS) {
if (config.server.cors.allowedOrigins.contains("*")) {
anyHost()
} else {
config.server.cors.allowedOrigins.forEach { allowHost(it, schemes = listOf("http", "https")) }
}
allowHeader(HttpHeaders.ContentType)
allowHeader(HttpHeaders.Authorization)
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Get)
allowMethod(HttpMethod.Post)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
}
}
// Call-Logging installieren
if (config.logging.logRequests) {
install(CallLogging)
}
routing {
// Hauptrouten
get("/") {
call.respondText(
"${config.appInfo.name} API v${config.appInfo.version} (${config.environment})",
ContentType.Text.Plain
)
}
}
}
+13
View File
@@ -0,0 +1,13 @@
# Entwicklungsumgebung spezifische Konfiguration
# Server-Einstellungen
server.port=8081
# Datenbank-Einstellungen
database.host=localhost
database.port=5432
# Logging-Einstellungen
logging.level=DEBUG
logging.requests=true
logging.responses=true
+16
View File
@@ -0,0 +1,16 @@
# Produktionsumgebung spezifische Konfiguration
# Server-Einstellungen
server.port=8081
server.workers=4
server.cors.allowedOrigins=https://meldestelle.at,https://app.meldestelle.at
# Datenbank-Einstellungen
database.host=db
database.port=5432
database.maxPoolSize=20
# Logging-Einstellungen
logging.level=INFO
logging.requests=true
logging.responses=false
+16
View File
@@ -0,0 +1,16 @@
# Staging-Umgebung spezifische Konfiguration
# Server-Einstellungen
server.port=8081
server.workers=2
server.cors.allowedOrigins=https://staging.meldestelle.at
# Datenbank-Einstellungen
database.host=db
database.port=5432
database.name=meldestelle_staging_db
# Logging-Einstellungen
logging.level=INFO
logging.requests=true
logging.responses=false
+14
View File
@@ -0,0 +1,14 @@
# Testumgebung spezifische Konfiguration
# Server-Einstellungen
server.port=8082
# Datenbank-Einstellungen
database.host=localhost
database.port=5432
database.name=meldestelle_test_db
# Logging-Einstellungen
logging.level=DEBUG
logging.requests=true
logging.responses=true
+32
View File
@@ -0,0 +1,32 @@
# Allgemeine Anwendungseinstellungen
app.name=Meldestelle
app.version=1.0.0
app.description=Pferdesport Meldestelle System
# Server-Einstellungen
server.port=8081
server.host=0.0.0.0
server.workers=4
server.cors.enabled=true
server.cors.allowedOrigins=*
# Datenbank-Einstellungen
database.host=localhost
database.port=5432
database.name=meldestelle_db
database.username=meldestelle_user
database.password=secure_password_change_me
database.maxPoolSize=10
database.autoMigrate=true
# Sicherheits-Einstellungen
security.jwt.secret=default-jwt-secret-key-please-change-in-production
security.jwt.issuer=meldestelle-api
security.jwt.audience=meldestelle-clients
security.jwt.realm=meldestelle
security.jwt.expirationInMinutes=1440
# Logging-Einstellungen
logging.level=INFO
logging.requests=true
logging.responses=false
@@ -1,16 +1,14 @@
package at.mocode.events.infrastructure.repository package at.mocode.events.infrastructure.repository
import at.mocode.enums.SparteE
import at.mocode.events.domain.model.Veranstaltung import at.mocode.events.domain.model.Veranstaltung
import at.mocode.events.domain.repository.VeranstaltungRepository import at.mocode.events.domain.repository.VeranstaltungRepository
import at.mocode.enums.SparteE
import com.benasher44.uuid.Uuid import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.statements.UpdateBuilder import org.jetbrains.exposed.sql.statements.UpdateBuilder
/** /**
@@ -22,21 +20,21 @@ import org.jetbrains.exposed.sql.statements.UpdateBuilder
class VeranstaltungRepositoryImpl : VeranstaltungRepository { class VeranstaltungRepositoryImpl : VeranstaltungRepository {
override suspend fun findById(id: Uuid): Veranstaltung? { override suspend fun findById(id: Uuid): Veranstaltung? {
return VeranstaltungTable.select { VeranstaltungTable.id eq id } return VeranstaltungTable.selectAll().where { VeranstaltungTable.id eq id }
.map { rowToVeranstaltung(it) } .map { rowToVeranstaltung(it) }
.singleOrNull() .singleOrNull()
} }
override suspend fun findByName(searchTerm: String, limit: Int): List<Veranstaltung> { override suspend fun findByName(searchTerm: String, limit: Int): List<Veranstaltung> {
val searchPattern = "%$searchTerm%" val searchPattern = "%$searchTerm%"
return VeranstaltungTable.select { VeranstaltungTable.name like searchPattern } return VeranstaltungTable.selectAll().where { VeranstaltungTable.name like searchPattern }
.orderBy(VeranstaltungTable.startDatum, SortOrder.DESC) .orderBy(VeranstaltungTable.startDatum, SortOrder.DESC)
.limit(limit) .limit(limit)
.map { rowToVeranstaltung(it) } .map { rowToVeranstaltung(it) }
} }
override suspend fun findByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): List<Veranstaltung> { override suspend fun findByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): List<Veranstaltung> {
val query = VeranstaltungTable.select { VeranstaltungTable.veranstalterVereinId eq vereinId } val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.veranstalterVereinId eq vereinId }
return if (activeOnly) { return if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true } query.andWhere { VeranstaltungTable.istAktiv eq true }
@@ -47,9 +45,9 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
} }
override suspend fun findByDateRange(startDate: LocalDate, endDate: LocalDate, activeOnly: Boolean): List<Veranstaltung> { override suspend fun findByDateRange(startDate: LocalDate, endDate: LocalDate, activeOnly: Boolean): List<Veranstaltung> {
val query = VeranstaltungTable.select { val query = VeranstaltungTable.selectAll().where {
(VeranstaltungTable.startDatum greaterEq startDate) and (VeranstaltungTable.startDatum greaterEq startDate) and
(VeranstaltungTable.endDatum lessEq endDate) (VeranstaltungTable.endDatum lessEq endDate)
} }
return if (activeOnly) { return if (activeOnly) {
@@ -61,7 +59,7 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
} }
override suspend fun findByStartDate(date: LocalDate, activeOnly: Boolean): List<Veranstaltung> { override suspend fun findByStartDate(date: LocalDate, activeOnly: Boolean): List<Veranstaltung> {
val query = VeranstaltungTable.select { VeranstaltungTable.startDatum eq date } val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.startDatum eq date }
return if (activeOnly) { return if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true } query.andWhere { VeranstaltungTable.istAktiv eq true }
@@ -72,14 +70,14 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
} }
override suspend fun findAllActive(limit: Int, offset: Int): List<Veranstaltung> { override suspend fun findAllActive(limit: Int, offset: Int): List<Veranstaltung> {
return VeranstaltungTable.select { VeranstaltungTable.istAktiv eq true } return VeranstaltungTable.selectAll().where { VeranstaltungTable.istAktiv eq true }
.orderBy(VeranstaltungTable.startDatum, SortOrder.DESC) .orderBy(VeranstaltungTable.startDatum, SortOrder.DESC)
.limit(limit, offset.toLong()) .limit(limit, offset.toLong())
.map { rowToVeranstaltung(it) } .map { rowToVeranstaltung(it) }
} }
override suspend fun findPublicEvents(activeOnly: Boolean): List<Veranstaltung> { override suspend fun findPublicEvents(activeOnly: Boolean): List<Veranstaltung> {
val query = VeranstaltungTable.select { VeranstaltungTable.istOeffentlich eq true } val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.istOeffentlich eq true }
return if (activeOnly) { return if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true } query.andWhere { VeranstaltungTable.istAktiv eq true }
@@ -94,7 +92,9 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
val updatedVeranstaltung = veranstaltung.copy(updatedAt = now) val updatedVeranstaltung = veranstaltung.copy(updatedAt = now)
// Check if record exists // Check if record exists
val existingRecord = VeranstaltungTable.select { VeranstaltungTable.id eq veranstaltung.veranstaltungId }.singleOrNull() val existingRecord = VeranstaltungTable.selectAll()
.where { VeranstaltungTable.id eq veranstaltung.veranstaltungId }
.singleOrNull()
return if (existingRecord != null) { return if (existingRecord != null) {
// Update existing record // Update existing record
@@ -118,12 +118,12 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
} }
override suspend fun countActive(): Long { override suspend fun countActive(): Long {
return VeranstaltungTable.select { VeranstaltungTable.istAktiv eq true } return VeranstaltungTable.selectAll().where { VeranstaltungTable.istAktiv eq true }
.count() .count()
} }
override suspend fun countByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): Long { override suspend fun countByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): Long {
val query = VeranstaltungTable.select { VeranstaltungTable.veranstalterVereinId eq vereinId } val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.veranstalterVereinId eq vereinId }
return if (activeOnly) { return if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true } query.andWhere { VeranstaltungTable.istAktiv eq true }
@@ -41,7 +41,7 @@ class HorseRepositoryImpl : HorseRepository {
} }
override suspend fun findByOepsNummer(oepsNummer: String): DomPferd? { override suspend fun findByOepsNummer(oepsNummer: String): DomPferd? {
return HorseTable.select { HorseTable.oepsNummer eq oepsNummer } return HorseTable.selectAll().where { HorseTable.oepsNummer eq oepsNummer }
.map { rowToDomPferd(it) } .map { rowToDomPferd(it) }
.singleOrNull() .singleOrNull()
} }
@@ -224,17 +224,17 @@ class HorseRepositoryImpl : HorseRepository {
} }
override suspend fun existsByFeiNummer(feiNummer: String): Boolean { override suspend fun existsByFeiNummer(feiNummer: String): Boolean {
return HorseTable.select { HorseTable.feiNummer eq feiNummer } return HorseTable.selectAll().where { HorseTable.feiNummer eq feiNummer }
.count() > 0 .count() > 0
} }
override suspend fun countActive(): Long { override suspend fun countActive(): Long {
return HorseTable.select { HorseTable.istAktiv eq true } return HorseTable.selectAll().where { HorseTable.istAktiv eq true }
.count() .count()
} }
override suspend fun countByOwnerId(ownerId: Uuid, activeOnly: Boolean): Long { override suspend fun countByOwnerId(ownerId: Uuid, activeOnly: Boolean): Long {
val query = HorseTable.select { HorseTable.besitzerId eq ownerId } val query = HorseTable.selectAll().where { HorseTable.besitzerId eq ownerId }
return if (activeOnly) { return if (activeOnly) {
query.andWhere { HorseTable.istAktiv eq true } query.andWhere { HorseTable.istAktiv eq true }
@@ -6,8 +6,6 @@ import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.SortOrder
/** /**
* PostgreSQL implementation of LandRepository using Exposed ORM. * PostgreSQL implementation of LandRepository using Exposed ORM.
@@ -18,29 +16,29 @@ import org.jetbrains.exposed.sql.SortOrder
class LandRepositoryImpl : LandRepository { class LandRepositoryImpl : LandRepository {
override suspend fun findById(id: Uuid): LandDefinition? { override suspend fun findById(id: Uuid): LandDefinition? {
return LandTable.select { LandTable.id eq id } return LandTable.selectAll().where { LandTable.id eq id }
.singleOrNull() .singleOrNull()
?.toLandDefinition() ?.toLandDefinition()
} }
override suspend fun findByIsoAlpha2Code(isoAlpha2Code: String): LandDefinition? { override suspend fun findByIsoAlpha2Code(isoAlpha2Code: String): LandDefinition? {
return LandTable.select { LandTable.isoAlpha2Code eq isoAlpha2Code } return LandTable.selectAll().where { LandTable.isoAlpha2Code eq isoAlpha2Code }
.singleOrNull() .singleOrNull()
?.toLandDefinition() ?.toLandDefinition()
} }
override suspend fun findByIsoAlpha3Code(isoAlpha3Code: String): LandDefinition? { override suspend fun findByIsoAlpha3Code(isoAlpha3Code: String): LandDefinition? {
return LandTable.select { LandTable.isoAlpha3Code eq isoAlpha3Code } return LandTable.selectAll().where { LandTable.isoAlpha3Code eq isoAlpha3Code }
.singleOrNull() .singleOrNull()
?.toLandDefinition() ?.toLandDefinition()
} }
override suspend fun findByName(searchTerm: String, limit: Int): List<LandDefinition> { override suspend fun findByName(searchTerm: String, limit: Int): List<LandDefinition> {
val searchPattern = "%$searchTerm%" val searchPattern = "%$searchTerm%"
return LandTable.select { return LandTable.selectAll().where {
(LandTable.nameGerman like searchPattern) or (LandTable.nameGerman like searchPattern) or
(LandTable.nameEnglish like searchPattern) or (LandTable.nameEnglish like searchPattern) or
(LandTable.nameLocal like searchPattern) (LandTable.nameLocal like searchPattern)
} }
.orderBy(LandTable.sortierReihenfolge) .orderBy(LandTable.sortierReihenfolge)
.limit(limit) .limit(limit)
@@ -48,7 +46,7 @@ class LandRepositoryImpl : LandRepository {
} }
override suspend fun findAllActive(orderBySortierung: Boolean): List<LandDefinition> { override suspend fun findAllActive(orderBySortierung: Boolean): List<LandDefinition> {
val query = LandTable.select { LandTable.isActive eq true } val query = LandTable.selectAll().where { LandTable.isActive eq true }
return if (orderBySortierung) { return if (orderBySortierung) {
query.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC) query.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC)
@@ -58,17 +56,13 @@ class LandRepositoryImpl : LandRepository {
} }
override suspend fun findEuMembers(): List<LandDefinition> { override suspend fun findEuMembers(): List<LandDefinition> {
return LandTable.select { return LandTable.selectAll().where { (LandTable.isActive eq true) and (LandTable.isEuMember eq true) }
(LandTable.isActive eq true) and (LandTable.isEuMember eq true)
}
.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC) .orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC)
.map { it.toLandDefinition() } .map { it.toLandDefinition() }
} }
override suspend fun findEwrMembers(): List<LandDefinition> { override suspend fun findEwrMembers(): List<LandDefinition> {
return LandTable.select { return LandTable.selectAll().where { (LandTable.isActive eq true) and (LandTable.isEwrMember eq true) }
(LandTable.isActive eq true) and (LandTable.isEwrMember eq true)
}
.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC) .orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameGerman to SortOrder.ASC)
.map { it.toLandDefinition() } .map { it.toLandDefinition() }
} }
@@ -77,7 +71,7 @@ class LandRepositoryImpl : LandRepository {
val now = Clock.System.now() val now = Clock.System.now()
// Check if record exists // Check if record exists
val existingRecord = LandTable.select { LandTable.id eq land.landId }.singleOrNull() val existingRecord = LandTable.selectAll().where { LandTable.id eq land.landId }.singleOrNull()
return if (existingRecord != null) { return if (existingRecord != null) {
// Update existing record // Update existing record
@@ -126,17 +120,17 @@ class LandRepositoryImpl : LandRepository {
} }
override suspend fun existsByIsoAlpha2Code(isoAlpha2Code: String): Boolean { override suspend fun existsByIsoAlpha2Code(isoAlpha2Code: String): Boolean {
return LandTable.select { LandTable.isoAlpha2Code eq isoAlpha2Code } return LandTable.selectAll().where { LandTable.isoAlpha2Code eq isoAlpha2Code }
.count() > 0 .count() > 0
} }
override suspend fun existsByIsoAlpha3Code(isoAlpha3Code: String): Boolean { override suspend fun existsByIsoAlpha3Code(isoAlpha3Code: String): Boolean {
return LandTable.select { LandTable.isoAlpha3Code eq isoAlpha3Code } return LandTable.selectAll().where { LandTable.isoAlpha3Code eq isoAlpha3Code }
.count() > 0 .count() > 0
} }
override suspend fun countActive(): Long { override suspend fun countActive(): Long {
return LandTable.select { LandTable.isActive eq true }.count() return LandTable.selectAll().where { LandTable.isActive eq true }.count()
} }
/** /**
@@ -129,7 +129,7 @@ class UserAuthorizationService(
for (roleType in roles) { for (roleType in roles) {
// Find the role by type // Find the role by type
val rolle = rolleRepository.findByTyp(roleType) val rolle = rolleRepository.findByTyp(roleType)
if (rolle != null && rolle.rolleId != null) { if (rolle != null) {
// Get role permissions // Get role permissions
val rolleBerechtigungen = rolleBerechtigungRepository.findByRolleId(rolle.rolleId) val rolleBerechtigungen = rolleBerechtigungRepository.findByRolleId(rolle.rolleId)
.filter { it.istAktiv } .filter { it.istAktiv }
@@ -1,20 +1,16 @@
package at.mocode.members.infrastructure.repository package at.mocode.members.infrastructure.repository
// Import table definition and extension functions
import at.mocode.enums.BerechtigungE import at.mocode.enums.BerechtigungE
import at.mocode.members.domain.model.DomBerechtigung import at.mocode.members.domain.model.DomBerechtigung
import at.mocode.members.domain.repository.BerechtigungRepository import at.mocode.members.domain.repository.BerechtigungRepository
import com.benasher44.uuid.Uuid import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.toKotlinInstant import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.selectAll
// Import table definition and extension functions import org.jetbrains.exposed.sql.update
import at.mocode.members.infrastructure.repository.BerechtigungTable
import at.mocode.members.infrastructure.repository.insertOrUpdate
import at.mocode.members.infrastructure.repository.toLocalDateTime
import at.mocode.members.infrastructure.repository.toInstant
/** /**
* Exposed-based implementation of BerechtigungRepository. * Exposed-based implementation of BerechtigungRepository.
@@ -45,35 +41,35 @@ class BerechtigungRepositoryImpl : BerechtigungRepository {
} }
override suspend fun findById(berechtigungId: Uuid): DomBerechtigung? { override suspend fun findById(berechtigungId: Uuid): DomBerechtigung? {
return BerechtigungTable.select { BerechtigungTable.id eq berechtigungId } return BerechtigungTable.selectAll().where { BerechtigungTable.id eq berechtigungId }
.map { rowToDomBerechtigung(it) } .map { rowToDomBerechtigung(it) }
.singleOrNull() .singleOrNull()
} }
override suspend fun findByTyp(berechtigungTyp: BerechtigungE): DomBerechtigung? { override suspend fun findByTyp(berechtigungTyp: BerechtigungE): DomBerechtigung? {
return BerechtigungTable.select { BerechtigungTable.berechtigungTyp eq berechtigungTyp } return BerechtigungTable.selectAll().where { BerechtigungTable.berechtigungTyp eq berechtigungTyp }
.map { rowToDomBerechtigung(it) } .map { rowToDomBerechtigung(it) }
.singleOrNull() .singleOrNull()
} }
override suspend fun findByName(name: String): List<DomBerechtigung> { override suspend fun findByName(name: String): List<DomBerechtigung> {
val searchPattern = "%$name%" val searchPattern = "%$name%"
return BerechtigungTable.select { BerechtigungTable.name like searchPattern } return BerechtigungTable.selectAll().where { BerechtigungTable.name like searchPattern }
.map { rowToDomBerechtigung(it) } .map { rowToDomBerechtigung(it) }
} }
override suspend fun findByRessource(ressource: String): List<DomBerechtigung> { override suspend fun findByRessource(ressource: String): List<DomBerechtigung> {
return BerechtigungTable.select { BerechtigungTable.ressource eq ressource } return BerechtigungTable.selectAll().where { BerechtigungTable.ressource eq ressource }
.map { rowToDomBerechtigung(it) } .map { rowToDomBerechtigung(it) }
} }
override suspend fun findByAktion(aktion: String): List<DomBerechtigung> { override suspend fun findByAktion(aktion: String): List<DomBerechtigung> {
return BerechtigungTable.select { BerechtigungTable.aktion eq aktion } return BerechtigungTable.selectAll().where { BerechtigungTable.aktion eq aktion }
.map { rowToDomBerechtigung(it) } .map { rowToDomBerechtigung(it) }
} }
override suspend fun findAllActive(): List<DomBerechtigung> { override suspend fun findAllActive(): List<DomBerechtigung> {
return BerechtigungTable.select { BerechtigungTable.istAktiv eq true } return BerechtigungTable.selectAll().where { BerechtigungTable.istAktiv eq true }
.map { rowToDomBerechtigung(it) } .map { rowToDomBerechtigung(it) }
} }
@@ -103,7 +99,7 @@ class BerechtigungRepositoryImpl : BerechtigungRepository {
} }
override suspend fun existsByTyp(berechtigungTyp: BerechtigungE): Boolean { override suspend fun existsByTyp(berechtigungTyp: BerechtigungE): Boolean {
return BerechtigungTable.select { BerechtigungTable.berechtigungTyp eq berechtigungTyp } return BerechtigungTable.selectAll().where { BerechtigungTable.berechtigungTyp eq berechtigungTyp }
.count() > 0 .count() > 0
} }
@@ -1,20 +1,15 @@
package at.mocode.members.infrastructure.repository package at.mocode.members.infrastructure.repository
// Import table definition and extension functions
import at.mocode.members.domain.model.DomPerson import at.mocode.members.domain.model.DomPerson
import at.mocode.members.domain.repository.PersonRepository import at.mocode.members.domain.repository.PersonRepository
import com.benasher44.uuid.Uuid import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.toKotlinInstant import org.jetbrains.exposed.sql.ResultRow
import kotlinx.datetime.toKotlinLocalDate
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.or
// Import table definition and extension functions import org.jetbrains.exposed.sql.selectAll
import at.mocode.members.infrastructure.repository.PersonTable
import at.mocode.members.infrastructure.repository.insertOrUpdate
import at.mocode.members.infrastructure.repository.toLocalDateTime
import at.mocode.members.infrastructure.repository.toInstant
/** /**
* Exposed-based implementation of PersonRepository. * Exposed-based implementation of PersonRepository.
@@ -25,34 +20,34 @@ import at.mocode.members.infrastructure.repository.toInstant
class PersonRepositoryImpl : PersonRepository { class PersonRepositoryImpl : PersonRepository {
override suspend fun findById(id: Uuid): DomPerson? { override suspend fun findById(id: Uuid): DomPerson? {
return PersonTable.select { PersonTable.id eq id } return PersonTable.selectAll().where { PersonTable.id eq id }
.map { rowToDomPerson(it) } .map { rowToDomPerson(it) }
.singleOrNull() .singleOrNull()
} }
override suspend fun findByOepsSatzNr(oepsSatzNr: String): DomPerson? { override suspend fun findByOepsSatzNr(oepsSatzNr: String): DomPerson? {
return PersonTable.select { PersonTable.oepsSatzNr eq oepsSatzNr } return PersonTable.selectAll().where { PersonTable.oepsSatzNr eq oepsSatzNr }
.map { rowToDomPerson(it) } .map { rowToDomPerson(it) }
.singleOrNull() .singleOrNull()
} }
override suspend fun findByStammVereinId(vereinId: Uuid): List<DomPerson> { override suspend fun findByStammVereinId(vereinId: Uuid): List<DomPerson> {
return PersonTable.select { PersonTable.stammVereinId eq vereinId } return PersonTable.selectAll().where { PersonTable.stammVereinId eq vereinId }
.map { rowToDomPerson(it) } .map { rowToDomPerson(it) }
} }
override suspend fun findByName(searchTerm: String, limit: Int): List<DomPerson> { override suspend fun findByName(searchTerm: String, limit: Int): List<DomPerson> {
val searchPattern = "%$searchTerm%" val searchPattern = "%$searchTerm%"
return PersonTable.select { return PersonTable.selectAll().where {
(PersonTable.nachname like searchPattern) or (PersonTable.nachname like searchPattern) or
(PersonTable.vorname like searchPattern) (PersonTable.vorname like searchPattern)
} }
.limit(limit) .limit(limit)
.map { rowToDomPerson(it) } .map { rowToDomPerson(it) }
} }
override suspend fun findAllActive(limit: Int, offset: Int): List<DomPerson> { override suspend fun findAllActive(limit: Int, offset: Int): List<DomPerson> {
return PersonTable.select { PersonTable.istAktiv eq true } return PersonTable.selectAll().where { PersonTable.istAktiv eq true }
.limit(limit, offset.toLong()) .limit(limit, offset.toLong())
.map { rowToDomPerson(it) } .map { rowToDomPerson(it) }
} }
@@ -100,12 +95,12 @@ class PersonRepositoryImpl : PersonRepository {
} }
override suspend fun existsByOepsSatzNr(oepsSatzNr: String): Boolean { override suspend fun existsByOepsSatzNr(oepsSatzNr: String): Boolean {
return PersonTable.select { PersonTable.oepsSatzNr eq oepsSatzNr } return PersonTable.selectAll().where { PersonTable.oepsSatzNr eq oepsSatzNr }
.count() > 0 .count() > 0
} }
override suspend fun countActive(): Long { override suspend fun countActive(): Long {
return PersonTable.select { PersonTable.istAktiv eq true } return PersonTable.selectAll().where { PersonTable.istAktiv eq true }
.count() .count()
} }
@@ -1,19 +1,12 @@
package at.mocode.members.infrastructure.repository package at.mocode.members.infrastructure.repository
// Import table definition and extension functions
import at.mocode.members.domain.model.DomVerein import at.mocode.members.domain.model.DomVerein
import at.mocode.members.domain.repository.VereinRepository import at.mocode.members.domain.repository.VereinRepository
import com.benasher44.uuid.Uuid import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.toKotlinInstant
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
// Import table definition and extension functions
import at.mocode.members.infrastructure.repository.VereinTable
import at.mocode.members.infrastructure.repository.insertOrUpdate
import at.mocode.members.infrastructure.repository.toLocalDateTime
import at.mocode.members.infrastructure.repository.toInstant
/** /**
* Exposed-based implementation of VereinRepository. * Exposed-based implementation of VereinRepository.
@@ -24,48 +17,48 @@ import at.mocode.members.infrastructure.repository.toInstant
class VereinRepositoryImpl : VereinRepository { class VereinRepositoryImpl : VereinRepository {
override suspend fun findById(id: Uuid): DomVerein? { override suspend fun findById(id: Uuid): DomVerein? {
return VereinTable.select { VereinTable.id eq id } return VereinTable.selectAll().where { VereinTable.id eq id }
.map { rowToDomVerein(it) } .map { rowToDomVerein(it) }
.singleOrNull() .singleOrNull()
} }
override suspend fun findByOepsVereinsNr(oepsVereinsNr: String): DomVerein? { override suspend fun findByOepsVereinsNr(oepsVereinsNr: String): DomVerein? {
return VereinTable.select { VereinTable.oepsVereinsNr eq oepsVereinsNr } return VereinTable.selectAll().where { VereinTable.oepsVereinsNr eq oepsVereinsNr }
.map { rowToDomVerein(it) } .map { rowToDomVerein(it) }
.singleOrNull() .singleOrNull()
} }
override suspend fun findByName(searchTerm: String, limit: Int): List<DomVerein> { override suspend fun findByName(searchTerm: String, limit: Int): List<DomVerein> {
val searchPattern = "%$searchTerm%" val searchPattern = "%$searchTerm%"
return VereinTable.select { return VereinTable.selectAll().where {
(VereinTable.name like searchPattern) or (VereinTable.name like searchPattern) or
(VereinTable.kuerzel like searchPattern) (VereinTable.kuerzel like searchPattern)
} }
.limit(limit) .limit(limit)
.map { rowToDomVerein(it) } .map { rowToDomVerein(it) }
} }
override suspend fun findByBundeslandId(bundeslandId: Uuid): List<DomVerein> { override suspend fun findByBundeslandId(bundeslandId: Uuid): List<DomVerein> {
return VereinTable.select { VereinTable.bundeslandId eq bundeslandId } return VereinTable.selectAll().where { VereinTable.bundeslandId eq bundeslandId }
.map { rowToDomVerein(it) } .map { rowToDomVerein(it) }
} }
override suspend fun findByLandId(landId: Uuid): List<DomVerein> { override suspend fun findByLandId(landId: Uuid): List<DomVerein> {
return VereinTable.select { VereinTable.landId eq landId } return VereinTable.selectAll().where { VereinTable.landId eq landId }
.map { rowToDomVerein(it) } .map { rowToDomVerein(it) }
} }
override suspend fun findAllActive(limit: Int, offset: Int): List<DomVerein> { override suspend fun findAllActive(limit: Int, offset: Int): List<DomVerein> {
return VereinTable.select { VereinTable.istAktiv eq true } return VereinTable.selectAll().where { VereinTable.istAktiv eq true }
.limit(limit, offset.toLong()) .limit(limit, offset.toLong())
.map { rowToDomVerein(it) } .map { rowToDomVerein(it) }
} }
override suspend fun findByLocation(searchTerm: String, limit: Int): List<DomVerein> { override suspend fun findByLocation(searchTerm: String, limit: Int): List<DomVerein> {
val searchPattern = "%$searchTerm%" val searchPattern = "%$searchTerm%"
return VereinTable.select { return VereinTable.selectAll().where {
(VereinTable.ort like searchPattern) or (VereinTable.ort like searchPattern) or
(VereinTable.plz like searchPattern) (VereinTable.plz like searchPattern)
} }
.limit(limit) .limit(limit)
.map { rowToDomVerein(it) } .map { rowToDomVerein(it) }
@@ -104,19 +97,18 @@ class VereinRepositoryImpl : VereinRepository {
} }
override suspend fun existsByOepsVereinsNr(oepsVereinsNr: String): Boolean { override suspend fun existsByOepsVereinsNr(oepsVereinsNr: String): Boolean {
return VereinTable.select { VereinTable.oepsVereinsNr eq oepsVereinsNr } return VereinTable.selectAll().where { VereinTable.oepsVereinsNr eq oepsVereinsNr }
.count() > 0 .count() > 0
} }
override suspend fun countActive(): Long { override suspend fun countActive(): Long {
return VereinTable.select { VereinTable.istAktiv eq true } return VereinTable.selectAll().where { VereinTable.istAktiv eq true }
.count() .count()
} }
override suspend fun countActiveByBundeslandId(bundeslandId: Uuid): Long { override suspend fun countActiveByBundeslandId(bundeslandId: Uuid): Long {
return VereinTable.select { return VereinTable.selectAll()
(VereinTable.istAktiv eq true) and (VereinTable.bundeslandId eq bundeslandId) .where { (VereinTable.istAktiv eq true) and (VereinTable.bundeslandId eq bundeslandId) }
}
.count() .count()
} }
@@ -0,0 +1,187 @@
package at.mocode.shared.config
import at.mocode.shared.database.DatabaseConfig
import java.io.File
import java.util.Properties
/**
* Zentrale Konfigurationsverwaltung für die Anwendung.
* Lädt Konfigurationen aus verschiedenen Quellen (Umgebungsvariablen, Property-Dateien).
*/
object AppConfig {
// Aktuelle Umgebung
val environment: AppEnvironment = AppEnvironment.current()
// Anwendungs-Informationen
val appInfo = AppInfoConfig()
// Server-Konfiguration
val server = ServerConfig()
// Sicherheits-Konfiguration
val security = SecurityConfig()
// Logging-Konfiguration
val logging = LoggingConfig()
// Datenbank-Konfiguration (wird nach dem Laden der Properties initialisiert)
val database: DatabaseConfig
init {
// Lade Umgebungsspezifische Properties
val props = loadProperties()
// Konfiguriere Komponenten mit Properties
appInfo.configure(props)
server.configure(props)
security.configure(props)
logging.configure(props)
// Datenbank-Konfiguration mit Properties initialisieren
database = DatabaseConfig.fromEnv(props)
// Log Konfigurationsinformationen
if (!AppEnvironment.isProduction()) {
println("=== Anwendungskonfiguration ===")
println("Umgebung: $environment")
println("App: ${appInfo.name} v${appInfo.version}")
println("Server: Port ${server.port}, ${server.workers} Worker")
println("Datenbank: ${database.jdbcUrl}")
println("===============================\n")
}
}
/**
* Lädt die Properties für die aktuelle Umgebung.
*/
private fun loadProperties(): Properties {
val props = Properties()
// Lade Basis-Properties
loadPropertiesFile("application.properties", props)
// Lade umgebungsspezifische Properties
val envFile = when (environment) {
AppEnvironment.DEVELOPMENT -> "application-dev.properties"
AppEnvironment.TEST -> "application-test.properties"
AppEnvironment.STAGING -> "application-staging.properties"
AppEnvironment.PRODUCTION -> "application-prod.properties"
}
loadPropertiesFile(envFile, props)
return props
}
/**
* Lädt eine Property-Datei, wenn sie existiert.
*/
private fun loadPropertiesFile(filename: String, props: Properties) {
val resourceStream = javaClass.classLoader.getResourceAsStream(filename)
if (resourceStream != null) {
props.load(resourceStream)
resourceStream.close()
} else {
// Versuche aus dem Dateisystem zu laden
val file = File("config/$filename")
if (file.exists()) {
file.inputStream().use { props.load(it) }
}
}
}
/**
* Gibt den Wert einer Property zurück, wobei die Priorität wie folgt ist:
* 1. Umgebungsvariable
* 2. Property aus Datei
* 3. Standardwert
*/
fun getProperty(key: String, defaultValue: String? = null): String? {
val envKey = key.replace('.', '_').uppercase()
return System.getenv(envKey) ?: defaultValue
}
}
/**
* Konfiguration für Anwendungsinformationen.
*/
class AppInfoConfig {
var name: String = "Meldestelle"
var version: String = "1.0.0"
var description: String = "Pferdesport Meldestelle System"
fun configure(props: Properties) {
name = props.getProperty("app.name", name)
version = props.getProperty("app.version", version)
description = props.getProperty("app.description", description)
}
}
/**
* Konfiguration für den Server.
*/
class ServerConfig {
var port: Int = System.getenv("API_PORT")?.toIntOrNull() ?: 8081
var host: String = System.getenv("API_HOST") ?: "0.0.0.0"
var workers: Int = Runtime.getRuntime().availableProcessors()
var cors: CorsConfig = CorsConfig()
fun configure(props: Properties) {
port = props.getProperty("server.port")?.toIntOrNull() ?: port
host = props.getProperty("server.host") ?: host
workers = props.getProperty("server.workers")?.toIntOrNull() ?: workers
// CORS Konfiguration
cors.enabled = props.getProperty("server.cors.enabled")?.toBoolean() ?: cors.enabled
props.getProperty("server.cors.allowedOrigins")?.split(",")?.map { it.trim() }?.let {
cors.allowedOrigins = it
}
}
class CorsConfig {
var enabled: Boolean = true
var allowedOrigins: List<String> = listOf("*")
}
}
/**
* Konfiguration für die Sicherheit.
*/
class SecurityConfig {
var jwt = JwtConfig()
fun configure(props: Properties) {
// JWT Konfiguration
jwt.secret = System.getenv("JWT_SECRET") ?: props.getProperty("security.jwt.secret") ?: jwt.secret
jwt.issuer = System.getenv("JWT_ISSUER") ?: props.getProperty("security.jwt.issuer") ?: jwt.issuer
jwt.audience = System.getenv("JWT_AUDIENCE") ?: props.getProperty("security.jwt.audience") ?: jwt.audience
jwt.realm = System.getenv("JWT_REALM") ?: props.getProperty("security.jwt.realm") ?: jwt.realm
props.getProperty("security.jwt.expirationInMinutes")?.toLongOrNull()?.let {
jwt.expirationInMinutes = it
}
}
class JwtConfig {
var secret: String = "default-jwt-secret-key-please-change-in-production"
var issuer: String = "meldestelle-api"
var audience: String = "meldestelle-clients"
var realm: String = "meldestelle"
var expirationInMinutes: Long = 60 * 24 // 24 Stunden
}
}
/**
* Konfiguration für das Logging.
*/
class LoggingConfig {
var level: String = if (AppEnvironment.isProduction()) "INFO" else "DEBUG"
var logRequests: Boolean = true
var logResponses: Boolean = !AppEnvironment.isProduction()
fun configure(props: Properties) {
level = props.getProperty("logging.level") ?: level
logRequests = props.getProperty("logging.requests")?.toBoolean() ?: logRequests
logResponses = props.getProperty("logging.responses")?.toBoolean() ?: logResponses
}
}
@@ -0,0 +1,48 @@
package at.mocode.shared.config
/**
* Aufzählung der verschiedenen Anwendungsumgebungen.
*/
enum class AppEnvironment {
DEVELOPMENT, // Lokale Entwicklungsumgebung
TEST, // Testumgebung (CI/CD, Integrationstests)
STAGING, // Vorabproduktionsumgebung
PRODUCTION; // Produktionsumgebung
companion object {
/**
* Ermittelt die aktuelle Umgebung basierend auf der APP_ENV Umgebungsvariable.
*
* @return Die aktuelle Umgebung (Standardmäßig DEVELOPMENT wenn nicht definiert)
*/
fun current(): AppEnvironment {
val envName = System.getenv("APP_ENV")?.uppercase() ?: "DEVELOPMENT"
return try {
valueOf(envName)
} catch (e: IllegalArgumentException) {
println("Warnung: Unbekannte Umgebung '$envName', verwende DEVELOPMENT")
DEVELOPMENT
}
}
/**
* Prüft, ob die aktuelle Umgebung die Entwicklungsumgebung ist.
*/
fun isDevelopment() = current() == DEVELOPMENT
/**
* Prüft, ob die aktuelle Umgebung die Testumgebung ist.
*/
fun isTest() = current() == TEST
/**
* Prüft, ob die aktuelle Umgebung die Staging-Umgebung ist.
*/
fun isStaging() = current() == STAGING
/**
* Prüft, ob die aktuelle Umgebung die Produktionsumgebung ist.
*/
fun isProduction() = current() == PRODUCTION
}
}
@@ -1,32 +1,45 @@
package at.mocode.shared.database package at.mocode.shared.database
import java.util.Properties
/** /**
* Konfiguration für die Datenbankverbindung. * Konfiguration für die Datenbankverbindung.
* Parameter werden aus Umgebungsvariablen gelesen oder Standardwerte verwendet. * Parameter werden aus Umgebungsvariablen oder Property-Dateien gelesen.
*/ */
data class DatabaseConfig( data class DatabaseConfig(
val jdbcUrl: String, val jdbcUrl: String,
val username: String, val username: String,
val password: String, val password: String,
val driverClassName: String = "org.postgresql.Driver", val driverClassName: String = "org.postgresql.Driver",
val maxPoolSize: Int = 10 val maxPoolSize: Int = 10,
val autoMigrate: Boolean = true
) { ) {
companion object { companion object {
/** /**
* Erstellt eine Datenbank-Konfiguration aus Umgebungsvariablen. * Erstellt eine Datenbank-Konfiguration aus Umgebungsvariablen und Properties.
* Wenn keine Umgebungsvariablen gefunden werden, werden Standardwerte für die Entwicklung verwendet. * Wenn keine Umgebungsvariablen gefunden werden, werden Standardwerte für die Entwicklung verwendet.
*/ */
fun fromEnv(): DatabaseConfig { fun fromEnv(props: Properties = Properties()): DatabaseConfig {
val host = System.getenv("DB_HOST") ?: "localhost" // Priorität: Umgebungsvariablen > Properties > Standardwerte
val port = System.getenv("DB_PORT") ?: "5432" val host = System.getenv("DB_HOST") ?: props.getProperty("database.host") ?: "localhost"
val database = System.getenv("DB_NAME") ?: "meldestelle_db" val port = System.getenv("DB_PORT") ?: props.getProperty("database.port") ?: "5432"
val username = System.getenv("DB_USER") ?: "meldestelle_user" val database = System.getenv("DB_NAME") ?: props.getProperty("database.name") ?: "meldestelle_db"
val password = System.getenv("DB_PASSWORD") ?: "secure_password_change_me" val username = System.getenv("DB_USER") ?: props.getProperty("database.username") ?: "meldestelle_user"
val password = System.getenv("DB_PASSWORD") ?: props.getProperty("database.password") ?: "secure_password_change_me"
val maxPoolSize = System.getenv("DB_MAX_POOL_SIZE")?.toIntOrNull()
?: props.getProperty("database.maxPoolSize")?.toIntOrNull()
?: 10
val autoMigrate = System.getenv("DB_AUTO_MIGRATE")?.toBoolean()
?: props.getProperty("database.autoMigrate")?.toBoolean()
?: true
return DatabaseConfig( return DatabaseConfig(
jdbcUrl = "jdbc:postgresql://$host:$port/$database", jdbcUrl = "jdbc:postgresql://$host:$port/$database",
username = username, username = username,
password = password password = password,
driverClassName = "org.postgresql.Driver",
maxPoolSize = maxPoolSize,
autoMigrate = autoMigrate
) )
} }
} }