/* * This file is a part of the SteamWar software. * * Copyright (C) 2024 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 . */ package de.steamwar.routes import de.steamwar.ResponseError import de.steamwar.data.Groups import de.steamwar.plugins.SWPermissionCheck import de.steamwar.sql.* 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 @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 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, val referees: List, ) { constructor(event: Event) : this( event.eventID, event.eventName, event.deadline.time, event.start.time, event.end.time, event.maximumTeamMembers, event.schematicType?.toDB(), event.publicSchemsOnly(), Referee.get(event.eventID).map { ResponseUser(SteamwarUser.get(it)) } ) } @Serializable data class ExtendedResponseEvent( val event: ResponseEvent, val teams: List, val fights: List ) @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? = null, val removeReferee: Set? = 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() 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 id = call.parameters["id"]?.toIntOrNull() if (id == null) { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid ID")) return@get } val event = Event.get(id) if (event == null) { call.respond(HttpStatusCode.NotFound, ResponseError("Event not found")) return@get } call.respond( ExtendedResponseEvent( ResponseEvent(event), TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) }, EventFight.getEvent(event.eventID).map { ResponseEventFight(it) }) ) } get("/teams") { val id = call.parameters["id"]?.toIntOrNull() if (id == null) { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid ID")) return@get } val event = Event.get(id) if (event == null) { call.respond(HttpStatusCode.NotFound, ResponseError("Event not found")) return@get } call.respond(TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) }) } get("/fights") { val id = call.parameters["id"]?.toIntOrNull() if (id == null) { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid ID")) return@get } val event = Event.get(id) if (event == null) { call.respond(HttpStatusCode.NotFound, ResponseError("Event not found")) return@get } call.respond(EventFight.getEvent(event.eventID).map { ResponseEventFight(it) }) } 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.get(it.teamBlue) val red = Team.get(it.teamRed) val winner = when(it.ergebnis) { 1 -> blue.teamName 2 -> red.teamName 3 -> "Tie" else -> "Unknown" } csv.append( arrayOf( it.startTime.toString(), Team.get(it.teamBlue).teamName, Team.get(it.teamRed).teamName, winner, Groups.getGroup(it.fightID)?.name ?: "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() 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, it) } } if (updateEvent.removeReferee != null) { updateEvent.removeReferee.forEach { Referee.remove(event.eventID, it) } } event.update(eventName, deadline, start, end, schemType, maxTeamMembers, publicSchemsOnly) call.respond(ResponseEvent(event)) } delete { val id = call.parameters["id"]?.toIntOrNull() if (id == null) { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid ID")) return@delete } val event = Event.get(id) if (event == null) { call.respond(HttpStatusCode.NotFound, ResponseError("Event not found")) return@delete } event.delete() call.respond(HttpStatusCode.NoContent) } } } } 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.get(eventId) if (event == null) { respond(HttpStatusCode.NotFound, ResponseError("Event not found")) return null } return event }