forked from SteamWar/SteamWar
a089d42d9a
Signed-off-by: Chaoscaot <max@maxsp.de>
273 lines
9.7 KiB
Kotlin
273 lines
9.7 KiB
Kotlin
/*
|
|
* This file is a part of the SteamWar software.
|
|
*
|
|
* Copyright (C) 2025 SteamWar.de-Serverteam
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package de.steamwar.routes
|
|
|
|
import de.steamwar.ResponseError
|
|
import de.steamwar.plugins.SWPermissionCheck
|
|
import de.steamwar.sql.*
|
|
import de.steamwar.sql.EventGroup.EventGroupType
|
|
import de.steamwar.sql.EventRelation.FromType
|
|
import io.ktor.http.*
|
|
import io.ktor.server.application.*
|
|
import io.ktor.server.request.*
|
|
import io.ktor.server.response.*
|
|
import io.ktor.server.routing.*
|
|
import kotlinx.serialization.Serializable
|
|
import java.lang.StringBuilder
|
|
import java.sql.Timestamp
|
|
import java.time.Instant
|
|
import java.util.*
|
|
|
|
@Serializable
|
|
data class ShortEvent(val id: Int, val name: String, val start: Long, val end: Long) {
|
|
constructor(event: Event) : this(event.eventID, event.eventName, event.start.time, event.end.time)
|
|
}
|
|
|
|
@Serializable
|
|
data class ResponseGroups(
|
|
val id: Int,
|
|
val name: String,
|
|
val pointsPerWin: Int,
|
|
val pointsPerLoss: Int,
|
|
val pointsPerDraw: Int,
|
|
val type: EventGroupType,
|
|
val points: Map<Int, Int>
|
|
) {
|
|
constructor(group: EventGroup, short: Boolean = false) : this(
|
|
group.getId(),
|
|
group.name,
|
|
group.pointsPerWin,
|
|
group.pointsPerLoss,
|
|
group.pointsPerDraw,
|
|
group.type,
|
|
if (short) mapOf() else group.calculatePoints().mapKeys { it.key.teamId })
|
|
}
|
|
|
|
@Serializable
|
|
data class ResponseRelation(
|
|
val id: Int,
|
|
val fight: Int,
|
|
val team: EventRelation.FightTeam,
|
|
val type: FromType,
|
|
val fromFight: ResponseEventFight? = null,
|
|
val fromGroup: ResponseGroups? = null,
|
|
val fromPlace: Int
|
|
) {
|
|
constructor(relation: EventRelation) : this(
|
|
relation.getId(),
|
|
relation.fightId,
|
|
relation.fightTeam,
|
|
relation.fromType,
|
|
relation.fromFight?.let { ResponseEventFight(it) },
|
|
relation.fromGroup?.let { ResponseGroups(it) },
|
|
relation.fromPlace
|
|
)
|
|
}
|
|
|
|
@Serializable
|
|
data class ResponseEvent(
|
|
val id: Int,
|
|
val name: String,
|
|
val deadline: Long,
|
|
val start: Long,
|
|
val end: Long,
|
|
val maxTeamMembers: Int,
|
|
val schemType: String?,
|
|
val publicSchemsOnly: Boolean,
|
|
) {
|
|
constructor(event: Event) : this(
|
|
event.eventID,
|
|
event.eventName,
|
|
event.deadline.time,
|
|
event.start.time,
|
|
event.end.time,
|
|
event.maximumTeamMembers,
|
|
event.schematicType?.toDB(),
|
|
event.publicSchemsOnly(),
|
|
)
|
|
}
|
|
|
|
@Serializable
|
|
data class ExtendedResponseEvent(
|
|
val event: ResponseEvent,
|
|
val teams: List<ResponseTeam>,
|
|
val groups: List<ResponseGroups>,
|
|
val fights: List<ResponseEventFight>,
|
|
val referees: List<ResponseUser>,
|
|
val relations: List<ResponseRelation>
|
|
) {
|
|
constructor(event: Event) : this(
|
|
ResponseEvent(event),
|
|
TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) },
|
|
EventGroup.get(event).map { ResponseGroups(it) },
|
|
EventFight.getEvent(event.eventID).map { ResponseEventFight(it) },
|
|
Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) },
|
|
EventRelation.get(event).map { ResponseRelation(it) }
|
|
)
|
|
}
|
|
|
|
@Serializable
|
|
data class CreateEvent(val name: String, val start: Long, val end: Long)
|
|
|
|
@Serializable
|
|
data class UpdateEvent(
|
|
val name: String? = null,
|
|
val deadline: Long? = null,
|
|
val start: Long? = null,
|
|
val end: Long? = null,
|
|
val maxTeamMembers: Int? = null,
|
|
val schemType: String? = null,
|
|
val publicSchemsOnly: Boolean? = null,
|
|
val addReferee: Set<String>? = null,
|
|
val removeReferee: Set<String>? = null,
|
|
)
|
|
|
|
fun Route.configureEventsRoute() {
|
|
route("/events") {
|
|
install(SWPermissionCheck) {
|
|
allowMethod(HttpMethod.Get)
|
|
permission = UserPerm.MODERATION
|
|
}
|
|
get {
|
|
call.respond(Event.getAll().map { ShortEvent(it) })
|
|
}
|
|
post {
|
|
val createEvent = call.receiveNullable<CreateEvent>()
|
|
if (createEvent == null) {
|
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid body"))
|
|
return@post
|
|
}
|
|
val event = Event.create(
|
|
createEvent.name,
|
|
Timestamp.from(Instant.ofEpochMilli(createEvent.start)),
|
|
Timestamp.from(Instant.ofEpochMilli(createEvent.end))
|
|
)
|
|
call.respond(HttpStatusCode.Created, ResponseEvent(event))
|
|
}
|
|
route("/{id}") {
|
|
get {
|
|
val event = call.receiveEvent() ?: return@get
|
|
call.respond(
|
|
ExtendedResponseEvent(event)
|
|
)
|
|
}
|
|
get("/csv") {
|
|
val event = call.receiveEvent() ?: return@get
|
|
|
|
val fights = EventFight.getEvent(event.eventID)
|
|
val csv = StringBuilder();
|
|
csv.append(arrayOf("Start", "BlueTeam", "RedTeam", "WinnerTeam", "Group").joinToString(","))
|
|
fights.forEach {
|
|
csv.appendLine()
|
|
val blue = Team.byId(it.teamBlue)
|
|
val red = Team.byId(it.teamRed)
|
|
val winner = when (it.ergebnis) {
|
|
1 -> blue.teamName
|
|
2 -> red.teamName
|
|
3 -> "Tie"
|
|
else -> "Unknown"
|
|
}
|
|
csv.append(
|
|
arrayOf(
|
|
it.startTime.toString(),
|
|
Team.byId(it.teamBlue).teamName,
|
|
Team.byId(it.teamRed).teamName,
|
|
winner,
|
|
it.group.map { it.name }.orElse("Ungrouped")
|
|
).joinToString(",")
|
|
)
|
|
}
|
|
call.response.header("Content-Disposition", "attachment; filename=\"${event.eventName}.csv\"")
|
|
call.response.header("Content-Type", "text/csv")
|
|
call.response.header("Content-Transfer-Encoding", "binary")
|
|
call.response.header("Pragma", "no-cache")
|
|
call.respondText(csv.toString())
|
|
}
|
|
put {
|
|
val event = call.receiveEvent() ?: return@put
|
|
|
|
val updateEvent = call.receiveNullable<UpdateEvent>()
|
|
if (updateEvent == null) {
|
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid body"))
|
|
return@put
|
|
}
|
|
val eventName = updateEvent.name ?: event.eventName
|
|
val deadline = updateEvent.deadline?.let { Timestamp.from(Instant.ofEpochMilli(it)) } ?: event.deadline
|
|
val start = updateEvent.start?.let { Timestamp.from(Instant.ofEpochMilli(it)) } ?: event.start
|
|
val end = updateEvent.end?.let { Timestamp.from(Instant.ofEpochMilli(it)) } ?: event.end
|
|
val maxTeamMembers = updateEvent.maxTeamMembers ?: event.maximumTeamMembers
|
|
|
|
val schemType =
|
|
if (updateEvent.schemType == "null") null else updateEvent.schemType?.let { SchematicType.fromDB(it) }
|
|
?: event.schematicType
|
|
val publicSchemsOnly = updateEvent.publicSchemsOnly ?: event.publicSchemsOnly()
|
|
|
|
if (updateEvent.addReferee != null) {
|
|
updateEvent.addReferee.forEach {
|
|
Referee.add(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId())
|
|
}
|
|
}
|
|
|
|
if (updateEvent.removeReferee != null) {
|
|
updateEvent.removeReferee.forEach {
|
|
Referee.remove(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId())
|
|
}
|
|
}
|
|
event.update(eventName, deadline, start, end, schemType, maxTeamMembers, publicSchemsOnly)
|
|
call.respond(ResponseEvent(Event.byId(event.eventID)!!))
|
|
}
|
|
delete {
|
|
val id = call.parameters["id"]?.toIntOrNull()
|
|
if (id == null) {
|
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid ID"))
|
|
return@delete
|
|
}
|
|
val event = Event.byId(id)
|
|
if (event == null) {
|
|
call.respond(HttpStatusCode.NotFound, ResponseError("Event not found"))
|
|
return@delete
|
|
}
|
|
event.delete()
|
|
call.respond(HttpStatusCode.NoContent)
|
|
}
|
|
configureEventFightRoutes()
|
|
configureEventTeams()
|
|
configureEventGroups()
|
|
configureEventRelations()
|
|
configureEventRefereesRouting()
|
|
}
|
|
}
|
|
}
|
|
|
|
suspend fun ApplicationCall.receiveEvent(fieldName: String = "id"): Event? {
|
|
val eventId = parameters[fieldName]?.toIntOrNull()
|
|
if (eventId == null) {
|
|
respond(HttpStatusCode.BadRequest, ResponseError("Invalid event ID"))
|
|
return null
|
|
}
|
|
|
|
val event = Event.byId(eventId)
|
|
if (event == null) {
|
|
respond(HttpStatusCode.NotFound, ResponseError("Event not found"))
|
|
return null
|
|
}
|
|
|
|
return event
|
|
} |