Integrate advanced filtering, sorting, and pagination logic into Altersklasse, Bundesland, and Platz controllers. Enhance error handling with centralized ErrorCodes and update date serialization for consistent handling of Instant values.
This commit is contained in:
parent
f91b067b36
commit
74df3514ae
|
|
@ -3,6 +3,9 @@
|
||||||
package at.mocode.masterdata.api.rest
|
package at.mocode.masterdata.api.rest
|
||||||
|
|
||||||
import at.mocode.core.domain.model.ApiResponse
|
import at.mocode.core.domain.model.ApiResponse
|
||||||
|
import at.mocode.core.domain.model.ErrorCodes
|
||||||
|
import at.mocode.core.domain.model.PagedResponse
|
||||||
|
import at.mocode.core.domain.model.SortDirection
|
||||||
import at.mocode.core.domain.model.SparteE
|
import at.mocode.core.domain.model.SparteE
|
||||||
import at.mocode.masterdata.application.usecase.CreateAltersklasseUseCase
|
import at.mocode.masterdata.application.usecase.CreateAltersklasseUseCase
|
||||||
import at.mocode.masterdata.application.usecase.GetAltersklasseUseCase
|
import at.mocode.masterdata.application.usecase.GetAltersklasseUseCase
|
||||||
|
|
@ -13,6 +16,9 @@ import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import at.mocode.core.domain.serialization.InstantSerializer
|
||||||
|
import kotlin.time.Instant
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API controller for age class management operations.
|
* REST API controller for age class management operations.
|
||||||
|
|
@ -34,8 +40,10 @@ class AltersklasseController(
|
||||||
val geschlechtFilter: String? = null,
|
val geschlechtFilter: String? = null,
|
||||||
val oetoRegelReferenzId: String? = null,
|
val oetoRegelReferenzId: String? = null,
|
||||||
val istAktiv: Boolean = true,
|
val istAktiv: Boolean = true,
|
||||||
val createdAt: String,
|
@Serializable(with = InstantSerializer::class)
|
||||||
val updatedAt: String
|
val createdAt: Instant,
|
||||||
|
@Serializable(with = InstantSerializer::class)
|
||||||
|
val updatedAt: Instant
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -76,9 +84,93 @@ class AltersklasseController(
|
||||||
}
|
}
|
||||||
val geschlecht = call.request.queryParameters["geschlecht"]?.getOrNull(0)
|
val geschlecht = call.request.queryParameters["geschlecht"]?.getOrNull(0)
|
||||||
|
|
||||||
val response = getAltersklasseUseCase.getAllActive(sparte, geschlecht)
|
val search = call.request.queryParameters["search"]?.trim().takeUnless { it.isNullOrBlank() }
|
||||||
val dtos = response.altersklassen.map { it.toDto() }
|
val activeParam = call.request.queryParameters["active"]?.lowercase()
|
||||||
call.respond(ApiResponse.success(dtos))
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
|
val sizeParam = call.request.queryParameters["size"]?.toIntOrNull()
|
||||||
|
val unpaged = call.request.queryParameters["unpaged"]?.let { v ->
|
||||||
|
when (v.lowercase()) { "true", "1", "yes" -> true; else -> false }
|
||||||
|
} ?: false
|
||||||
|
val size = sizeParam ?: 20
|
||||||
|
val sortParams = call.request.queryParameters.getAll("sort")?.mapNotNull { token ->
|
||||||
|
val parts = token.split(",")
|
||||||
|
if (parts.isEmpty()) null else {
|
||||||
|
val field = parts[0].trim()
|
||||||
|
val dir = if (parts.getOrNull(1)?.trim()?.equals("desc", ignoreCase = true) == true) SortDirection.DESC else SortDirection.ASC
|
||||||
|
field to dir
|
||||||
|
}
|
||||||
|
} ?: listOf("bezeichnung" to SortDirection.ASC)
|
||||||
|
|
||||||
|
val baseList = when (activeParam) {
|
||||||
|
null, "true" -> getAltersklasseUseCase.getAllActive(sparte, geschlecht).altersklassen
|
||||||
|
"false", "all" -> getAltersklasseUseCase.getAllActive(null, null).altersklassen // TODO: getAll wenn verfügbar
|
||||||
|
else -> getAltersklasseUseCase.getAllActive(sparte, geschlecht).altersklassen
|
||||||
|
}
|
||||||
|
|
||||||
|
val filtered = baseList.asSequence()
|
||||||
|
.filter { item ->
|
||||||
|
when (activeParam) {
|
||||||
|
"true", null -> item.istAktiv
|
||||||
|
"false" -> !item.istAktiv
|
||||||
|
"all" -> true
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { item ->
|
||||||
|
val q = search
|
||||||
|
if (q.isNullOrBlank()) return@filter true
|
||||||
|
val s = q.lowercase()
|
||||||
|
item.altersklasseCode.lowercase().contains(s) || item.bezeichnung.lowercase().contains(s)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
var list = filtered
|
||||||
|
sortParams.forEach { (field, dir) ->
|
||||||
|
list = when (field) {
|
||||||
|
"bezeichnung" -> if (dir == SortDirection.ASC) list.sortedBy { it.bezeichnung.lowercase() } else list.sortedByDescending { it.bezeichnung.lowercase() }
|
||||||
|
"altersklasseCode" -> if (dir == SortDirection.ASC) list.sortedBy { it.altersklasseCode.lowercase() } else list.sortedByDescending { it.altersklasseCode.lowercase() }
|
||||||
|
else -> list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val total = list.size.toLong()
|
||||||
|
val isUnpaged = unpaged || size == -1
|
||||||
|
val pageContent: List<AltersklasseDefinition>
|
||||||
|
val effPage: Int
|
||||||
|
val effSize: Int
|
||||||
|
val totalPages: Int
|
||||||
|
val hasNext: Boolean
|
||||||
|
val hasPrevious: Boolean
|
||||||
|
|
||||||
|
if (isUnpaged) {
|
||||||
|
pageContent = list
|
||||||
|
effPage = 0
|
||||||
|
effSize = list.size
|
||||||
|
totalPages = 1
|
||||||
|
hasNext = false
|
||||||
|
hasPrevious = false
|
||||||
|
} else {
|
||||||
|
val fromIndex = (page * size).coerceAtLeast(0)
|
||||||
|
val toIndex = min(fromIndex + size, list.size)
|
||||||
|
pageContent = if (fromIndex in 0..list.size) list.subList(fromIndex, toIndex) else emptyList()
|
||||||
|
totalPages = if (size > 0) ((total + size - 1) / size).toInt() else 1
|
||||||
|
hasNext = page + 1 < totalPages
|
||||||
|
hasPrevious = page > 0
|
||||||
|
effPage = page
|
||||||
|
effSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
val dtoPage = pageContent.map { it.toDto() }
|
||||||
|
val paged = PagedResponse.create(
|
||||||
|
content = dtoPage,
|
||||||
|
page = effPage,
|
||||||
|
size = effSize,
|
||||||
|
totalElements = total,
|
||||||
|
totalPages = totalPages,
|
||||||
|
hasNext = hasNext,
|
||||||
|
hasPrevious = hasPrevious
|
||||||
|
)
|
||||||
|
call.respond(ApiResponse.success(paged))
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/{id}") {
|
get("/{id}") {
|
||||||
|
|
@ -92,13 +184,13 @@ class AltersklasseController(
|
||||||
}
|
}
|
||||||
?: return@get call.respond(
|
?: return@get call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
ApiResponse.error<Unit>(ErrorCodes.INVALID_ID, "Missing or invalid ID")
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = getAltersklasseUseCase.getById(id)
|
val response = getAltersklasseUseCase.getById(id)
|
||||||
response.altersklasse?.let {
|
response.altersklasse?.let {
|
||||||
call.respond(ApiResponse.success(it.toDto()))
|
call.respond(ApiResponse.success(it.toDto()))
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("NOT_FOUND", "Age class not found"))
|
} ?: call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>(ErrorCodes.NOT_FOUND, "Age class not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
|
@ -150,7 +242,7 @@ class AltersklasseController(
|
||||||
}
|
}
|
||||||
?: return@put call.respond(
|
?: return@put call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
ApiResponse.error<Unit>(ErrorCodes.INVALID_ID, "Missing or invalid ID")
|
||||||
)
|
)
|
||||||
|
|
||||||
val dto = call.receive<UpdateAltersklasseDto>()
|
val dto = call.receive<UpdateAltersklasseDto>()
|
||||||
|
|
@ -186,7 +278,7 @@ class AltersklasseController(
|
||||||
} else {
|
} else {
|
||||||
call.respond(
|
call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("UPDATE_FAILED", response.errors.joinToString())
|
ApiResponse.error<Unit>(ErrorCodes.UPDATE_FAILED, response.errors.joinToString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +294,7 @@ class AltersklasseController(
|
||||||
}
|
}
|
||||||
?: return@delete call.respond(
|
?: return@delete call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
ApiResponse.error<Unit>(ErrorCodes.INVALID_ID, "Missing or invalid ID")
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = createAltersklasseUseCase.deleteAltersklasse(id)
|
val response = createAltersklasseUseCase.deleteAltersklasse(id)
|
||||||
|
|
@ -211,7 +303,7 @@ class AltersklasseController(
|
||||||
} else {
|
} else {
|
||||||
call.respond(
|
call.respond(
|
||||||
HttpStatusCode.NotFound,
|
HttpStatusCode.NotFound,
|
||||||
ApiResponse.error<Unit>("DELETE_FAILED", response.errors.joinToString())
|
ApiResponse.error<Unit>(ErrorCodes.DELETE_FAILED, response.errors.joinToString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +321,7 @@ class AltersklasseController(
|
||||||
geschlechtFilter = geschlechtFilter?.toString(),
|
geschlechtFilter = geschlechtFilter?.toString(),
|
||||||
oetoRegelReferenzId = oetoRegelReferenzId?.toString(),
|
oetoRegelReferenzId = oetoRegelReferenzId?.toString(),
|
||||||
istAktiv = istAktiv,
|
istAktiv = istAktiv,
|
||||||
createdAt = createdAt.toString(),
|
createdAt = createdAt,
|
||||||
updatedAt = updatedAt.toString()
|
updatedAt = updatedAt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
package at.mocode.masterdata.api.rest
|
package at.mocode.masterdata.api.rest
|
||||||
|
|
||||||
import at.mocode.core.domain.model.ApiResponse
|
import at.mocode.core.domain.model.ApiResponse
|
||||||
|
import at.mocode.core.domain.model.ErrorCodes
|
||||||
|
import at.mocode.core.domain.model.PagedResponse
|
||||||
|
import at.mocode.core.domain.model.SortDirection
|
||||||
import at.mocode.masterdata.application.usecase.CreateBundeslandUseCase
|
import at.mocode.masterdata.application.usecase.CreateBundeslandUseCase
|
||||||
import at.mocode.masterdata.application.usecase.GetBundeslandUseCase
|
import at.mocode.masterdata.application.usecase.GetBundeslandUseCase
|
||||||
import at.mocode.masterdata.domain.model.BundeslandDefinition
|
import at.mocode.masterdata.domain.model.BundeslandDefinition
|
||||||
|
|
@ -11,6 +14,9 @@ import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import at.mocode.core.domain.serialization.InstantSerializer
|
||||||
|
import kotlin.time.Instant
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API controller for federal state (Bundesland) management.
|
* REST API controller for federal state (Bundesland) management.
|
||||||
|
|
@ -31,8 +37,10 @@ class BundeslandController(
|
||||||
val wappenUrl: String? = null,
|
val wappenUrl: String? = null,
|
||||||
val istAktiv: Boolean = true,
|
val istAktiv: Boolean = true,
|
||||||
val sortierReihenfolge: Int? = null,
|
val sortierReihenfolge: Int? = null,
|
||||||
val createdAt: String,
|
@Serializable(with = InstantSerializer::class)
|
||||||
val updatedAt: String
|
val createdAt: Instant,
|
||||||
|
@Serializable(with = InstantSerializer::class)
|
||||||
|
val updatedAt: Instant
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -58,14 +66,97 @@ class BundeslandController(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = if (landId != null) {
|
val search = call.request.queryParameters["search"]?.trim().takeUnless { it.isNullOrBlank() }
|
||||||
getBundeslandUseCase.getByCountry(landId)
|
val activeParam = call.request.queryParameters["active"]?.lowercase()
|
||||||
} else {
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
getBundeslandUseCase.getAllActive()
|
val sizeParam = call.request.queryParameters["size"]?.toIntOrNull()
|
||||||
|
val unpaged = call.request.queryParameters["unpaged"]?.let {
|
||||||
|
when (it.lowercase()) { "true", "1", "yes" -> true; else -> false }
|
||||||
|
} ?: false
|
||||||
|
val size = sizeParam ?: 20
|
||||||
|
val sortParams = call.request.queryParameters.getAll("sort")?.mapNotNull { token ->
|
||||||
|
val parts = token.split(",")
|
||||||
|
if (parts.isEmpty()) null else {
|
||||||
|
val field = parts[0].trim()
|
||||||
|
val dir = if (parts.getOrNull(1)?.trim()?.equals("desc", ignoreCase = true) == true) SortDirection.DESC else SortDirection.ASC
|
||||||
|
field to dir
|
||||||
|
}
|
||||||
|
} ?: listOf("sortierReihenfolge" to SortDirection.ASC, "name" to SortDirection.ASC)
|
||||||
|
|
||||||
|
val baseList = when (activeParam) {
|
||||||
|
null, "true" -> if (landId != null) getBundeslandUseCase.getByCountry(landId).bundeslaender.filter { it.istAktiv } else getBundeslandUseCase.getAllActive().bundeslaender
|
||||||
|
"false", "all" -> if (landId != null) getBundeslandUseCase.getByCountry(landId).bundeslaender else getBundeslandUseCase.getAllActive().bundeslaender
|
||||||
|
else -> if (landId != null) getBundeslandUseCase.getByCountry(landId).bundeslaender else getBundeslandUseCase.getAllActive().bundeslaender
|
||||||
}
|
}
|
||||||
|
|
||||||
val dtos = response.bundeslaender.map { it.toDto() }
|
val filtered = baseList.asSequence()
|
||||||
call.respond(ApiResponse.success(dtos))
|
.filter { item ->
|
||||||
|
when (activeParam) {
|
||||||
|
"true", null -> item.istAktiv
|
||||||
|
"false" -> !item.istAktiv
|
||||||
|
"all" -> true
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { item ->
|
||||||
|
val q = search
|
||||||
|
if (q.isNullOrBlank()) return@filter true
|
||||||
|
val s = q.lowercase()
|
||||||
|
item.name.lowercase().contains(s) ||
|
||||||
|
(item.kuerzel?.lowercase()?.contains(s) == true) ||
|
||||||
|
(item.oepsCode?.lowercase()?.contains(s) == true) ||
|
||||||
|
(item.iso3166_2_Code?.lowercase()?.contains(s) == true)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
var list = filtered
|
||||||
|
sortParams.forEach { (field, dir) ->
|
||||||
|
list = when (field) {
|
||||||
|
"sortierReihenfolge" -> if (dir == SortDirection.ASC) list.sortedBy { it.sortierReihenfolge ?: Int.MAX_VALUE } else list.sortedByDescending { it.sortierReihenfolge ?: Int.MIN_VALUE }
|
||||||
|
"name" -> if (dir == SortDirection.ASC) list.sortedBy { it.name.lowercase() } else list.sortedByDescending { it.name.lowercase() }
|
||||||
|
"kuerzel" -> if (dir == SortDirection.ASC) list.sortedBy { it.kuerzel?.lowercase() } else list.sortedByDescending { it.kuerzel?.lowercase() }
|
||||||
|
else -> list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val total = list.size.toLong()
|
||||||
|
val isUnpaged = unpaged || size == -1
|
||||||
|
val pageContent: List<BundeslandDefinition>
|
||||||
|
val effPage: Int
|
||||||
|
val effSize: Int
|
||||||
|
val totalPages: Int
|
||||||
|
val hasNext: Boolean
|
||||||
|
val hasPrevious: Boolean
|
||||||
|
|
||||||
|
if (isUnpaged) {
|
||||||
|
pageContent = list
|
||||||
|
effPage = 0
|
||||||
|
effSize = list.size
|
||||||
|
totalPages = 1
|
||||||
|
hasNext = false
|
||||||
|
hasPrevious = false
|
||||||
|
} else {
|
||||||
|
val fromIndex = (page * size).coerceAtLeast(0)
|
||||||
|
val toIndex = min(fromIndex + size, list.size)
|
||||||
|
pageContent = if (fromIndex in 0..list.size) list.subList(fromIndex, toIndex) else emptyList()
|
||||||
|
totalPages = if (size > 0) ((total + size - 1) / size).toInt() else 1
|
||||||
|
hasNext = page + 1 < totalPages
|
||||||
|
hasPrevious = page > 0
|
||||||
|
effPage = page
|
||||||
|
effSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
val dtoPage = pageContent.map { it.toDto() }
|
||||||
|
val paged = PagedResponse.create(
|
||||||
|
content = dtoPage,
|
||||||
|
page = effPage,
|
||||||
|
size = effSize,
|
||||||
|
totalElements = total,
|
||||||
|
totalPages = totalPages,
|
||||||
|
hasNext = hasNext,
|
||||||
|
hasPrevious = hasPrevious
|
||||||
|
)
|
||||||
|
call.respond(ApiResponse.success(paged))
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/{id}") {
|
get("/{id}") {
|
||||||
|
|
@ -79,7 +170,7 @@ class BundeslandController(
|
||||||
}
|
}
|
||||||
?: return@get call.respond(
|
?: return@get call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
ApiResponse.error<Unit>(ErrorCodes.INVALID_ID, "Missing or invalid ID")
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = getBundeslandUseCase.getById(id)
|
val response = getBundeslandUseCase.getById(id)
|
||||||
|
|
@ -87,7 +178,7 @@ class BundeslandController(
|
||||||
call.respond(ApiResponse.success(it.toDto()))
|
call.respond(ApiResponse.success(it.toDto()))
|
||||||
} ?: call.respond(
|
} ?: call.respond(
|
||||||
HttpStatusCode.NotFound,
|
HttpStatusCode.NotFound,
|
||||||
ApiResponse.error<Unit>("NOT_FOUND", "Federal state not found")
|
ApiResponse.error<Unit>(ErrorCodes.NOT_FOUND, "Federal state not found")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,7 +189,7 @@ class BundeslandController(
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return@post call.respond(
|
return@post call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("INVALID_LAND_ID", "Invalid landId format")
|
ApiResponse.error<Unit>(ErrorCodes.INVALID_PARAMETER, "Invalid landId format")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +211,7 @@ class BundeslandController(
|
||||||
} else {
|
} else {
|
||||||
call.respond(
|
call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("CREATION_FAILED", response.errors.joinToString())
|
ApiResponse.error<Unit>(ErrorCodes.CREATION_FAILED, response.errors.joinToString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +228,7 @@ class BundeslandController(
|
||||||
wappenUrl = wappenUrl,
|
wappenUrl = wappenUrl,
|
||||||
istAktiv = istAktiv,
|
istAktiv = istAktiv,
|
||||||
sortierReihenfolge = sortierReihenfolge,
|
sortierReihenfolge = sortierReihenfolge,
|
||||||
createdAt = createdAt.toString(),
|
createdAt = createdAt,
|
||||||
updatedAt = updatedAt.toString()
|
updatedAt = updatedAt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,19 @@ package at.mocode.masterdata.api.rest
|
||||||
import at.mocode.core.domain.model.ApiResponse
|
import at.mocode.core.domain.model.ApiResponse
|
||||||
import at.mocode.core.domain.model.ErrorCodes
|
import at.mocode.core.domain.model.ErrorCodes
|
||||||
import at.mocode.core.domain.model.PagedResponse
|
import at.mocode.core.domain.model.PagedResponse
|
||||||
import at.mocode.core.domain.model.PageNumber
|
|
||||||
import at.mocode.core.domain.model.PageSize
|
|
||||||
import at.mocode.core.domain.model.SortDirection
|
import at.mocode.core.domain.model.SortDirection
|
||||||
|
import at.mocode.core.domain.serialization.InstantSerializer
|
||||||
import at.mocode.masterdata.application.usecase.CreateCountryUseCase
|
import at.mocode.masterdata.application.usecase.CreateCountryUseCase
|
||||||
import at.mocode.masterdata.application.usecase.GetCountryUseCase
|
import at.mocode.masterdata.application.usecase.GetCountryUseCase
|
||||||
import at.mocode.masterdata.domain.model.LandDefinition
|
import at.mocode.masterdata.domain.model.LandDefinition
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import at.mocode.core.domain.serialization.InstantSerializer
|
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API controller for country (Land) management.
|
* REST API controller for country (Land) management.
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
package at.mocode.masterdata.api.rest
|
package at.mocode.masterdata.api.rest
|
||||||
|
|
||||||
import at.mocode.core.domain.model.ApiResponse
|
import at.mocode.core.domain.model.ApiResponse
|
||||||
|
import at.mocode.core.domain.model.ErrorCodes
|
||||||
|
import at.mocode.core.domain.model.PagedResponse
|
||||||
|
import at.mocode.core.domain.model.SortDirection
|
||||||
import at.mocode.core.domain.model.PlatzTypE
|
import at.mocode.core.domain.model.PlatzTypE
|
||||||
import at.mocode.masterdata.application.usecase.CreatePlatzUseCase
|
import at.mocode.masterdata.application.usecase.CreatePlatzUseCase
|
||||||
import at.mocode.masterdata.application.usecase.GetPlatzUseCase
|
import at.mocode.masterdata.application.usecase.GetPlatzUseCase
|
||||||
|
|
@ -12,6 +15,9 @@ import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import at.mocode.core.domain.serialization.InstantSerializer
|
||||||
|
import kotlin.time.Instant
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST API controller for venue/arena (Platz) management.
|
* REST API controller for venue/arena (Platz) management.
|
||||||
|
|
@ -31,8 +37,10 @@ class PlatzController(
|
||||||
val typ: String,
|
val typ: String,
|
||||||
val istAktiv: Boolean = true,
|
val istAktiv: Boolean = true,
|
||||||
val sortierReihenfolge: Int? = null,
|
val sortierReihenfolge: Int? = null,
|
||||||
val createdAt: String,
|
@Serializable(with = InstantSerializer::class)
|
||||||
val updatedAt: String
|
val createdAt: Instant,
|
||||||
|
@Serializable(with = InstantSerializer::class)
|
||||||
|
val updatedAt: Instant
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -58,12 +66,93 @@ class PlatzController(
|
||||||
}
|
}
|
||||||
?: return@get call.respond(
|
?: return@get call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("MISSING_TURNIER_ID", "Query parameter turnierId is required")
|
ApiResponse.error<Unit>(ErrorCodes.MISSING_PARAMETER, "Query parameter turnierId is required")
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = getPlatzUseCase.getByTournament(turnierId)
|
val search = call.request.queryParameters["search"]?.trim().takeUnless { it.isNullOrBlank() }
|
||||||
val dtos = response.plaetze.map { it.toDto() }
|
val activeParam = call.request.queryParameters["active"]?.lowercase()
|
||||||
call.respond(ApiResponse.success(dtos))
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
|
val sizeParam = call.request.queryParameters["size"]?.toIntOrNull()
|
||||||
|
val unpaged = call.request.queryParameters["unpaged"]?.let { v ->
|
||||||
|
when (v.lowercase()) { "true", "1", "yes" -> true; else -> false }
|
||||||
|
} ?: false
|
||||||
|
val size = sizeParam ?: 20
|
||||||
|
val sortParams = call.request.queryParameters.getAll("sort")?.mapNotNull { token ->
|
||||||
|
val parts = token.split(",")
|
||||||
|
if (parts.isEmpty()) null else {
|
||||||
|
val field = parts[0].trim()
|
||||||
|
val dir = if (parts.getOrNull(1)?.trim()?.equals("desc", ignoreCase = true) == true) SortDirection.DESC else SortDirection.ASC
|
||||||
|
field to dir
|
||||||
|
}
|
||||||
|
} ?: listOf("sortierReihenfolge" to SortDirection.ASC, "name" to SortDirection.ASC)
|
||||||
|
|
||||||
|
val baseList = getPlatzUseCase.getByTournament(turnierId).plaetze
|
||||||
|
val filtered = baseList.asSequence()
|
||||||
|
.filter { item ->
|
||||||
|
when (activeParam) {
|
||||||
|
"true", null -> item.istAktiv
|
||||||
|
"false" -> !item.istAktiv
|
||||||
|
"all" -> true
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { item ->
|
||||||
|
val q = search
|
||||||
|
if (q.isNullOrBlank()) return@filter true
|
||||||
|
val s = q.lowercase()
|
||||||
|
item.name.lowercase().contains(s) ||
|
||||||
|
(item.dimension?.lowercase()?.contains(s) == true) ||
|
||||||
|
(item.boden?.lowercase()?.contains(s) == true)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
var list = filtered
|
||||||
|
sortParams.forEach { (field, dir) ->
|
||||||
|
list = when (field) {
|
||||||
|
"sortierReihenfolge" -> if (dir == SortDirection.ASC) list.sortedBy { it.sortierReihenfolge ?: Int.MAX_VALUE } else list.sortedByDescending { it.sortierReihenfolge ?: Int.MIN_VALUE }
|
||||||
|
"name" -> if (dir == SortDirection.ASC) list.sortedBy { it.name.lowercase() } else list.sortedByDescending { it.name.lowercase() }
|
||||||
|
else -> list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val total = list.size.toLong()
|
||||||
|
val isUnpaged = unpaged || size == -1
|
||||||
|
val pageContent: List<Platz>
|
||||||
|
val effPage: Int
|
||||||
|
val effSize: Int
|
||||||
|
val totalPages: Int
|
||||||
|
val hasNext: Boolean
|
||||||
|
val hasPrevious: Boolean
|
||||||
|
|
||||||
|
if (isUnpaged) {
|
||||||
|
pageContent = list
|
||||||
|
effPage = 0
|
||||||
|
effSize = list.size
|
||||||
|
totalPages = 1
|
||||||
|
hasNext = false
|
||||||
|
hasPrevious = false
|
||||||
|
} else {
|
||||||
|
val fromIndex = (page * size).coerceAtLeast(0)
|
||||||
|
val toIndex = min(fromIndex + size, list.size)
|
||||||
|
pageContent = if (fromIndex in 0..list.size) list.subList(fromIndex, toIndex) else emptyList()
|
||||||
|
totalPages = if (size > 0) ((total + size - 1) / size).toInt() else 1
|
||||||
|
hasNext = page + 1 < totalPages
|
||||||
|
hasPrevious = page > 0
|
||||||
|
effPage = page
|
||||||
|
effSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
val dtoPage = pageContent.map { it.toDto() }
|
||||||
|
val paged = PagedResponse.create(
|
||||||
|
content = dtoPage,
|
||||||
|
page = effPage,
|
||||||
|
size = effSize,
|
||||||
|
totalElements = total,
|
||||||
|
totalPages = totalPages,
|
||||||
|
hasNext = hasNext,
|
||||||
|
hasPrevious = hasPrevious
|
||||||
|
)
|
||||||
|
call.respond(ApiResponse.success(paged))
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/{id}") {
|
get("/{id}") {
|
||||||
|
|
@ -77,13 +166,13 @@ class PlatzController(
|
||||||
}
|
}
|
||||||
?: return@get call.respond(
|
?: return@get call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
ApiResponse.error<Unit>(ErrorCodes.INVALID_ID, "Missing or invalid ID")
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = getPlatzUseCase.getById(id)
|
val response = getPlatzUseCase.getById(id)
|
||||||
response.platz?.let {
|
response.platz?.let {
|
||||||
call.respond(ApiResponse.success(it.toDto()))
|
call.respond(ApiResponse.success(it.toDto()))
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("NOT_FOUND", "Venue not found"))
|
} ?: call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>(ErrorCodes.NOT_FOUND, "Venue not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
|
@ -93,7 +182,7 @@ class PlatzController(
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return@post call.respond(
|
return@post call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("INVALID_TURNIER_ID", "Invalid turnierId format")
|
ApiResponse.error<Unit>(ErrorCodes.INVALID_PARAMETER, "Invalid turnierId format")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +207,7 @@ class PlatzController(
|
||||||
} else {
|
} else {
|
||||||
call.respond(
|
call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ApiResponse.error<Unit>("CREATION_FAILED", response.errors.joinToString())
|
ApiResponse.error<Unit>(ErrorCodes.CREATION_FAILED, response.errors.joinToString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +223,7 @@ class PlatzController(
|
||||||
typ = typ.name,
|
typ = typ.name,
|
||||||
istAktiv = istAktiv,
|
istAktiv = istAktiv,
|
||||||
sortierReihenfolge = sortierReihenfolge,
|
sortierReihenfolge = sortierReihenfolge,
|
||||||
createdAt = createdAt.toString(),
|
createdAt = createdAt,
|
||||||
updatedAt = updatedAt.toString()
|
updatedAt = updatedAt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user