(fix) Umbau zu SCS

**Backend:**
- Vervollständigen Sie alle Repository-Implementierungen
- Implementieren Sie die Authentifizierung und Autorisierung
- Fügen Sie Validierung für alle API-Endpunkte hinzu
This commit is contained in:
stefan
2025-07-19 17:54:25 +02:00
parent db465e461e
commit 8c1ddb6cb2
47 changed files with 4278 additions and 1422 deletions
@@ -5,6 +5,8 @@ import at.mocode.events.application.usecase.*
import at.mocode.events.domain.repository.VeranstaltungRepository
import at.mocode.enums.SparteE
import at.mocode.serializers.UuidSerializer
import at.mocode.validation.ApiValidationUtils
import at.mocode.validation.ValidationError
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuidFrom
import io.ktor.http.*
@@ -40,10 +42,32 @@ class VeranstaltungController(
// GET /api/events - Get all events with optional filtering
get {
try {
// Validate query parameters
val validationErrors = ApiValidationUtils.validateQueryParameters(
limit = call.request.queryParameters["limit"],
offset = call.request.queryParameters["offset"],
startDate = call.request.queryParameters["startDate"],
endDate = call.request.queryParameters["endDate"],
search = call.request.queryParameters["search"]
)
if (!ApiValidationUtils.isValid(validationErrors)) {
call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Any>(ApiValidationUtils.createErrorMessage(validationErrors))
)
return@get
}
val activeOnly = call.request.queryParameters["activeOnly"]?.toBoolean() ?: true
val limit = call.request.queryParameters["limit"]?.toInt() ?: 100
val offset = call.request.queryParameters["offset"]?.toInt() ?: 0
val organizerId = call.request.queryParameters["organizerId"]?.let { uuidFrom(it) }
val organizerId = call.request.queryParameters["organizerId"]?.let {
ApiValidationUtils.validateUuidString(it) ?: return@get call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Any>("Invalid organizerId format")
)
}
val searchTerm = call.request.queryParameters["search"]
val publicOnly = call.request.queryParameters["publicOnly"]?.toBoolean() ?: false
val startDate = call.request.queryParameters["startDate"]?.let { LocalDate.parse(it) }
@@ -104,6 +128,24 @@ class VeranstaltungController(
post {
try {
val createRequest = call.receive<CreateEventRequest>()
// Validate input using shared validation utilities
val validationErrors = ApiValidationUtils.validateEventRequest(
name = createRequest.name,
ort = createRequest.ort,
startDatum = createRequest.startDatum,
endDatum = createRequest.endDatum,
maxTeilnehmer = createRequest.maxTeilnehmer
)
if (!ApiValidationUtils.isValid(validationErrors)) {
call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Any>(ApiValidationUtils.createErrorMessage(validationErrors))
)
return@post
}
val useCaseRequest = CreateVeranstaltungUseCase.CreateVeranstaltungRequest(
name = createRequest.name,
beschreibung = createRequest.beschreibung,
@@ -140,6 +182,24 @@ class VeranstaltungController(
try {
val eventId = uuidFrom(call.parameters["id"]!!)
val updateRequest = call.receive<UpdateEventRequest>()
// Validate input using shared validation utilities
val validationErrors = ApiValidationUtils.validateEventRequest(
name = updateRequest.name,
ort = updateRequest.ort,
startDatum = updateRequest.startDatum,
endDatum = updateRequest.endDatum,
maxTeilnehmer = updateRequest.maxTeilnehmer
)
if (!ApiValidationUtils.isValid(validationErrors)) {
call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Any>(ApiValidationUtils.createErrorMessage(validationErrors))
)
return@put
}
val useCaseRequest = UpdateVeranstaltungUseCase.UpdateVeranstaltungRequest(
veranstaltungId = eventId,
name = updateRequest.name,
@@ -178,8 +238,26 @@ class VeranstaltungController(
// DELETE /api/events/{id} - Delete event
delete("/{id}") {
try {
val eventId = uuidFrom(call.parameters["id"]!!)
val forceDelete = call.request.queryParameters["force"]?.toBoolean() ?: false
val eventId = ApiValidationUtils.validateUuidString(call.parameters["id"])
?: return@delete call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Any>("Invalid event ID format")
)
// Validate force parameter if provided
val forceParam = call.request.queryParameters["force"]
val forceDelete = if (forceParam != null) {
try {
forceParam.toBoolean()
} catch (e: Exception) {
return@delete call.respond(
HttpStatusCode.BadRequest,
ApiResponse.error<Any>("Invalid force parameter. Must be true or false")
)
}
} else {
false
}
val useCaseRequest = DeleteVeranstaltungUseCase.DeleteVeranstaltungRequest(
veranstaltungId = eventId,
forceDelete = forceDelete
@@ -3,6 +3,8 @@ package at.mocode.events.infrastructure.repository
import at.mocode.enums.SparteE
import at.mocode.events.domain.model.Veranstaltung
import at.mocode.events.domain.repository.VeranstaltungRepository
import at.mocode.events.infrastructure.repository.VeranstaltungTable
import at.mocode.shared.database.DatabaseFactory
import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
@@ -19,24 +21,24 @@ import org.jetbrains.exposed.sql.statements.UpdateBuilder
*/
class VeranstaltungRepositoryImpl : VeranstaltungRepository {
override suspend fun findById(id: Uuid): Veranstaltung? {
return VeranstaltungTable.selectAll().where { VeranstaltungTable.id eq id }
override suspend fun findById(id: Uuid): Veranstaltung? = DatabaseFactory.dbQuery {
VeranstaltungTable.selectAll().where { VeranstaltungTable.id eq id }
.map { rowToVeranstaltung(it) }
.singleOrNull()
}
override suspend fun findByName(searchTerm: String, limit: Int): List<Veranstaltung> {
override suspend fun findByName(searchTerm: String, limit: Int): List<Veranstaltung> = DatabaseFactory.dbQuery {
val searchPattern = "%$searchTerm%"
return VeranstaltungTable.selectAll().where { VeranstaltungTable.name like searchPattern }
VeranstaltungTable.selectAll().where { VeranstaltungTable.name like searchPattern }
.orderBy(VeranstaltungTable.startDatum, SortOrder.DESC)
.limit(limit)
.map { rowToVeranstaltung(it) }
}
override suspend fun findByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): List<Veranstaltung> {
override suspend fun findByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): List<Veranstaltung> = DatabaseFactory.dbQuery {
val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.veranstalterVereinId eq vereinId }
return if (activeOnly) {
if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true }
} else {
query
@@ -44,13 +46,13 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
.map { rowToVeranstaltung(it) }
}
override suspend fun findByDateRange(startDate: LocalDate, endDate: LocalDate, activeOnly: Boolean): List<Veranstaltung> {
override suspend fun findByDateRange(startDate: LocalDate, endDate: LocalDate, activeOnly: Boolean): List<Veranstaltung> = DatabaseFactory.dbQuery {
val query = VeranstaltungTable.selectAll().where {
(VeranstaltungTable.startDatum greaterEq startDate) and
(VeranstaltungTable.endDatum lessEq endDate)
}
return if (activeOnly) {
if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true }
} else {
query
@@ -58,10 +60,10 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
.map { rowToVeranstaltung(it) }
}
override suspend fun findByStartDate(date: LocalDate, activeOnly: Boolean): List<Veranstaltung> {
override suspend fun findByStartDate(date: LocalDate, activeOnly: Boolean): List<Veranstaltung> = DatabaseFactory.dbQuery {
val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.startDatum eq date }
return if (activeOnly) {
if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true }
} else {
query
@@ -69,17 +71,17 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
.map { rowToVeranstaltung(it) }
}
override suspend fun findAllActive(limit: Int, offset: Int): List<Veranstaltung> {
return VeranstaltungTable.selectAll().where { VeranstaltungTable.istAktiv eq true }
override suspend fun findAllActive(limit: Int, offset: Int): List<Veranstaltung> = DatabaseFactory.dbQuery {
VeranstaltungTable.selectAll().where { VeranstaltungTable.istAktiv eq true }
.orderBy(VeranstaltungTable.startDatum, SortOrder.DESC)
.limit(limit, offset.toLong())
.map { rowToVeranstaltung(it) }
}
override suspend fun findPublicEvents(activeOnly: Boolean): List<Veranstaltung> {
override suspend fun findPublicEvents(activeOnly: Boolean): List<Veranstaltung> = DatabaseFactory.dbQuery {
val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.istOeffentlich eq true }
return if (activeOnly) {
if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true }
} else {
query
@@ -87,7 +89,7 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
.map { rowToVeranstaltung(it) }
}
override suspend fun save(veranstaltung: Veranstaltung): Veranstaltung {
override suspend fun save(veranstaltung: Veranstaltung): Veranstaltung = DatabaseFactory.dbQuery {
val now = Clock.System.now()
val updatedVeranstaltung = veranstaltung.copy(updatedAt = now)
@@ -96,7 +98,7 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
.where { VeranstaltungTable.id eq veranstaltung.veranstaltungId }
.singleOrNull()
return if (existingRecord != null) {
if (existingRecord != null) {
// Update existing record
VeranstaltungTable.update({ VeranstaltungTable.id eq veranstaltung.veranstaltungId }) {
veranstaltungToStatement(it, updatedVeranstaltung)
@@ -112,20 +114,20 @@ class VeranstaltungRepositoryImpl : VeranstaltungRepository {
}
}
override suspend fun delete(id: Uuid): Boolean {
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
val deletedRows = VeranstaltungTable.deleteWhere { VeranstaltungTable.id eq id }
return deletedRows > 0
deletedRows > 0
}
override suspend fun countActive(): Long {
return VeranstaltungTable.selectAll().where { VeranstaltungTable.istAktiv eq true }
override suspend fun countActive(): Long = DatabaseFactory.dbQuery {
VeranstaltungTable.selectAll().where { VeranstaltungTable.istAktiv eq true }
.count()
}
override suspend fun countByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): Long {
override suspend fun countByVeranstalterVereinId(vereinId: Uuid, activeOnly: Boolean): Long = DatabaseFactory.dbQuery {
val query = VeranstaltungTable.selectAll().where { VeranstaltungTable.veranstalterVereinId eq vereinId }
return if (activeOnly) {
if (activeOnly) {
query.andWhere { VeranstaltungTable.istAktiv eq true }
} else {
query