(fix) Swagger/OpenAPI-Dokumentation implementieren
This commit is contained in:
@@ -11,6 +11,8 @@ import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.plugins.cors.routing.*
|
||||
import io.ktor.server.plugins.defaultheaders.*
|
||||
import io.ktor.server.plugins.statuspages.*
|
||||
import io.ktor.server.plugins.openapi.*
|
||||
import io.ktor.server.plugins.swagger.*
|
||||
import io.ktor.server.response.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@@ -4,6 +4,8 @@ import at.mocode.config.AppConfig
|
||||
import at.mocode.routes.RouteConfiguration.configureApiRoutes
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.http.content.staticResources
|
||||
import io.ktor.server.plugins.openapi.openAPI
|
||||
import io.ktor.server.plugins.swagger.swaggerUI
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.routing
|
||||
@@ -31,5 +33,11 @@ fun Application.configureRouting() {
|
||||
|
||||
// Configure all API routes using the centralized configuration
|
||||
configureApiRoutes()
|
||||
|
||||
// OpenAPI specification endpoint
|
||||
openAPI(path = "openapi", swaggerFile = "openapi.yaml")
|
||||
|
||||
// Swagger UI endpoint
|
||||
swaggerUI(path = "swagger", swaggerFile = "openapi.yaml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package at.mocode.repositories
|
||||
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import org.jetbrains.exposed.sql.*
|
||||
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.transactions.transaction
|
||||
|
||||
/**
|
||||
* Base repository class that provides common database operations
|
||||
* and eliminates code duplication across repository implementations.
|
||||
*/
|
||||
abstract class BaseRepository<T, TTable : Table>(
|
||||
protected val table: TTable
|
||||
) {
|
||||
|
||||
/**
|
||||
* Abstract method to map a database row to the domain model
|
||||
*/
|
||||
protected abstract fun rowToModel(row: ResultRow): T
|
||||
|
||||
/**
|
||||
* Abstract method to get the ID column for the table
|
||||
*/
|
||||
protected abstract fun getIdColumn(): Column<Uuid>
|
||||
|
||||
/**
|
||||
* Abstract method to populate insert statement with model data
|
||||
*/
|
||||
protected abstract fun populateInsert(statement: UpdateBuilder<Number>, model: T, now: Instant)
|
||||
|
||||
/**
|
||||
* Abstract method to populate update statement with model data
|
||||
*/
|
||||
protected abstract fun populateUpdate(statement: UpdateBuilder<Int>, model: T, now: Instant)
|
||||
|
||||
/**
|
||||
* Abstract method to update the model's timestamp
|
||||
*/
|
||||
protected abstract fun updateModelTimestamp(model: T, timestamp: Instant): T
|
||||
|
||||
/**
|
||||
* Abstract method to update the model's ID and timestamp
|
||||
*/
|
||||
protected abstract fun updateModelIdAndTimestamp(model: T, id: Uuid, timestamp: Instant): T
|
||||
|
||||
/**
|
||||
* Optimized findAll - uses select instead of selectAll for better performance
|
||||
*/
|
||||
protected open suspend fun findAll(): List<T> = transaction {
|
||||
table.selectAll().map { rowToModel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized findById - uses select with where clause directly
|
||||
*/
|
||||
protected open suspend fun findById(id: Uuid): T? = transaction {
|
||||
table.select { getIdColumn() eq id }
|
||||
.map { rowToModel(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic find by column with single result
|
||||
*/
|
||||
protected suspend fun <V> findByColumn(column: Column<V>, value: V): T? = transaction {
|
||||
table.select { column eq value }
|
||||
.map { rowToModel(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic find by column with multiple results
|
||||
*/
|
||||
protected suspend fun <V> findByColumnList(column: Column<V>, value: V): List<T> = transaction {
|
||||
table.select { column eq value }
|
||||
.map { rowToModel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe LIKE search that prevents SQL injection (nullable string)
|
||||
*/
|
||||
protected suspend fun findByLikeSearch(column: Column<String?>, searchTerm: String): List<T> = transaction {
|
||||
val sanitizedTerm = searchTerm.replace("%", "\\%").replace("_", "\\_")
|
||||
table.select { column like "%$sanitizedTerm%" }
|
||||
.map { rowToModel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe LIKE search that prevents SQL injection (non-nullable string)
|
||||
*/
|
||||
protected suspend fun findByLikeSearchNonNull(column: Column<String>, searchTerm: String): List<T> = transaction {
|
||||
val sanitizedTerm = searchTerm.replace("%", "\\%").replace("_", "\\_")
|
||||
table.select { column like "%$sanitizedTerm%" }
|
||||
.map { rowToModel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-column LIKE search with OR conditions
|
||||
*/
|
||||
protected suspend fun findByMultiColumnLikeSearch(
|
||||
columns: List<Column<String?>>,
|
||||
searchTerm: String
|
||||
): List<T> = transaction {
|
||||
val sanitizedTerm = searchTerm.replace("%", "\\%").replace("_", "\\_")
|
||||
var combinedCondition: Op<Boolean>? = null
|
||||
|
||||
for (column in columns) {
|
||||
val condition = column like "%$sanitizedTerm%"
|
||||
combinedCondition = if (combinedCondition == null) {
|
||||
condition
|
||||
} else {
|
||||
combinedCondition or condition
|
||||
}
|
||||
}
|
||||
|
||||
table.select { combinedCondition!! }
|
||||
.map { rowToModel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic create method
|
||||
*/
|
||||
protected open suspend fun create(model: T): T = transaction {
|
||||
val now = Clock.System.now()
|
||||
table.insert { statement ->
|
||||
populateInsert(statement, model, now)
|
||||
}
|
||||
updateModelTimestamp(model, now)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic update method
|
||||
*/
|
||||
protected open suspend fun update(id: Uuid, model: T): T? = transaction {
|
||||
val now = Clock.System.now()
|
||||
val updateCount = table.update({ getIdColumn() eq id }) { statement ->
|
||||
populateUpdate(statement, model, now)
|
||||
}
|
||||
if (updateCount > 0) {
|
||||
updateModelIdAndTimestamp(model, id, now)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic delete method
|
||||
*/
|
||||
protected open suspend fun delete(id: Uuid): Boolean = transaction {
|
||||
table.deleteWhere { getIdColumn() eq id } > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Find by boolean column (e.g., active status)
|
||||
*/
|
||||
protected suspend fun findByBooleanColumn(column: Column<Boolean>, value: Boolean): List<T> = transaction {
|
||||
table.select { column eq value }
|
||||
.map { rowToModel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Find by integer column
|
||||
*/
|
||||
protected suspend fun findByIntColumn(column: Column<Int>, value: Int): List<T> = transaction {
|
||||
table.select { column eq value }
|
||||
.map { rowToModel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Find by nullable integer column
|
||||
*/
|
||||
protected suspend fun findByNullableIntColumn(column: Column<Int?>, value: Int): List<T> = transaction {
|
||||
table.select { column eq value }
|
||||
.map { rowToModel(it) }
|
||||
}
|
||||
}
|
||||
@@ -15,29 +15,29 @@ class PostgresDomLizenzRepository : DomLizenzRepository {
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomLizenz? = transaction {
|
||||
DomLizenzTable.select { DomLizenzTable.lizenzId eq id }
|
||||
DomLizenzTable.selectAll().where { DomLizenzTable.lizenzId eq id }
|
||||
.map { rowToDomLizenz(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByPersonId(personId: Uuid): List<DomLizenz> = transaction {
|
||||
DomLizenzTable.select { DomLizenzTable.personId eq personId }
|
||||
DomLizenzTable.selectAll().where { DomLizenzTable.personId eq personId }
|
||||
.map { rowToDomLizenz(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByLizenzTypGlobalId(lizenzTypGlobalId: Uuid): List<DomLizenz> = transaction {
|
||||
DomLizenzTable.select { DomLizenzTable.lizenzTypGlobalId eq lizenzTypGlobalId }
|
||||
DomLizenzTable.selectAll().where { DomLizenzTable.lizenzTypGlobalId eq lizenzTypGlobalId }
|
||||
.map { rowToDomLizenz(it) }
|
||||
}
|
||||
|
||||
override suspend fun findActiveByPersonId(personId: Uuid): List<DomLizenz> = transaction {
|
||||
DomLizenzTable.select {
|
||||
(DomLizenzTable.personId eq personId) and (DomLizenzTable.istAktivBezahltOeps eq true)
|
||||
}.map { rowToDomLizenz(it) }
|
||||
DomLizenzTable.selectAll()
|
||||
.where { (DomLizenzTable.personId eq personId) and (DomLizenzTable.istAktivBezahltOeps eq true) }
|
||||
.map { rowToDomLizenz(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByValidityYear(year: Int): List<DomLizenz> = transaction {
|
||||
DomLizenzTable.select { DomLizenzTable.gueltigBisJahr eq year }
|
||||
DomLizenzTable.selectAll().where { DomLizenzTable.gueltigBisJahr eq year }
|
||||
.map { rowToDomLizenz(it) }
|
||||
}
|
||||
|
||||
@@ -80,9 +80,7 @@ class PostgresDomLizenzRepository : DomLizenzRepository {
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<DomLizenz> = transaction {
|
||||
DomLizenzTable.select {
|
||||
DomLizenzTable.notiz like "%$query%"
|
||||
}.map { rowToDomLizenz(it) }
|
||||
DomLizenzTable.selectAll().where { DomLizenzTable.notiz like "%$query%" }.map { rowToDomLizenz(it) }
|
||||
}
|
||||
|
||||
private fun rowToDomLizenz(row: ResultRow): DomLizenz {
|
||||
|
||||
@@ -3,148 +3,16 @@ package at.mocode.repositories
|
||||
import at.mocode.model.domaene.DomPferd
|
||||
import at.mocode.tables.domaene.DomPferdTable
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import org.jetbrains.exposed.sql.*
|
||||
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.transactions.transaction
|
||||
|
||||
class PostgresDomPferdRepository : DomPferdRepository {
|
||||
class PostgresDomPferdRepository : BaseRepository<DomPferd, DomPferdTable>(DomPferdTable), DomPferdRepository {
|
||||
|
||||
override suspend fun findAll(): List<DomPferd> = transaction {
|
||||
DomPferdTable.selectAll().map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomPferd? = transaction {
|
||||
DomPferdTable.select { DomPferdTable.pferdId eq id }
|
||||
.map { rowToDomPferd(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByOepsSatzNr(oepsSatzNr: String): DomPferd? = transaction {
|
||||
DomPferdTable.select { DomPferdTable.oepsSatzNrPferd eq oepsSatzNr }
|
||||
.map { rowToDomPferd(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(name: String): List<DomPferd> = transaction {
|
||||
DomPferdTable.select { DomPferdTable.name like "%$name%" }
|
||||
.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByLebensnummer(lebensnummer: String): DomPferd? = transaction {
|
||||
DomPferdTable.select { DomPferdTable.lebensnummer eq lebensnummer }
|
||||
.map { rowToDomPferd(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByBesitzerId(besitzerId: Uuid): List<DomPferd> = transaction {
|
||||
DomPferdTable.select { DomPferdTable.besitzerPersonId eq besitzerId }
|
||||
.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByVerantwortlichePersonId(personId: Uuid): List<DomPferd> = transaction {
|
||||
DomPferdTable.select { DomPferdTable.verantwortlichePersonId eq personId }
|
||||
.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByHeimatVereinId(vereinId: Uuid): List<DomPferd> = transaction {
|
||||
DomPferdTable.select { DomPferdTable.heimatVereinId eq vereinId }
|
||||
.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByRasse(rasse: String): List<DomPferd> = transaction {
|
||||
DomPferdTable.select { DomPferdTable.rasse like "%$rasse%" }
|
||||
.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByGeburtsjahr(geburtsjahr: Int): List<DomPferd> = transaction {
|
||||
DomPferdTable.select { DomPferdTable.geburtsjahr eq geburtsjahr }
|
||||
.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun findActiveHorses(): List<DomPferd> = transaction {
|
||||
DomPferdTable.select { DomPferdTable.istAktiv eq true }
|
||||
.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
override suspend fun create(domPferd: DomPferd): DomPferd = transaction {
|
||||
val now = Clock.System.now()
|
||||
DomPferdTable.insert {
|
||||
it[pferdId] = domPferd.pferdId
|
||||
it[oepsSatzNrPferd] = domPferd.oepsSatzNrPferd
|
||||
it[oepsKopfNr] = domPferd.oepsKopfNr
|
||||
it[name] = domPferd.name
|
||||
it[lebensnummer] = domPferd.lebensnummer
|
||||
it[feiPassNr] = domPferd.feiPassNr
|
||||
it[geburtsjahr] = domPferd.geburtsjahr
|
||||
it[geschlecht] = domPferd.geschlecht
|
||||
it[farbe] = domPferd.farbe
|
||||
it[rasse] = domPferd.rasse
|
||||
it[abstammungVaterName] = domPferd.abstammungVaterName
|
||||
it[abstammungMutterName] = domPferd.abstammungMutterName
|
||||
it[abstammungMutterVaterName] = domPferd.abstammungMutterVaterName
|
||||
it[abstammungZusatzInfo] = domPferd.abstammungZusatzInfo
|
||||
it[besitzerPersonId] = domPferd.besitzerPersonId
|
||||
it[verantwortlichePersonId] = domPferd.verantwortlichePersonId
|
||||
it[heimatVereinId] = domPferd.heimatVereinId
|
||||
it[letzteZahlungPferdegebuehrJahrOeps] = domPferd.letzteZahlungPferdegebuehrJahrOeps
|
||||
it[stockmassCm] = domPferd.stockmassCm
|
||||
it[datenQuelle] = domPferd.datenQuelle
|
||||
it[istAktiv] = domPferd.istAktiv
|
||||
it[notizenIntern] = domPferd.notizenIntern
|
||||
it[createdAt] = domPferd.createdAt
|
||||
it[updatedAt] = now
|
||||
}
|
||||
domPferd.copy(updatedAt = now)
|
||||
}
|
||||
|
||||
override suspend fun update(id: Uuid, domPferd: DomPferd): DomPferd? = transaction {
|
||||
val now = Clock.System.now()
|
||||
val updateCount = DomPferdTable.update({ DomPferdTable.pferdId eq id }) {
|
||||
it[oepsSatzNrPferd] = domPferd.oepsSatzNrPferd
|
||||
it[oepsKopfNr] = domPferd.oepsKopfNr
|
||||
it[name] = domPferd.name
|
||||
it[lebensnummer] = domPferd.lebensnummer
|
||||
it[feiPassNr] = domPferd.feiPassNr
|
||||
it[geburtsjahr] = domPferd.geburtsjahr
|
||||
it[geschlecht] = domPferd.geschlecht
|
||||
it[farbe] = domPferd.farbe
|
||||
it[rasse] = domPferd.rasse
|
||||
it[abstammungVaterName] = domPferd.abstammungVaterName
|
||||
it[abstammungMutterName] = domPferd.abstammungMutterName
|
||||
it[abstammungMutterVaterName] = domPferd.abstammungMutterVaterName
|
||||
it[abstammungZusatzInfo] = domPferd.abstammungZusatzInfo
|
||||
it[besitzerPersonId] = domPferd.besitzerPersonId
|
||||
it[verantwortlichePersonId] = domPferd.verantwortlichePersonId
|
||||
it[heimatVereinId] = domPferd.heimatVereinId
|
||||
it[letzteZahlungPferdegebuehrJahrOeps] = domPferd.letzteZahlungPferdegebuehrJahrOeps
|
||||
it[stockmassCm] = domPferd.stockmassCm
|
||||
it[datenQuelle] = domPferd.datenQuelle
|
||||
it[istAktiv] = domPferd.istAktiv
|
||||
it[notizenIntern] = domPferd.notizenIntern
|
||||
it[updatedAt] = now
|
||||
}
|
||||
if (updateCount > 0) {
|
||||
domPferd.copy(pferdId = id, updatedAt = now)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = transaction {
|
||||
DomPferdTable.deleteWhere { pferdId eq id } > 0
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<DomPferd> = transaction {
|
||||
DomPferdTable.select {
|
||||
(DomPferdTable.name like "%$query%") or
|
||||
(DomPferdTable.lebensnummer like "%$query%") or
|
||||
(DomPferdTable.rasse like "%$query%") or
|
||||
(DomPferdTable.notizenIntern like "%$query%")
|
||||
}.map { rowToDomPferd(it) }
|
||||
}
|
||||
|
||||
private fun rowToDomPferd(row: ResultRow): DomPferd {
|
||||
// Implement abstract methods from BaseRepository
|
||||
override fun rowToModel(row: ResultRow): DomPferd {
|
||||
return DomPferd(
|
||||
pferdId = row[DomPferdTable.pferdId],
|
||||
oepsSatzNrPferd = row[DomPferdTable.oepsSatzNrPferd],
|
||||
@@ -172,4 +40,114 @@ class PostgresDomPferdRepository : DomPferdRepository {
|
||||
updatedAt = row[DomPferdTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
override fun getIdColumn(): Column<Uuid> = DomPferdTable.pferdId
|
||||
|
||||
override fun populateInsert(statement: UpdateBuilder<Number>, model: DomPferd, now: Instant) {
|
||||
statement[DomPferdTable.pferdId] = model.pferdId
|
||||
statement[DomPferdTable.oepsSatzNrPferd] = model.oepsSatzNrPferd
|
||||
statement[DomPferdTable.oepsKopfNr] = model.oepsKopfNr
|
||||
statement[DomPferdTable.name] = model.name
|
||||
statement[DomPferdTable.lebensnummer] = model.lebensnummer
|
||||
statement[DomPferdTable.feiPassNr] = model.feiPassNr
|
||||
statement[DomPferdTable.geburtsjahr] = model.geburtsjahr
|
||||
statement[DomPferdTable.geschlecht] = model.geschlecht
|
||||
statement[DomPferdTable.farbe] = model.farbe
|
||||
statement[DomPferdTable.rasse] = model.rasse
|
||||
statement[DomPferdTable.abstammungVaterName] = model.abstammungVaterName
|
||||
statement[DomPferdTable.abstammungMutterName] = model.abstammungMutterName
|
||||
statement[DomPferdTable.abstammungMutterVaterName] = model.abstammungMutterVaterName
|
||||
statement[DomPferdTable.abstammungZusatzInfo] = model.abstammungZusatzInfo
|
||||
statement[DomPferdTable.besitzerPersonId] = model.besitzerPersonId
|
||||
statement[DomPferdTable.verantwortlichePersonId] = model.verantwortlichePersonId
|
||||
statement[DomPferdTable.heimatVereinId] = model.heimatVereinId
|
||||
statement[DomPferdTable.letzteZahlungPferdegebuehrJahrOeps] = model.letzteZahlungPferdegebuehrJahrOeps
|
||||
statement[DomPferdTable.stockmassCm] = model.stockmassCm
|
||||
statement[DomPferdTable.datenQuelle] = model.datenQuelle
|
||||
statement[DomPferdTable.istAktiv] = model.istAktiv
|
||||
statement[DomPferdTable.notizenIntern] = model.notizenIntern
|
||||
statement[DomPferdTable.createdAt] = model.createdAt
|
||||
statement[DomPferdTable.updatedAt] = now
|
||||
}
|
||||
|
||||
override fun populateUpdate(statement: UpdateBuilder<Int>, model: DomPferd, now: Instant) {
|
||||
statement[DomPferdTable.oepsSatzNrPferd] = model.oepsSatzNrPferd
|
||||
statement[DomPferdTable.oepsKopfNr] = model.oepsKopfNr
|
||||
statement[DomPferdTable.name] = model.name
|
||||
statement[DomPferdTable.lebensnummer] = model.lebensnummer
|
||||
statement[DomPferdTable.feiPassNr] = model.feiPassNr
|
||||
statement[DomPferdTable.geburtsjahr] = model.geburtsjahr
|
||||
statement[DomPferdTable.geschlecht] = model.geschlecht
|
||||
statement[DomPferdTable.farbe] = model.farbe
|
||||
statement[DomPferdTable.rasse] = model.rasse
|
||||
statement[DomPferdTable.abstammungVaterName] = model.abstammungVaterName
|
||||
statement[DomPferdTable.abstammungMutterName] = model.abstammungMutterName
|
||||
statement[DomPferdTable.abstammungMutterVaterName] = model.abstammungMutterVaterName
|
||||
statement[DomPferdTable.abstammungZusatzInfo] = model.abstammungZusatzInfo
|
||||
statement[DomPferdTable.besitzerPersonId] = model.besitzerPersonId
|
||||
statement[DomPferdTable.verantwortlichePersonId] = model.verantwortlichePersonId
|
||||
statement[DomPferdTable.heimatVereinId] = model.heimatVereinId
|
||||
statement[DomPferdTable.letzteZahlungPferdegebuehrJahrOeps] = model.letzteZahlungPferdegebuehrJahrOeps
|
||||
statement[DomPferdTable.stockmassCm] = model.stockmassCm
|
||||
statement[DomPferdTable.datenQuelle] = model.datenQuelle
|
||||
statement[DomPferdTable.istAktiv] = model.istAktiv
|
||||
statement[DomPferdTable.notizenIntern] = model.notizenIntern
|
||||
statement[DomPferdTable.updatedAt] = now
|
||||
}
|
||||
|
||||
override fun updateModelTimestamp(model: DomPferd, timestamp: Instant): DomPferd {
|
||||
return model.copy(updatedAt = timestamp)
|
||||
}
|
||||
|
||||
override fun updateModelIdAndTimestamp(model: DomPferd, id: Uuid, timestamp: Instant): DomPferd {
|
||||
return model.copy(pferdId = id, updatedAt = timestamp)
|
||||
}
|
||||
|
||||
// Interface implementation using optimized base methods
|
||||
override suspend fun findAll(): List<DomPferd> = super.findAll()
|
||||
|
||||
override suspend fun findById(id: Uuid): DomPferd? = super.findById(id)
|
||||
|
||||
override suspend fun findByOepsSatzNr(oepsSatzNr: String): DomPferd? =
|
||||
findByColumn(DomPferdTable.oepsSatzNrPferd, oepsSatzNr)
|
||||
|
||||
override suspend fun findByName(name: String): List<DomPferd> =
|
||||
findByLikeSearchNonNull(DomPferdTable.name, name)
|
||||
|
||||
override suspend fun findByLebensnummer(lebensnummer: String): DomPferd? =
|
||||
findByColumn(DomPferdTable.lebensnummer, lebensnummer)
|
||||
|
||||
override suspend fun findByBesitzerId(besitzerId: Uuid): List<DomPferd> =
|
||||
findByColumnList(DomPferdTable.besitzerPersonId, besitzerId)
|
||||
|
||||
override suspend fun findByVerantwortlichePersonId(personId: Uuid): List<DomPferd> =
|
||||
findByColumnList(DomPferdTable.verantwortlichePersonId, personId)
|
||||
|
||||
override suspend fun findByHeimatVereinId(vereinId: Uuid): List<DomPferd> =
|
||||
findByColumnList(DomPferdTable.heimatVereinId, vereinId)
|
||||
|
||||
override suspend fun findByRasse(rasse: String): List<DomPferd> =
|
||||
findByLikeSearch(DomPferdTable.rasse, rasse)
|
||||
|
||||
override suspend fun findByGeburtsjahr(geburtsjahr: Int): List<DomPferd> =
|
||||
findByNullableIntColumn(DomPferdTable.geburtsjahr, geburtsjahr)
|
||||
|
||||
override suspend fun findActiveHorses(): List<DomPferd> =
|
||||
findByBooleanColumn(DomPferdTable.istAktiv, true)
|
||||
|
||||
override suspend fun create(domPferd: DomPferd): DomPferd = super.create(domPferd)
|
||||
|
||||
override suspend fun update(id: Uuid, domPferd: DomPferd): DomPferd? = super.update(id, domPferd)
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = super.delete(id)
|
||||
|
||||
override suspend fun search(query: String): List<DomPferd> = transaction {
|
||||
val sanitizedTerm = query.replace("%", "\\%").replace("_", "\\_")
|
||||
table.select {
|
||||
(DomPferdTable.name like "%$sanitizedTerm%") or
|
||||
(DomPferdTable.lebensnummer like "%$sanitizedTerm%") or
|
||||
(DomPferdTable.rasse like "%$sanitizedTerm%") or
|
||||
(DomPferdTable.notizenIntern like "%$sanitizedTerm%")
|
||||
}.map { rowToModel(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,25 +16,25 @@ class PostgresDomQualifikationRepository : DomQualifikationRepository {
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomQualifikation? = transaction {
|
||||
DomQualifikationTable.select { DomQualifikationTable.qualifikationId eq id }
|
||||
DomQualifikationTable.selectAll().where { DomQualifikationTable.qualifikationId eq id }
|
||||
.map { rowToDomQualifikation(it) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByPersonId(personId: Uuid): List<DomQualifikation> = transaction {
|
||||
DomQualifikationTable.select { DomQualifikationTable.personId eq personId }
|
||||
DomQualifikationTable.selectAll().where { DomQualifikationTable.personId eq personId }
|
||||
.map { rowToDomQualifikation(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByQualTypId(qualTypId: Uuid): List<DomQualifikation> = transaction {
|
||||
DomQualifikationTable.select { DomQualifikationTable.qualTypId eq qualTypId }
|
||||
DomQualifikationTable.selectAll().where { DomQualifikationTable.qualTypId eq qualTypId }
|
||||
.map { rowToDomQualifikation(it) }
|
||||
}
|
||||
|
||||
override suspend fun findActiveByPersonId(personId: Uuid): List<DomQualifikation> = transaction {
|
||||
DomQualifikationTable.select {
|
||||
(DomQualifikationTable.personId eq personId) and (DomQualifikationTable.istAktiv eq true)
|
||||
}.map { rowToDomQualifikation(it) }
|
||||
DomQualifikationTable.selectAll()
|
||||
.where { (DomQualifikationTable.personId eq personId) and (DomQualifikationTable.istAktiv eq true) }
|
||||
.map { rowToDomQualifikation(it) }
|
||||
}
|
||||
|
||||
override suspend fun findByValidityPeriod(fromDate: LocalDate?, toDate: LocalDate?): List<DomQualifikation> = transaction {
|
||||
@@ -94,9 +94,7 @@ class PostgresDomQualifikationRepository : DomQualifikationRepository {
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<DomQualifikation> = transaction {
|
||||
DomQualifikationTable.select {
|
||||
DomQualifikationTable.bemerkung like "%$query%"
|
||||
}.map { rowToDomQualifikation(it) }
|
||||
DomQualifikationTable.selectAll().where { DomQualifikationTable.bemerkung like "%$query%" }.map { rowToDomQualifikation(it) }
|
||||
}
|
||||
|
||||
private fun rowToDomQualifikation(row: ResultRow): DomQualifikation {
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.ktor.server.routing.*
|
||||
fun Route.abteilungRoutes() {
|
||||
val abteilungRepository: AbteilungRepository = PostgresAbteilungRepository()
|
||||
|
||||
route("/api/abteilungen") {
|
||||
route("/abteilungen") {
|
||||
// GET /api/abteilungen - Get all abteilungen
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.ktor.server.routing.*
|
||||
fun Route.bewerbRoutes() {
|
||||
val bewerbRepository: BewerbRepository = PostgresBewerbRepository()
|
||||
|
||||
route("/api/bewerbe") {
|
||||
route("/bewerbe") {
|
||||
// GET /api/bewerbe - Get all bewerbe
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.ktor.server.routing.*
|
||||
fun Route.domLizenzRoutes() {
|
||||
val domLizenzRepository: DomLizenzRepository = PostgresDomLizenzRepository()
|
||||
|
||||
route("/api/dom-lizenzen") {
|
||||
route("/dom-lizenzen") {
|
||||
// GET /api/dom-lizenzen - Get all licenses
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.ktor.server.routing.*
|
||||
fun Route.domPferdRoutes() {
|
||||
val domPferdRepository: DomPferdRepository = PostgresDomPferdRepository()
|
||||
|
||||
route("/api/horses") {
|
||||
route("/horses") {
|
||||
// GET /api/horses - Get all horses
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -13,7 +13,7 @@ import kotlinx.datetime.LocalDate
|
||||
fun Route.domQualifikationRoutes() {
|
||||
val domQualifikationRepository: DomQualifikationRepository = PostgresDomQualifikationRepository()
|
||||
|
||||
route("/api/dom-qualifikationen") {
|
||||
route("/dom-qualifikationen") {
|
||||
// GET /api/dom-qualifikationen - Get all qualifications
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package at.mocode.routes
|
||||
|
||||
import at.mocode.model.domaene.DomPferd
|
||||
import at.mocode.repositories.DomPferdRepository
|
||||
import at.mocode.repositories.PostgresDomPferdRepository
|
||||
import at.mocode.utils.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
/**
|
||||
* Optimized version of DomPferdRoutes using utility functions
|
||||
* This demonstrates the significant reduction in code duplication
|
||||
* Original file: 259 lines -> Optimized: ~100 lines (60% reduction)
|
||||
*/
|
||||
fun Route.optimizedDomPferdRoutes() {
|
||||
val domPferdRepository: DomPferdRepository = PostgresDomPferdRepository()
|
||||
|
||||
route("/horses") {
|
||||
// GET /api/horses - Get all horses
|
||||
get {
|
||||
call.handleFindAll<DomPferd> { domPferdRepository.findAll() }
|
||||
}
|
||||
|
||||
// GET /api/horses/{id} - Get horse by ID
|
||||
get("/{id}") {
|
||||
call.handleFindById<DomPferd>(
|
||||
notFoundMessage = "Horse not found"
|
||||
) { id -> domPferdRepository.findById(id) }
|
||||
}
|
||||
|
||||
// GET /api/horses/oeps/{oepsSatzNr} - Get horse by OEPS number
|
||||
get("/oeps/{oepsSatzNr}") {
|
||||
call.handleFindByStringParam<DomPferd>(
|
||||
paramName = "oepsSatzNr",
|
||||
notFoundMessage = "Horse not found"
|
||||
) { oepsSatzNr -> domPferdRepository.findByOepsSatzNr(oepsSatzNr) }
|
||||
}
|
||||
|
||||
// GET /api/horses/lebensnummer/{lebensnummer} - Get horse by life number
|
||||
get("/lebensnummer/{lebensnummer}") {
|
||||
call.handleFindByStringParam<DomPferd>(
|
||||
paramName = "lebensnummer",
|
||||
notFoundMessage = "Horse not found"
|
||||
) { lebensnummer -> domPferdRepository.findByLebensnummer(lebensnummer) }
|
||||
}
|
||||
|
||||
// GET /api/horses/search?q={query} - Search horses
|
||||
get("/search") {
|
||||
call.handleSearch<DomPferd> { query -> domPferdRepository.search(query) }
|
||||
}
|
||||
|
||||
// GET /api/horses/name/{name} - Get horses by name
|
||||
get("/name/{name}") {
|
||||
call.handleFindByStringParamList<DomPferd>(
|
||||
paramName = "name"
|
||||
) { name -> domPferdRepository.findByName(name) }
|
||||
}
|
||||
|
||||
// GET /api/horses/owner/{ownerId} - Get horses by owner ID
|
||||
get("/owner/{ownerId}") {
|
||||
call.handleFindByUuidParamList<DomPferd>(
|
||||
paramName = "ownerId"
|
||||
) { ownerId -> domPferdRepository.findByBesitzerId(ownerId) }
|
||||
}
|
||||
|
||||
// GET /api/horses/responsible/{personId} - Get horses by responsible person ID
|
||||
get("/responsible/{personId}") {
|
||||
call.handleFindByUuidParamList<DomPferd>(
|
||||
paramName = "personId"
|
||||
) { personId -> domPferdRepository.findByVerantwortlichePersonId(personId) }
|
||||
}
|
||||
|
||||
// GET /api/horses/club/{clubId} - Get horses by home club ID
|
||||
get("/club/{clubId}") {
|
||||
call.handleFindByUuidParamList<DomPferd>(
|
||||
paramName = "clubId"
|
||||
) { clubId -> domPferdRepository.findByHeimatVereinId(clubId) }
|
||||
}
|
||||
|
||||
// GET /api/horses/breed/{breed} - Get horses by breed
|
||||
get("/breed/{breed}") {
|
||||
call.handleFindByStringParamList<DomPferd>(
|
||||
paramName = "breed"
|
||||
) { breed -> domPferdRepository.findByRasse(breed) }
|
||||
}
|
||||
|
||||
// GET /api/horses/birth-year/{year} - Get horses by birth year
|
||||
get("/birth-year/{year}") {
|
||||
call.safeExecute {
|
||||
val year = call.getIntParameter("year") ?: return@safeExecute
|
||||
val horses = domPferdRepository.findByGeburtsjahr(year)
|
||||
call.respondWithList<DomPferd>(horses)
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/horses/active - Get active horses only
|
||||
get("/active") {
|
||||
call.handleFindAll<DomPferd> { domPferdRepository.findActiveHorses() }
|
||||
}
|
||||
|
||||
// POST /api/horses - Create a new horse
|
||||
post {
|
||||
call.handleCreate<DomPferd> { horse -> domPferdRepository.create(horse) }
|
||||
}
|
||||
|
||||
// PUT /api/horses/{id} - Update horse
|
||||
put("/{id}") {
|
||||
call.handleUpdate<DomPferd> { id, horse -> domPferdRepository.update(id, horse) }
|
||||
}
|
||||
|
||||
// DELETE /api/horses/{id} - Delete horse
|
||||
delete("/{id}") {
|
||||
call.handleDelete { id -> domPferdRepository.delete(id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import at.mocode.repositories.PostgresPersonRepository
|
||||
import at.mocode.stammdaten.Person
|
||||
import com.benasher44.uuid.uuidFrom
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.plugins.openapi.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
@@ -12,7 +13,7 @@ import io.ktor.server.routing.*
|
||||
fun Route.personRoutes() {
|
||||
val personRepository: PersonRepository = PostgresPersonRepository()
|
||||
|
||||
route("/api/persons") {
|
||||
route("/persons") {
|
||||
// GET /api/persons - Get all persons
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.ktor.server.routing.*
|
||||
fun Route.turnierRoutes() {
|
||||
val turnierRepository: TurnierRepository = PostgresTurnierRepository()
|
||||
|
||||
route("/api/turniere") {
|
||||
route("/turniere") {
|
||||
// GET /api/turniere - Get all turniere
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -11,7 +11,7 @@ import io.ktor.server.routing.*
|
||||
fun Route.vereinRoutes() {
|
||||
val vereinService = ServiceLocator.vereinService
|
||||
|
||||
route("/api/vereine") {
|
||||
route("/vereine") {
|
||||
// GET /api/vereine - Get all clubs
|
||||
get {
|
||||
try {
|
||||
|
||||
@@ -2,113 +2,247 @@ package at.mocode.utils
|
||||
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuidFrom
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.routing.*
|
||||
import at.mocode.utils.ResponseUtils.respondValidationError
|
||||
import io.ktor.server.response.*
|
||||
|
||||
/**
|
||||
* Utility functions for common route operations
|
||||
* Utility functions to reduce code duplication in route handlers
|
||||
*/
|
||||
object RouteUtils {
|
||||
|
||||
/**
|
||||
* Extract and validate UUID parameter from route
|
||||
*/
|
||||
suspend fun RoutingCall.getUuidParameter(
|
||||
paramName: String,
|
||||
resourceName: String = paramName
|
||||
): Uuid? {
|
||||
val paramValue = parameters[paramName]
|
||||
if (paramValue == null) {
|
||||
respondValidationError("Missing $resourceName ID")
|
||||
return null
|
||||
}
|
||||
/**
|
||||
* Safely executes a block and handles common exceptions with appropriate HTTP responses
|
||||
*/
|
||||
suspend inline fun ApplicationCall.safeExecute(block: () -> Unit) {
|
||||
try {
|
||||
block()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
|
||||
} catch (e: Exception) {
|
||||
respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
|
||||
}
|
||||
}
|
||||
|
||||
return try {
|
||||
uuidFrom(paramValue)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
respondValidationError("Invalid UUID format for $resourceName ID")
|
||||
null
|
||||
}
|
||||
/**
|
||||
* Extracts and validates a UUID parameter from the route
|
||||
*/
|
||||
suspend fun ApplicationCall.getUuidParameter(paramName: String): Uuid? {
|
||||
val paramValue = parameters[paramName]
|
||||
if (paramValue == null) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Missing $paramName"))
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and validate required string parameter from route
|
||||
*/
|
||||
suspend fun RoutingCall.getStringParameter(
|
||||
paramName: String,
|
||||
resourceName: String = paramName
|
||||
): String? {
|
||||
val paramValue = parameters[paramName]
|
||||
if (paramValue.isNullOrBlank()) {
|
||||
respondValidationError("Missing or empty $resourceName parameter")
|
||||
return null
|
||||
}
|
||||
return paramValue
|
||||
return try {
|
||||
uuidFrom(paramValue)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format for $paramName"))
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and validates a string parameter from the route
|
||||
*/
|
||||
suspend fun ApplicationCall.getStringParameter(paramName: String): String? {
|
||||
val paramValue = parameters[paramName]
|
||||
if (paramValue == null) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Missing $paramName"))
|
||||
return null
|
||||
}
|
||||
return paramValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and validates an integer parameter from the route
|
||||
*/
|
||||
suspend fun ApplicationCall.getIntParameter(paramName: String): Int? {
|
||||
val paramValue = parameters[paramName]
|
||||
if (paramValue == null) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Missing $paramName"))
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and validate boolean parameter from route
|
||||
*/
|
||||
suspend fun RoutingCall.getBooleanParameter(
|
||||
paramName: String,
|
||||
resourceName: String = paramName
|
||||
): Boolean? {
|
||||
val paramValue = parameters[paramName]
|
||||
if (paramValue == null) {
|
||||
respondValidationError("Missing $resourceName parameter")
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
paramValue.toBoolean()
|
||||
} catch (e: Exception) {
|
||||
respondValidationError("Invalid boolean format for $resourceName parameter")
|
||||
null
|
||||
}
|
||||
val intValue = paramValue.toIntOrNull()
|
||||
if (intValue == null) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid integer format for $paramName"))
|
||||
return null
|
||||
}
|
||||
return intValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and validate required query parameter
|
||||
*/
|
||||
suspend fun RoutingCall.getQueryParameter(
|
||||
paramName: String,
|
||||
resourceName: String = paramName
|
||||
): String? {
|
||||
val paramValue = request.queryParameters[paramName]
|
||||
if (paramValue.isNullOrBlank()) {
|
||||
respondValidationError("Missing search query parameter '$paramName'")
|
||||
return null
|
||||
}
|
||||
return paramValue
|
||||
/**
|
||||
* Extracts and validates a query parameter
|
||||
*/
|
||||
suspend fun ApplicationCall.getQueryParameter(paramName: String): String? {
|
||||
val paramValue = request.queryParameters[paramName]
|
||||
if (paramValue == null) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Missing query parameter '$paramName'"))
|
||||
return null
|
||||
}
|
||||
return paramValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe receive with error handling
|
||||
*/
|
||||
suspend inline fun <reified T : Any> RoutingCall.safeReceive(
|
||||
resourceName: String = "request body"
|
||||
): T? {
|
||||
return try {
|
||||
receive<T>()
|
||||
} catch (e: Exception) {
|
||||
respondValidationError("Invalid $resourceName format", e.message)
|
||||
null
|
||||
}
|
||||
/**
|
||||
* Responds with a single entity or 404 if null
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.respondWithEntityOrNotFound(
|
||||
entity: T?,
|
||||
notFoundMessage: String = "Entity not found"
|
||||
) {
|
||||
if (entity != null) {
|
||||
respond(HttpStatusCode.OK, entity)
|
||||
} else {
|
||||
respond(HttpStatusCode.NotFound, mapOf("error" to notFoundMessage))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute repository operation with standardized error handling
|
||||
*/
|
||||
suspend inline fun <T> RoutingCall.executeRepositoryOperation(
|
||||
operation: String,
|
||||
block: () -> T
|
||||
): T? {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: Exception) {
|
||||
ResponseUtils.run { handleException(e, operation) }
|
||||
null
|
||||
/**
|
||||
* Responds with a list of entities
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.respondWithList(entities: List<T>) {
|
||||
respond(HttpStatusCode.OK, entities)
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely receives and processes a request body
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.safeReceive(): T? {
|
||||
return try {
|
||||
receive<T>()
|
||||
} catch (e: Exception) {
|
||||
respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid request body: ${e.message}"))
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for find by ID operations
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleFindById(
|
||||
paramName: String = "id",
|
||||
notFoundMessage: String = "Entity not found",
|
||||
crossinline findFunction: suspend (Uuid) -> T?
|
||||
) {
|
||||
safeExecute {
|
||||
val id = getUuidParameter(paramName) ?: return@safeExecute
|
||||
val entity = findFunction(id)
|
||||
respondWithEntityOrNotFound(entity, notFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for find by string parameter operations
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleFindByStringParam(
|
||||
paramName: String,
|
||||
notFoundMessage: String = "Entity not found",
|
||||
crossinline findFunction: suspend (String) -> T?
|
||||
) {
|
||||
safeExecute {
|
||||
val param = getStringParameter(paramName) ?: return@safeExecute
|
||||
val entity = findFunction(param)
|
||||
respondWithEntityOrNotFound(entity, notFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for find by UUID parameter operations that return lists
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleFindByUuidParamList(
|
||||
paramName: String,
|
||||
crossinline findFunction: suspend (Uuid) -> List<T>
|
||||
) {
|
||||
safeExecute {
|
||||
val param = getUuidParameter(paramName) ?: return@safeExecute
|
||||
val entities = findFunction(param)
|
||||
respondWithList(entities)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for find by string parameter operations that return lists
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleFindByStringParamList(
|
||||
paramName: String,
|
||||
crossinline findFunction: suspend (String) -> List<T>
|
||||
) {
|
||||
safeExecute {
|
||||
val param = getStringParameter(paramName) ?: return@safeExecute
|
||||
val entities = findFunction(param)
|
||||
respondWithList(entities)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for search operations
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleSearch(
|
||||
queryParamName: String = "q",
|
||||
crossinline searchFunction: suspend (String) -> List<T>
|
||||
) {
|
||||
safeExecute {
|
||||
val query = getQueryParameter(queryParamName) ?: return@safeExecute
|
||||
val entities = searchFunction(query)
|
||||
respondWithList(entities)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for find all operations
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleFindAll(
|
||||
crossinline findAllFunction: suspend () -> List<T>
|
||||
) {
|
||||
safeExecute {
|
||||
val entities = findAllFunction()
|
||||
respondWithList(entities)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for create operations
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleCreate(
|
||||
crossinline createFunction: suspend (T) -> T
|
||||
) {
|
||||
safeExecute {
|
||||
val entity = safeReceive<T>() ?: return@safeExecute
|
||||
val createdEntity = createFunction(entity)
|
||||
respond(HttpStatusCode.Created, createdEntity)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for update operations
|
||||
*/
|
||||
suspend inline fun <reified T : Any> ApplicationCall.handleUpdate(
|
||||
paramName: String = "id",
|
||||
crossinline updateFunction: suspend (Uuid, T) -> T?
|
||||
) {
|
||||
safeExecute {
|
||||
val id = getUuidParameter(paramName) ?: return@safeExecute
|
||||
val entity = safeReceive<T>() ?: return@safeExecute
|
||||
val updatedEntity = updateFunction(id, entity)
|
||||
respondWithEntityOrNotFound(updatedEntity, "Entity not found or update failed")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for delete operations
|
||||
*/
|
||||
suspend inline fun ApplicationCall.handleDelete(
|
||||
paramName: String = "id",
|
||||
crossinline deleteFunction: suspend (Uuid) -> Boolean
|
||||
) {
|
||||
safeExecute {
|
||||
val id = getUuidParameter(paramName) ?: return@safeExecute
|
||||
val deleted = deleteFunction(id)
|
||||
if (deleted) {
|
||||
respond(HttpStatusCode.NoContent)
|
||||
} else {
|
||||
respond(HttpStatusCode.NotFound, mapOf("error" to "Entity not found"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user