(fix) RESTful API

This commit is contained in:
2025-06-29 23:59:38 +02:00
parent de4f134b7a
commit c38270eb58
9 changed files with 830 additions and 0 deletions
@@ -0,0 +1,14 @@
package at.mocode.model
import at.mocode.shared.model.Artikel
import com.benasher44.uuid.Uuid
interface ArtikelRepository {
suspend fun findAll(): List<Artikel>
suspend fun findById(id: Uuid): Artikel?
suspend fun create(artikel: Artikel): Artikel
suspend fun update(id: Uuid, artikel: Artikel): Artikel?
suspend fun delete(id: Uuid): Boolean
suspend fun findByVerbandsabgabe(istVerbandsabgabe: Boolean): List<Artikel>
suspend fun search(query: String): List<Artikel>
}
@@ -0,0 +1,15 @@
package at.mocode.model
import at.mocode.shared.stammdaten.Person
import com.benasher44.uuid.Uuid
interface PersonRepository {
suspend fun findAll(): List<Person>
suspend fun findById(id: Uuid): Person?
suspend fun findByOepsSatzNr(oepsSatzNr: String): Person?
suspend fun create(person: Person): Person
suspend fun update(id: Uuid, person: Person): Person?
suspend fun delete(id: Uuid): Boolean
suspend fun findByVereinId(vereinId: Uuid): List<Person>
suspend fun search(query: String): List<Person>
}
@@ -0,0 +1,84 @@
package at.mocode.model
import at.mocode.shared.model.Artikel
import at.mocode.tables.ArtikelTable
import com.benasher44.uuid.Uuid
import com.ionspin.kotlin.bignum.decimal.BigDecimal
import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
class PostgresArtikelRepository : ArtikelRepository {
override suspend fun findAll(): List<Artikel> = transaction {
ArtikelTable.selectAll().map { rowToArtikel(it) }
}
override suspend fun findById(id: Uuid): Artikel? = transaction {
ArtikelTable.select { ArtikelTable.id eq id }
.map { rowToArtikel(it) }
.singleOrNull()
}
override suspend fun create(artikel: Artikel): Artikel = transaction {
val now = Clock.System.now()
ArtikelTable.insert {
it[id] = artikel.id
it[bezeichnung] = artikel.bezeichnung
it[preis] = artikel.preis.toStringExpanded()
it[einheit] = artikel.einheit
it[istVerbandsabgabe] = artikel.istVerbandsabgabe
it[createdAt] = now
it[updatedAt] = now
}
artikel.copy(createdAt = now, updatedAt = now)
}
override suspend fun update(id: Uuid, artikel: Artikel): Artikel? = transaction {
val updateCount = ArtikelTable.update({ ArtikelTable.id eq id }) {
it[bezeichnung] = artikel.bezeichnung
it[preis] = artikel.preis.toStringExpanded()
it[einheit] = artikel.einheit
it[istVerbandsabgabe] = artikel.istVerbandsabgabe
it[updatedAt] = Clock.System.now()
}
if (updateCount > 0) {
ArtikelTable.select { ArtikelTable.id eq id }
.map { rowToArtikel(it) }
.singleOrNull()
} else null
}
override suspend fun delete(id: Uuid): Boolean = transaction {
ArtikelTable.deleteWhere { ArtikelTable.id eq id } > 0
}
override suspend fun findByVerbandsabgabe(istVerbandsabgabe: Boolean): List<Artikel> = transaction {
ArtikelTable.select { ArtikelTable.istVerbandsabgabe eq istVerbandsabgabe }
.map { rowToArtikel(it) }
}
override suspend fun search(query: String): List<Artikel> = transaction {
ArtikelTable.select {
(ArtikelTable.bezeichnung.lowerCase() like "%${query.lowercase()}%") or
(ArtikelTable.einheit.lowerCase() like "%${query.lowercase()}%")
}.map { rowToArtikel(it) }
}
private fun rowToArtikel(row: ResultRow): Artikel {
return Artikel(
id = row[ArtikelTable.id],
bezeichnung = row[ArtikelTable.bezeichnung],
preis = try {
BigDecimal.parseString(row[ArtikelTable.preis])
} catch (e: Exception) {
BigDecimal.ZERO
},
einheit = row[ArtikelTable.einheit],
istVerbandsabgabe = row[ArtikelTable.istVerbandsabgabe],
createdAt = row[ArtikelTable.createdAt],
updatedAt = row[ArtikelTable.updatedAt]
)
}
}
@@ -0,0 +1,167 @@
package at.mocode.model
import at.mocode.shared.enums.FunktionaerRolle
import at.mocode.shared.stammdaten.LizenzInfo
import at.mocode.shared.stammdaten.Person
import at.mocode.tables.PersonenTable
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuidFrom
import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
class PostgresPersonRepository : PersonRepository {
override suspend fun findAll(): List<Person> = transaction {
PersonenTable.selectAll().map { rowToPerson(it) }
}
override suspend fun findById(id: Uuid): Person? = transaction {
PersonenTable.select { PersonenTable.id eq id }
.map { rowToPerson(it) }
.singleOrNull()
}
override suspend fun findByOepsSatzNr(oepsSatzNr: String): Person? = transaction {
PersonenTable.select { PersonenTable.oepsSatzNr eq oepsSatzNr }
.map { rowToPerson(it) }
.singleOrNull()
}
override suspend fun create(person: Person): Person = transaction {
val now = Clock.System.now()
PersonenTable.insert {
it[id] = person.id
it[oepsSatzNr] = person.oepsSatzNr
it[nachname] = person.nachname
it[vorname] = person.vorname
it[titel] = person.titel
it[geburtsdatum] = person.geburtsdatum
it[geschlecht] = person.geschlechtE
it[nationalitaet] = person.nationalitaet
it[email] = person.email
it[telefon] = person.telefon
it[adresse] = person.adresse
it[plz] = person.plz
it[ort] = person.ort
it[stammVereinId] = person.stammVereinId
it[mitgliedsNummerIntern] = person.mitgliedsNummerIntern
it[letzteZahlungJahr] = person.letzteZahlungJahr
it[feiId] = person.feiId
it[istGesperrt] = person.istGesperrt
it[sperrGrund] = person.sperrGrund
it[rollenCsv] = person.rollen.joinToString(",") { rolle -> rolle.name }
it[qualifikationenRichterCsv] = person.qualifikationenRichter.joinToString(",")
it[qualifikationenParcoursbauerCsv] = person.qualifikationenParcoursbauer.joinToString(",")
it[istAktiv] = person.istAktiv
it[createdAt] = now
it[updatedAt] = now
}
person.copy(createdAt = now, updatedAt = now)
}
override suspend fun update(id: Uuid, person: Person): Person? = transaction {
val updateCount = PersonenTable.update({ PersonenTable.id eq id }) {
it[nachname] = person.nachname
it[vorname] = person.vorname
it[titel] = person.titel
it[geburtsdatum] = person.geburtsdatum
it[geschlecht] = person.geschlechtE
it[nationalitaet] = person.nationalitaet
it[email] = person.email
it[telefon] = person.telefon
it[adresse] = person.adresse
it[plz] = person.plz
it[ort] = person.ort
it[stammVereinId] = person.stammVereinId
it[mitgliedsNummerIntern] = person.mitgliedsNummerIntern
it[letzteZahlungJahr] = person.letzteZahlungJahr
it[feiId] = person.feiId
it[istGesperrt] = person.istGesperrt
it[sperrGrund] = person.sperrGrund
it[rollenCsv] = person.rollen.joinToString(",") { rolle -> rolle.name }
it[qualifikationenRichterCsv] = person.qualifikationenRichter.joinToString(",")
it[qualifikationenParcoursbauerCsv] = person.qualifikationenParcoursbauer.joinToString(",")
it[istAktiv] = person.istAktiv
it[updatedAt] = Clock.System.now()
}
if (updateCount > 0) {
PersonenTable.select { PersonenTable.id eq id }
.map { rowToPerson(it) }
.singleOrNull()
} else null
}
override suspend fun delete(id: Uuid): Boolean = transaction {
PersonenTable.deleteWhere { PersonenTable.id eq id } > 0
}
override suspend fun findByVereinId(vereinId: Uuid): List<Person> = transaction {
PersonenTable.select { PersonenTable.stammVereinId eq vereinId }
.map { rowToPerson(it) }
}
override suspend fun search(query: String): List<Person> = transaction {
PersonenTable.select {
(PersonenTable.nachname.lowerCase() like "%${query.lowercase()}%") or
(PersonenTable.vorname.lowerCase() like "%${query.lowercase()}%") or
(PersonenTable.email?.lowerCase()?.like("%${query.lowercase()}%") ?: Op.FALSE)
}.map { rowToPerson(it) }
}
private fun rowToPerson(row: ResultRow): Person {
return Person(
id = row[PersonenTable.id],
oepsSatzNr = row[PersonenTable.oepsSatzNr],
nachname = row[PersonenTable.nachname],
vorname = row[PersonenTable.vorname],
titel = row[PersonenTable.titel],
geburtsdatum = row[PersonenTable.geburtsdatum],
geschlechtE = row[PersonenTable.geschlecht],
nationalitaet = row[PersonenTable.nationalitaet],
email = row[PersonenTable.email],
telefon = row[PersonenTable.telefon],
adresse = row[PersonenTable.adresse],
plz = row[PersonenTable.plz],
ort = row[PersonenTable.ort],
stammVereinId = row[PersonenTable.stammVereinId],
mitgliedsNummerIntern = row[PersonenTable.mitgliedsNummerIntern],
letzteZahlungJahr = row[PersonenTable.letzteZahlungJahr],
feiId = row[PersonenTable.feiId],
istGesperrt = row[PersonenTable.istGesperrt],
sperrGrund = row[PersonenTable.sperrGrund],
rollen = parseRollen(row[PersonenTable.rollenCsv]),
lizenzen = emptyList(), // TODO: Load from separate table if needed
qualifikationenRichter = parseQualifikationen(row[PersonenTable.qualifikationenRichterCsv]),
qualifikationenParcoursbauer = parseQualifikationen(row[PersonenTable.qualifikationenParcoursbauerCsv]),
istAktiv = row[PersonenTable.istAktiv],
createdAt = row[PersonenTable.createdAt],
updatedAt = row[PersonenTable.updatedAt]
)
}
private fun parseRollen(rollenCsv: String?): Set<FunktionaerRolle> {
return if (rollenCsv.isNullOrBlank()) {
emptySet()
} else {
rollenCsv.split(",")
.mapNotNull { roleName ->
try {
FunktionaerRolle.valueOf(roleName.trim())
} catch (e: IllegalArgumentException) {
null
}
}
.toSet()
}
}
private fun parseQualifikationen(qualifikationenCsv: String?): List<String> {
return if (qualifikationenCsv.isNullOrBlank()) {
emptyList()
} else {
qualifikationenCsv.split(",").map { it.trim() }.filter { it.isNotEmpty() }
}
}
}
@@ -0,0 +1,106 @@
package at.mocode.model
import at.mocode.shared.stammdaten.Verein
import at.mocode.tables.VereineTable
import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
class PostgresVereinRepository : VereinRepository {
override suspend fun findAll(): List<Verein> = transaction {
VereineTable.selectAll().map { rowToVerein(it) }
}
override suspend fun findById(id: Uuid): Verein? = transaction {
VereineTable.select { VereineTable.id eq id }
.map { rowToVerein(it) }
.singleOrNull()
}
override suspend fun findByOepsVereinsNr(oepsVereinsNr: String): Verein? = transaction {
VereineTable.select { VereineTable.oepsVereinsNr eq oepsVereinsNr }
.map { rowToVerein(it) }
.singleOrNull()
}
override suspend fun create(verein: Verein): Verein = transaction {
val now = Clock.System.now()
VereineTable.insert {
it[id] = verein.id
it[oepsVereinsNr] = verein.oepsVereinsNr
it[name] = verein.name
it[kuerzel] = verein.kuerzel
it[bundesland] = verein.bundesland
it[adresse] = verein.adresse
it[plz] = verein.plz
it[ort] = verein.ort
it[email] = verein.email
it[telefon] = verein.telefon
it[webseite] = verein.webseite
it[istAktiv] = verein.istAktiv
it[createdAt] = now
it[updatedAt] = now
}
verein.copy(createdAt = now, updatedAt = now)
}
override suspend fun update(id: Uuid, verein: Verein): Verein? = transaction {
val updateCount = VereineTable.update({ VereineTable.id eq id }) {
it[name] = verein.name
it[kuerzel] = verein.kuerzel
it[bundesland] = verein.bundesland
it[adresse] = verein.adresse
it[plz] = verein.plz
it[ort] = verein.ort
it[email] = verein.email
it[telefon] = verein.telefon
it[webseite] = verein.webseite
it[istAktiv] = verein.istAktiv
it[updatedAt] = Clock.System.now()
}
if (updateCount > 0) {
VereineTable.select { VereineTable.id eq id }
.map { rowToVerein(it) }
.singleOrNull()
} else null
}
override suspend fun delete(id: Uuid): Boolean = transaction {
VereineTable.deleteWhere { VereineTable.id eq id } > 0
}
override suspend fun findByBundesland(bundesland: String): List<Verein> = transaction {
VereineTable.select { VereineTable.bundesland eq bundesland }
.map { rowToVerein(it) }
}
override suspend fun search(query: String): List<Verein> = transaction {
VereineTable.select {
(VereineTable.name.lowerCase() like "%${query.lowercase()}%") or
(VereineTable.kuerzel?.lowerCase()?.like("%${query.lowercase()}%") ?: Op.FALSE) or
(VereineTable.ort?.lowerCase()?.like("%${query.lowercase()}%") ?: Op.FALSE)
}.map { rowToVerein(it) }
}
private fun rowToVerein(row: ResultRow): Verein {
return Verein(
id = row[VereineTable.id],
oepsVereinsNr = row[VereineTable.oepsVereinsNr],
name = row[VereineTable.name],
kuerzel = row[VereineTable.kuerzel],
bundesland = row[VereineTable.bundesland],
adresse = row[VereineTable.adresse],
plz = row[VereineTable.plz],
ort = row[VereineTable.ort],
email = row[VereineTable.email],
telefon = row[VereineTable.telefon],
webseite = row[VereineTable.webseite],
istAktiv = row[VereineTable.istAktiv],
createdAt = row[VereineTable.createdAt],
updatedAt = row[VereineTable.updatedAt]
)
}
}
@@ -0,0 +1,15 @@
package at.mocode.model
import at.mocode.shared.stammdaten.Verein
import com.benasher44.uuid.Uuid
interface VereinRepository {
suspend fun findAll(): List<Verein>
suspend fun findById(id: Uuid): Verein?
suspend fun findByOepsVereinsNr(oepsVereinsNr: String): Verein?
suspend fun create(verein: Verein): Verein
suspend fun update(id: Uuid, verein: Verein): Verein?
suspend fun delete(id: Uuid): Boolean
suspend fun findByBundesland(bundesland: String): List<Verein>
suspend fun search(query: String): List<Verein>
}
@@ -0,0 +1,130 @@
package at.mocode.routes
import at.mocode.model.ArtikelRepository
import at.mocode.model.PostgresArtikelRepository
import at.mocode.shared.model.Artikel
import com.benasher44.uuid.uuidFrom
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.artikelRoutes() {
val artikelRepository: ArtikelRepository = PostgresArtikelRepository()
route("/api/artikel") {
// GET /api/artikel - Get all articles
get {
try {
val artikel = artikelRepository.findAll()
call.respond(HttpStatusCode.OK, artikel)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/artikel/{id} - Get article by ID
get("/{id}") {
try {
val id = call.parameters["id"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing artikel ID")
)
val uuid = uuidFrom(id)
val artikel = artikelRepository.findById(uuid)
if (artikel != null) {
call.respond(HttpStatusCode.OK, artikel)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Artikel not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/artikel/search?q={query} - Search articles
get("/search") {
try {
val query = call.request.queryParameters["q"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing search query parameter 'q'")
)
val artikel = artikelRepository.search(query)
call.respond(HttpStatusCode.OK, artikel)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/artikel/verbandsabgabe/{istVerbandsabgabe} - Get articles by association fee status
get("/verbandsabgabe/{istVerbandsabgabe}") {
try {
val istVerbandsabgabe = call.parameters["istVerbandsabgabe"]?.toBoolean() ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing or invalid verbandsabgabe parameter")
)
val artikel = artikelRepository.findByVerbandsabgabe(istVerbandsabgabe)
call.respond(HttpStatusCode.OK, artikel)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// POST /api/artikel - Create new article
post {
try {
val artikel = call.receive<Artikel>()
val createdArtikel = artikelRepository.create(artikel)
call.respond(HttpStatusCode.Created, createdArtikel)
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
}
}
// PUT /api/artikel/{id} - Update article
put("/{id}") {
try {
val id = call.parameters["id"] ?: return@put call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing artikel ID")
)
val uuid = uuidFrom(id)
val artikel = call.receive<Artikel>()
val updatedArtikel = artikelRepository.update(uuid, artikel)
if (updatedArtikel != null) {
call.respond(HttpStatusCode.OK, updatedArtikel)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Artikel not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
}
}
// DELETE /api/artikel/{id} - Delete article
delete("/{id}") {
try {
val id = call.parameters["id"] ?: return@delete call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing artikel ID")
)
val uuid = uuidFrom(id)
val deleted = artikelRepository.delete(uuid)
if (deleted) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Artikel not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
}
}
@@ -0,0 +1,151 @@
package at.mocode.routes
import at.mocode.model.PersonRepository
import at.mocode.model.PostgresPersonRepository
import at.mocode.shared.stammdaten.Person
import com.benasher44.uuid.uuidFrom
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.personRoutes() {
val personRepository: PersonRepository = PostgresPersonRepository()
route("/api/persons") {
// GET /api/persons - Get all persons
get {
try {
val persons = personRepository.findAll()
call.respond(HttpStatusCode.OK, persons)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/persons/{id} - Get person by ID
get("/{id}") {
try {
val id = call.parameters["id"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing person ID")
)
val uuid = uuidFrom(id)
val person = personRepository.findById(uuid)
if (person != null) {
call.respond(HttpStatusCode.OK, person)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Person not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/persons/oeps/{oepsSatzNr} - Get person by OEPS number
get("/oeps/{oepsSatzNr}") {
try {
val oepsSatzNr = call.parameters["oepsSatzNr"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing OEPS Satz number")
)
val person = personRepository.findByOepsSatzNr(oepsSatzNr)
if (person != null) {
call.respond(HttpStatusCode.OK, person)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Person not found"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/persons/search?q={query} - Search persons
get("/search") {
try {
val query = call.request.queryParameters["q"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing search query parameter 'q'")
)
val persons = personRepository.search(query)
call.respond(HttpStatusCode.OK, persons)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/persons/verein/{vereinId} - Get persons by club ID
get("/verein/{vereinId}") {
try {
val vereinId = call.parameters["vereinId"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing verein ID")
)
val uuid = uuidFrom(vereinId)
val persons = personRepository.findByVereinId(uuid)
call.respond(HttpStatusCode.OK, persons)
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// POST /api/persons - Create new person
post {
try {
val person = call.receive<Person>()
val createdPerson = personRepository.create(person)
call.respond(HttpStatusCode.Created, createdPerson)
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
}
}
// PUT /api/persons/{id} - Update person
put("/{id}") {
try {
val id = call.parameters["id"] ?: return@put call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing person ID")
)
val uuid = uuidFrom(id)
val person = call.receive<Person>()
val updatedPerson = personRepository.update(uuid, person)
if (updatedPerson != null) {
call.respond(HttpStatusCode.OK, updatedPerson)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Person not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
}
}
// DELETE /api/persons/{id} - Delete person
delete("/{id}") {
try {
val id = call.parameters["id"] ?: return@delete call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing person ID")
)
val uuid = uuidFrom(id)
val deleted = personRepository.delete(uuid)
if (deleted) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Person not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
}
}
@@ -0,0 +1,148 @@
package at.mocode.routes
import at.mocode.model.PostgresVereinRepository
import at.mocode.model.VereinRepository
import at.mocode.shared.stammdaten.Verein
import com.benasher44.uuid.uuidFrom
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.vereinRoutes() {
val vereinRepository: VereinRepository = PostgresVereinRepository()
route("/api/vereine") {
// GET /api/vereine - Get all clubs
get {
try {
val vereine = vereinRepository.findAll()
call.respond(HttpStatusCode.OK, vereine)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/vereine/{id} - Get club by ID
get("/{id}") {
try {
val id = call.parameters["id"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing verein ID")
)
val uuid = uuidFrom(id)
val verein = vereinRepository.findById(uuid)
if (verein != null) {
call.respond(HttpStatusCode.OK, verein)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Verein not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/vereine/oeps/{oepsVereinsNr} - Get club by OEPS number
get("/oeps/{oepsVereinsNr}") {
try {
val oepsVereinsNr = call.parameters["oepsVereinsNr"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing OEPS Vereins number")
)
val verein = vereinRepository.findByOepsVereinsNr(oepsVereinsNr)
if (verein != null) {
call.respond(HttpStatusCode.OK, verein)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Verein not found"))
}
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/vereine/search?q={query} - Search clubs
get("/search") {
try {
val query = call.request.queryParameters["q"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing search query parameter 'q'")
)
val vereine = vereinRepository.search(query)
call.respond(HttpStatusCode.OK, vereine)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// GET /api/vereine/bundesland/{bundesland} - Get clubs by state
get("/bundesland/{bundesland}") {
try {
val bundesland = call.parameters["bundesland"] ?: return@get call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing bundesland")
)
val vereine = vereinRepository.findByBundesland(bundesland)
call.respond(HttpStatusCode.OK, vereine)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
// POST /api/vereine - Create new club
post {
try {
val verein = call.receive<Verein>()
val createdVerein = vereinRepository.create(verein)
call.respond(HttpStatusCode.Created, createdVerein)
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
}
}
// PUT /api/vereine/{id} - Update club
put("/{id}") {
try {
val id = call.parameters["id"] ?: return@put call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing verein ID")
)
val uuid = uuidFrom(id)
val verein = call.receive<Verein>()
val updatedVerein = vereinRepository.update(uuid, verein)
if (updatedVerein != null) {
call.respond(HttpStatusCode.OK, updatedVerein)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Verein not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
}
}
// DELETE /api/vereine/{id} - Delete club
delete("/{id}") {
try {
val id = call.parameters["id"] ?: return@delete call.respond(
HttpStatusCode.BadRequest,
mapOf("error" to "Missing verein ID")
)
val uuid = uuidFrom(id)
val deleted = vereinRepository.delete(uuid)
if (deleted) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "Verein not found"))
}
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid UUID format"))
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, mapOf("error" to e.message))
}
}
}
}