Add Backend to Monorepo

This commit is contained in:
2024-08-18 11:15:54 +02:00
parent b8e50dc139
commit fd7fe8c305
37 changed files with 2703 additions and 26 deletions
@@ -0,0 +1,252 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<ResponseUser>,
) {
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<ResponseTeam>,
val fights: List<ResponseEventFight>
)
@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<Int>? = null,
val removeReferee: Set<Int>? = 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 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 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
}
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 id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid ID"))
return@put
}
val event = Event.get(id)
if (event == null) {
call.respond(HttpStatusCode.NotFound, ResponseError("Event not found"))
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, 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)
}
}
}
}