diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java index 9aa57510..4143c596 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java @@ -37,6 +37,8 @@ public class EventFight implements Comparable { private static final Table table = new Table<>(EventFight.class); private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final SelectStatement byGroup = new SelectStatement(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime ASC"); + private static final SelectStatement byGroupLast = new SelectStatement(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime DESC LIMIT 1"); private static final SelectStatement allComing = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE StartTime > now() ORDER BY StartTime ASC"); private static final SelectStatement event = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE EventID = ? ORDER BY StartTime ASC"); private static final SelectStatement activeFights = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE Fight IS NOT NULL AND StartTime < now() AND DATEDIFF(StartTime, now()) < 0"); @@ -46,6 +48,7 @@ public class EventFight implements Comparable { private static final Statement create = table.insertFields(true, "eventID", "startTime", "spielmodus", "map", "teamBlue", "teamRed", "spectatePort"); private static final Statement update = table.update(Table.PRIMARY, "startTime", "spielModus", "map", "teamBlue", "teamRed", "spectatePort"); + private static final Statement setGroup = table.update(Table.PRIMARY, "GroupID"); private static final Statement delete = table.delete(Table.PRIMARY); @Getter @@ -55,6 +58,14 @@ public class EventFight implements Comparable { return byId.select(fightID); } + public static List get(EventGroup group) { + return byGroup.listSelect(group.getId()); + } + + public static Optional getLast(EventGroup group) { + return Optional.ofNullable(byGroupLast.select(group.getId())); + } + public static void loadAllComingFights() { fights.clear(); fights.addAll(allComing.listSelect()); @@ -89,6 +100,10 @@ public class EventFight implements Comparable { private final int fightID; @Getter @Setter + @Field(nullable = true, def = "null") + private Integer groupId; + @Getter + @Setter @Field private Timestamp startTime; @Getter @@ -112,11 +127,35 @@ public class EventFight implements Comparable { @Field(nullable = true) private Integer spectatePort; @Getter + @Setter + @Field(def = "1") + private int bestOf; + @Getter @Field(def = "0") private int ergebnis; @Field(nullable = true) private int fight; + public Optional getGroup() { + return Optional.ofNullable(groupId).flatMap(EventGroup::get); + } + + public Optional getWinner() { + if(ergebnis == 0) + return Optional.empty(); + return Optional.ofNullable(ergebnis == 1 ? Team.get(teamBlue) : Team.get(teamRed)); + } + + public Optional getLosser() { + if(ergebnis == 0) + return Optional.empty(); + return Optional.ofNullable(ergebnis == 1 ? Team.get(teamRed) : Team.get(teamBlue)); + } + + public List getDependents() { + return EventRelation.getFightRelations(this); + } + public void setErgebnis(int winner) { this.ergebnis = winner; setResult.update(winner, fightID); @@ -128,6 +167,11 @@ public class EventFight implements Comparable { setFight.update(fight, fightID); } + public void setGroup(Integer group) { + setGroup.update(group, fightID); + this.groupId = group; + } + public boolean hasFinished() { return fight != 0 || ergebnis != 0; } diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java new file mode 100644 index 00000000..68934765 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java @@ -0,0 +1,173 @@ +/* + * 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 . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Getter +@Setter +public class EventGroup { + static { + SqlTypeMapper.ordinalEnumMapper(EventGroupType.class); + } + + private static final Table table = new Table<>(EventGroup.class); + + private static final SelectStatement get = table.select(Table.PRIMARY); + private static final SelectStatement byEvent = new SelectStatement<>(table, "SELECT * FROM EventGroup WHERE EventID = ?"); + + private static final Statement insert = table.insertFields(true, "EventID", "Name", "Type"); + private static final Statement update = table.update(Table.PRIMARY, "Name", "Type", "PointsPerWin", "PointsPerLoss", "PointsPerDraw"); + private static final Statement delete = table.delete(Table.PRIMARY); + + public static List get(Event eventID) { + return byEvent.listSelect(eventID.getEventID()); + } + + public static EventGroup create(Event event, String name, EventGroupType type) { + int key = insert.insertGetKey(event.getEventID(), name, type); + return EventGroup.get(key).get(); + } + + public static Optional get(int id) { + return Optional.ofNullable(get.select(id)); + } + + @Field(keys = Table.PRIMARY) + private final int id; + + @Field(keys = "EVENT_NAME") + private int eventID; + + @Field(keys = "EVENT_NAME") + private String name; + + @Field + private EventGroupType type; + + @Field + private int pointsPerWin; + + @Field + private int pointsPerLoss; + + @Field + private int pointsPerDraw; + + public EventGroup(int id, int eventID, String name, EventGroupType type, int pointsPerWin, int pointsPerLoss, int pointsPerDraw) { + this.id = id; + this.eventID = eventID; + this.name = name; + this.type = type; + this.pointsPerWin = pointsPerWin; + this.pointsPerLoss = pointsPerLoss; + this.pointsPerDraw = pointsPerDraw; + } + + private Map points; + + public List getFights() { + return EventFight.get(this); + } + + public Set getTeamsId() { + return getFights().stream().flatMap(fight -> Stream.of(fight.getTeamBlue(), fight.getTeamRed())) + .collect(Collectors.toSet()); + } + + public Set getTeams() { + return getTeamsId().stream().map(Team::get).collect(Collectors.toSet()); + } + + public Optional getLastFight() { + return EventFight.getLast(this); + } + + public List getDependents() { + return EventRelation.getGroupRelations(this); + } + + public Map calculatePoints() { + if (points == null) { + Map p = getTeamsId().stream().collect(Collectors.toMap(team -> team, team -> 0)); + + for (EventFight fight : getFights()) { + int blueTeamAdd = 0; + int redTeamAdd = 0; + + if (!fight.hasFinished()) { + continue; + } + + switch (fight.getErgebnis()) { + case 1: + blueTeamAdd += pointsPerWin; + redTeamAdd += pointsPerLoss; + break; + case 2: + blueTeamAdd += pointsPerLoss; + redTeamAdd += pointsPerWin; + break; + case 0: + if (fight.getFightID() != 0) { + blueTeamAdd += pointsPerDraw; + redTeamAdd += pointsPerDraw; + } + break; + } + + p.put(fight.getTeamBlue(), p.get(fight.getTeamBlue()) + blueTeamAdd); + p.put(fight.getTeamRed(), p.get(fight.getTeamRed()) + redTeamAdd); + } + + points = p.entrySet().stream().collect(Collectors.toMap(integerIntegerEntry -> Team.get(integerIntegerEntry.getKey()), Map.Entry::getValue)); + } + + return points; + } + + public void update(String name, EventGroupType type, int pointsPerWin, int pointsPerLoss, int pointsPerDraw) { + update.update(name, type, pointsPerWin, pointsPerLoss, pointsPerDraw, id); + this.name = name; + this.type = type; + this.pointsPerWin = pointsPerWin; + this.pointsPerLoss = pointsPerLoss; + this.pointsPerDraw = pointsPerDraw; + } + + public boolean needsTieBreak() { + return calculatePoints().values().stream().sorted().limit(2).distinct().count() < 2; + } + + public void delete() { + delete.update(id); + } + + public static enum EventGroupType { + GROUP_STAGE, + ELIMINATION_STAGE + } +} diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java new file mode 100644 index 00000000..d9fde245 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java @@ -0,0 +1,191 @@ +/* + * 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 . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@AllArgsConstructor +@Getter +@Setter +public class EventRelation { + + static { + SqlTypeMapper.ordinalEnumMapper(FightTeam.class); + SqlTypeMapper.ordinalEnumMapper(FromType.class); + } + + private static final Table table = new Table<>(EventRelation.class); + + private static final SelectStatement get = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE FromType = ? AND FromId = ?"); + private static final SelectStatement byId = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE id = ?"); + private static final SelectStatement byEvent = new SelectStatement<>(table, "SELECT ER.* FROM EventRelation ER JOIN EventFight EF ON EF.id = ER.fightId WHERE EF.EventID = ?"); + private static final Statement insert = table.insertAll(true); + private static final Statement update = table.update(Table.PRIMARY, "fromType", "fromId", "fromPlace"); + private static final Statement updateTeam = table.update(Table.PRIMARY, "fightTeam"); + private static final Statement delete = table.delete(Table.PRIMARY); + + public static List get(Event event) { + return byId.listSelect(event.getEventID()); + } + + public static EventRelation get(int id) { + return byId.select(id); + } + + public static List getFightRelations(EventFight fight) { + return get.listSelect(FromType.FIGHT, fight.getFightID()); + } + + public static List getGroupRelations(EventGroup group) { + return get.listSelect(FromType.GROUP, group.getId()); + } + + public static EventRelation create(EventFight fight, FightTeam fightTeam, FromType fromType, int fromId, int fromPlace) { + int id = insert.insertGetKey(fight.getFightID(), fightTeam, fromType, fromId, fromPlace); + return get(id); + } + + @Field(keys = Table.PRIMARY) + private final int id; + + @Field + private int fightId; + + @Field + private FightTeam fightTeam; + + @Field + private FromType fromType; + + @Field + private int fromId; + + @Field + private int fromPlace; + + public EventFight getFight() { + return EventFight.get(fightId); + } + + public Optional getFromFight() { + if(fromType == FromType.FIGHT) { + return Optional.of(EventFight.get(fromId)); + } else { + return Optional.empty(); + } + } + + public Optional getFromGroup() { + if(fromType == FromType.GROUP) { + return EventGroup.get(fromId); + } else { + return Optional.empty(); + } + } + + public void delete() { + delete.update(id); + } + + public void setUpdateTeam(FightTeam team) { + updateTeam.update(id, team); + this.fightTeam = team; + } + + public void setFromFight(EventFight fight, int place) { + setFrom(fight.getFightID(), place, FromType.FIGHT); + } + + public void setFromGroup(EventGroup group, int place) { + setFrom(group.getId(), place, FromType.GROUP); + } + + private void setFrom(int id, int place, FromType type) { + update.update(id, type, id, place); + this.fromType = type; + this.fromId = id; + this.fromPlace = place; + } + + public Optional getAdvancingTeam() { + if (fromType == FromType.FIGHT) { + if (fromPlace == 0) { + return getFromFight().flatMap(EventFight::getWinner); + } else { + return getFromFight().flatMap(EventFight::getLosser); + } + } else if (fromType == FromType.GROUP) { + return getFromGroup().map(EventGroup::calculatePoints) + .flatMap(points -> points.entrySet().stream() + .sorted(Map.Entry.comparingByValue()) + .skip(fromPlace) + .findFirst() + .map(Map.Entry::getKey)); + } else { + return Optional.empty(); + } + } + + public boolean apply() { + Optional team = getAdvancingTeam().map(Team::getTeamId); + if(!team.isPresent()) + return false; + + EventFight fight = getFight(); + if(fightTeam == FightTeam.RED) { + fight.update( + fight.getStartTime(), + fight.getSpielmodus(), + fight.getMap(), + team.get(), + fight.getTeamBlue(), + fight.getSpectatePort() + ); + } else { + fight.update( + fight.getStartTime(), + fight.getSpielmodus(), + fight.getMap(), + fight.getTeamRed(), + team.get(), + fight.getSpectatePort() + ); + } + + return true; + } + + public static enum FightTeam { + RED, + BLUE + } + + public static enum FromType { + FIGHT, + GROUP + } +} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java index ae9a56ac..3d6c5cac 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java @@ -33,6 +33,8 @@ import de.steamwar.fightsystem.states.OneShotStateDependent; import de.steamwar.fightsystem.winconditions.Wincondition; import de.steamwar.network.NetworkSender; import de.steamwar.network.packets.common.FightEndsPacket; +import de.steamwar.sql.EventFight; +import de.steamwar.sql.EventRelation; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; import lombok.Getter; @@ -70,12 +72,21 @@ public class FightStatistics { } private void setEventResult() { - if (FightSystem.getLastWinner() == null) + if (FightSystem.getLastWinner() == null) { Config.EventKampf.setErgebnis(0); - else if (FightSystem.getLastWinner().isBlue()) + } else if (FightSystem.getLastWinner().isBlue()) { Config.EventKampf.setErgebnis(1); - else + } else { Config.EventKampf.setErgebnis(2); + } + + Config.EventKampf.getDependents().forEach(EventRelation::apply); + + Config.EventKampf.getGroup().ifPresent(group -> { + if (group.getLastFight().map(EventFight::getFightID).orElse(-1) == Config.EventKampf.getFightID() && !group.needsTieBreak()) { + group.getDependents().forEach(EventRelation::apply); + } + }); } private void disable() { diff --git a/WebsiteBackend/src/de/steamwar/data/Groups.kt b/WebsiteBackend/src/de/steamwar/data/Groups.kt deleted file mode 100644 index 2ecff054..00000000 --- a/WebsiteBackend/src/de/steamwar/data/Groups.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.data - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.Serializable -import kotlinx.serialization.cbor.Cbor -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.encodeToByteArray - -@Serializable -data class GroupsData(val groups: MutableList) - -@Serializable -data class GroupData(val name: String, val fights: MutableList) - -@OptIn(ExperimentalSerializationApi::class) -class Groups { - companion object { - private var groups: GroupsData = if (kGroupsFile.exists()) { - Cbor.decodeFromByteArray(kGroupsFile.readBytes()) - } else { - if (!kGroupsFile.parentFile.exists()) { - kGroupsFile.parentFile.mkdirs() - } - kGroupsFile.createNewFile() - kGroupsFile.writeBytes(Cbor.encodeToByteArray(GroupsData(mutableListOf()))) - - GroupsData(mutableListOf()) - } - - fun getGroup(name: String): GroupData? { - return groups.groups.find { it.name == name } - } - - fun getGroup(fight: Int): GroupData? { - return groups.groups.find { it.fights.contains(fight) } - } - - fun getOrCreateGroup(name: String): GroupData { - val group = getGroup(name) - if (group != null) { - return group - } - val newGroup = GroupData(name, mutableListOf()) - groups.groups.add(newGroup) - return newGroup - } - - fun resetGroup(fight: Int, save: Boolean = false) { - val oldGroup = getGroup(fight) - oldGroup?.fights?.remove(fight) - if(oldGroup?.fights?.isEmpty() == true) { - groups.groups.remove(oldGroup) - } - if(save) { - kGroupsFile.writeBytes(Cbor.encodeToByteArray(groups)) - } - } - - fun setGroup(fight: Int, group: String) { - resetGroup(fight) - val newGroup = getOrCreateGroup(group) - newGroup.fights.add(fight) - kGroupsFile.writeBytes(Cbor.encodeToByteArray(groups)) - } - - fun getAllGroups(): List { - return groups.groups.map { it.name } - } - } -} \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/Data.kt b/WebsiteBackend/src/de/steamwar/routes/Data.kt index 0a2dd3ae..a7672063 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Data.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Data.kt @@ -20,12 +20,12 @@ package de.steamwar.routes import de.steamwar.ResponseError -import de.steamwar.data.Groups import de.steamwar.data.getCachedSkin import de.steamwar.plugins.SWAuthPrincipal import de.steamwar.plugins.SWPermissionCheck import de.steamwar.sql.SchematicType import de.steamwar.sql.SteamwarUser +import de.steamwar.sql.Team import de.steamwar.sql.UserPerm import de.steamwar.sql.loadSchematicTypes import de.steamwar.util.fetchData @@ -78,6 +78,9 @@ fun Route.configureDataRoutes() { get("/users") { call.respond(SteamwarUser.getAll().map { ResponseUser(it) }) } + get("/teams") { + call.respond(Team.getAll().map { ResponseTeam(it) }) + } get("/schematicTypes") { val types = mutableListOf() loadSchematicTypes(types, mutableMapOf()) @@ -102,9 +105,6 @@ fun Route.configureDataRoutes() { } call.respond(YamlConfiguration.loadConfiguration(file).getStringList("Server.Maps")) } - get("/groups") { - call.respond(Groups.getAllGroups()) - } } get("/server") { try { diff --git a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt index e95ea89d..d1974686 100644 --- a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt +++ b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt @@ -20,12 +20,7 @@ package de.steamwar.routes import de.steamwar.ResponseError -import de.steamwar.data.Groups -import de.steamwar.plugins.SWPermissionCheck -import de.steamwar.sql.EventFight -import de.steamwar.sql.SteamwarUser -import de.steamwar.sql.Team -import de.steamwar.sql.UserPerm +import de.steamwar.sql.* import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* @@ -45,7 +40,8 @@ data class ResponseEventFight( val start: Long, val ergebnis: Int, val spectatePort: Int?, - val group: String? + val group: ResponseGroups?, + val hasFinished: Boolean ) { constructor(eventFight: EventFight) : this( eventFight.fightID, @@ -56,7 +52,8 @@ data class ResponseEventFight( eventFight.startTime.time, eventFight.ergebnis, eventFight.spectatePort, - Groups.getGroup(eventFight.fightID)?.name + eventFight.group.orElse(null)?.let { ResponseGroups(it, short = true) }, + eventFight.hasFinished() ) } @@ -72,36 +69,39 @@ data class UpdateEventFight( val start: Long? = null, val spielmodus: String? = null, val map: String? = null, - val group: String? = null, - val spectatePort: Int? = null + val group: Int? = null, + val spectatePort: Int? = null, + val ergebnis: Int? = null, ) @Serializable data class CreateEventFight( - val event: Int, val spielmodus: String, val map: String, val blueTeam: Int, val redTeam: Int, val start: Long, val spectatePort: Int? = null, - val group: String? = null + val group: Int? = null ) fun Route.configureEventFightRoutes() { route("/fights") { - install(SWPermissionCheck) { - allowMethod(HttpMethod.Get) - permission = UserPerm.MODERATION + get { + val event = call.receiveEvent() ?: return@get + call.respond(EventFight.getEvent(event.eventID).map { ResponseEventFight(it) }) } post { + val event = call.receiveEvent() ?: return@post + val fight = call.receiveNullable() if (fight == null) { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid body")) return@post } + val eventFight = EventFight.create( - fight.event, + event.eventID, Timestamp.from(Instant.ofEpochMilli(fight.start)), fight.spielmodus, fight.map, @@ -110,9 +110,7 @@ fun Route.configureEventFightRoutes() { fight.spectatePort ) if (fight.group != null) { - if (fight.group != "null") { - Groups.setGroup(eventFight.fightID, fight.group) - } + eventFight.groupId = fight.group } call.respond(HttpStatusCode.Created, ResponseEventFight(eventFight)) } @@ -133,12 +131,17 @@ fun Route.configureEventFightRoutes() { val spectatePort = updateFight.spectatePort ?: fight.spectatePort if (updateFight.group != null) { - if (updateFight.group == "null") { - Groups.resetGroup(fight.fightID, true) + if (updateFight.group == -1) { + fight.setGroup(null) } else { - Groups.setGroup(fight.fightID, updateFight.group) + fight.setGroup(updateFight.group) } } + + if (updateFight.ergebnis != null) { + fight.ergebnis = updateFight.ergebnis + } + fight.update(start, spielmodus, map, teamBlue, teamRed, spectatePort) call.respond(HttpStatusCode.OK, ResponseEventFight(fight)) } diff --git a/WebsiteBackend/src/de/steamwar/routes/EventGroups.kt b/WebsiteBackend/src/de/steamwar/routes/EventGroups.kt new file mode 100644 index 00000000..38260e74 --- /dev/null +++ b/WebsiteBackend/src/de/steamwar/routes/EventGroups.kt @@ -0,0 +1,94 @@ +/* + * 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 . + */ + +package de.steamwar.routes + +import de.steamwar.sql.EventGroup +import de.steamwar.sql.EventGroup.EventGroupType +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 + +@Serializable +data class CreateEventGroup(val name: String, val type: EventGroupType) + +@Serializable +data class UpdateEventGroup( + val name: String? = null, + val type: EventGroupType? = null, + val pointsPerWin: Int? = null, + val pointsPerLoss: Int? = null, + val pointsPerDraw: Int? = null, +) + +fun Route.configureEventGroups() { + route("/groups") { + get { + val event = call.receiveEvent() ?: return@get + call.respond(EventGroup.get(event).map { ResponseGroups(it) }) + } + post { + val event = call.receiveEvent() ?: return@post + val createEventGroup = call.receive() + val group = EventGroup.create(event, createEventGroup.name, createEventGroup.type) + call.respond(ResponseGroups(group)) + } + route("/{group}") { + get { + val group = call.receiveEventGroup() ?: return@get + call.respond(ResponseGroups(group)) + } + put { + val group = call.receiveEventGroup() ?: return@put + val updateEventGroup = call.receive() + val name = updateEventGroup.name ?: group.name + val type = updateEventGroup.type ?: group.type + val pointsPerWin = updateEventGroup.pointsPerWin ?: group.pointsPerWin + val pointsPerLoss = updateEventGroup.pointsPerLoss ?: group.pointsPerLoss + val pointsPerDraw = updateEventGroup.pointsPerDraw ?: group.pointsPerDraw + group.update(name, type, pointsPerWin, pointsPerLoss, pointsPerDraw) + call.respond(ResponseGroups(EventGroup.get(group.id).orElse(null) ?: return@put)) + } + delete { + val group = call.receiveEventGroup() ?: return@delete + group.delete() + call.respond(HttpStatusCode.NoContent) + } + } + } +} + +suspend fun ApplicationCall.receiveEventGroup(): EventGroup? { + val groupId = parameters["group"]?.toIntOrNull() + if (groupId == null) { + respond(HttpStatusCode.BadRequest) + return null + } + + val group = EventGroup.get(groupId).orElse(null) + if (group == null) { + respond(HttpStatusCode.NotFound) + return null + } + + return group +} \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/EventReferees.kt b/WebsiteBackend/src/de/steamwar/routes/EventReferees.kt new file mode 100644 index 00000000..051c31cd --- /dev/null +++ b/WebsiteBackend/src/de/steamwar/routes/EventReferees.kt @@ -0,0 +1,54 @@ +/* + * 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 . + */ + +package de.steamwar.routes + +import de.steamwar.sql.Referee +import de.steamwar.sql.SteamwarUser +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 java.util.* + +fun Route.configureEventRefereesRouting() { + route("/referees") { + get { + val event = call.receiveEvent() ?: return@get + call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.get(it)) }) + } + put { + val event = call.receiveEvent() ?: return@put + val referees = call.receive>() + referees.forEach { + Referee.add(event.eventID, SteamwarUser.get(UUID.fromString(it)).id) + } + call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.get(it)) }) + } + delete { + val event = call.receiveEvent() ?: return@delete + val referees = call.receive>() + referees.forEach { + Referee.remove(event.eventID, SteamwarUser.get(UUID.fromString(it)).id) + } + call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.get(it)) }) + } + } +} \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/EventRelations.kt b/WebsiteBackend/src/de/steamwar/routes/EventRelations.kt new file mode 100644 index 00000000..c8af5866 --- /dev/null +++ b/WebsiteBackend/src/de/steamwar/routes/EventRelations.kt @@ -0,0 +1,109 @@ +/* + * 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 . + */ + +package de.steamwar.routes + +import de.steamwar.sql.EventFight +import de.steamwar.sql.EventGroup +import de.steamwar.sql.EventRelation +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 + +@Serializable +data class CreateEventRelation(val fightId: Int, val team: EventRelation.FightTeam, val fromType: EventRelation.FromType, val fromId: Int, val fromPlace: Int) + +@Serializable +data class UpdateEventRelation(val team: EventRelation.FightTeam? = null, val from: UpdateFromRelation? = null) + +@Serializable +data class UpdateFromRelation(val fromType: EventRelation.FromType, val fromId: Int, val fromPlace: Int) + +fun Route.configureEventRelations() { + route("/relations") { + get { + val event = call.receiveEvent() ?: return@get + + call.respond(EventRelation.get(event).map { ResponseRelation(it) }) + } + post { + val create = call.receive() + + val fight = EventFight.get(create.fightId) ?: return@post call.respond(HttpStatusCode.NotFound) + + when (create.fromType) { + EventRelation.FromType.FIGHT -> EventFight.get(create.fromId) ?: return@post call.respond(HttpStatusCode.BadRequest) + EventRelation.FromType.GROUP -> EventGroup.get(create.fromId) ?: return@post call.respond(HttpStatusCode.BadRequest) + } + + val relation = EventRelation.create(fight, create.team, create.fromType, create.fromId, create.fromPlace) + + call.respond(ResponseRelation(relation)) + } + route("/{relation}") { + get { + val relation = call.receiveEventRelation() ?: return@get + call.respond(ResponseRelation(relation)) + } + put { + val relation = call.receiveEventRelation() ?: return@put + val update = call.receive() + + update.from?.let { + when(it.fromType) { + EventRelation.FromType.FIGHT -> relation.setFromFight(EventFight.get(it.fromId) ?: return@put call.respond(HttpStatusCode.BadRequest), + it.fromPlace + ) + EventRelation.FromType.GROUP -> relation.setFromGroup(EventGroup.get(it.fromId).orElse(null) ?: return@put call.respond(HttpStatusCode.BadRequest), + it.fromPlace + ) + } + } + + update.team?.let { relation.setUpdateTeam(it) } + + call.respond(ResponseRelation(EventRelation.get(relation.id))) + } + delete { + val relation = call.receiveEventRelation() ?: return@delete + relation.delete() + call.respond(HttpStatusCode.NoContent) + } + } + } +} + +suspend fun ApplicationCall.receiveEventRelation(): EventRelation? { + val relationId = parameters["relation"]?.toIntOrNull() + if (relationId == null) { + respond(HttpStatusCode.BadRequest) + return null + } + + val relation = EventRelation.get(relationId) + if (relation == null) { + respond(HttpStatusCode.NotFound) + return null + } + + return relation +} \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/EventTeams.kt b/WebsiteBackend/src/de/steamwar/routes/EventTeams.kt new file mode 100644 index 00000000..caacc0a9 --- /dev/null +++ b/WebsiteBackend/src/de/steamwar/routes/EventTeams.kt @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +package de.steamwar.routes + +import de.steamwar.sql.TeamTeilnahme +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.configureEventTeams() { + route("/teams") { + get { + val event = call.receiveEvent() ?: return@get + call.respond(TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) }) + } + put { + val event = call.receiveEvent() ?: return@put + val team = call.receive>() + team.forEach { + TeamTeilnahme.teilnehmen(it, event.eventID) + } + call.respond(HttpStatusCode.NoContent) + } + delete { + val event = call.receiveEvent() ?: return@delete + val team = call.receive>() + team.forEach { + TeamTeilnahme.notTeilnehmen(it, event.eventID) + } + call.respond(HttpStatusCode.NoContent) + } + } +} \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/Events.kt b/WebsiteBackend/src/de/steamwar/routes/Events.kt index d40cdd3b..8dbb3993 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Events.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Events.kt @@ -20,9 +20,10 @@ package de.steamwar.routes import de.steamwar.ResponseError -import de.steamwar.data.Groups 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.* @@ -39,6 +40,45 @@ data class ShortEvent(val id: Int, val name: String, val start: Long, val end: L 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 +) { + constructor(group: EventGroup, short: Boolean = false) : this( + group.id, + 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: ResponseEventFight, + val type: FromType, + val fromFight: ResponseEventFight? = null, + val fromGroup: ResponseGroups? = null, + val fromPlace: Int +) { + constructor(relation: EventRelation) : this( + relation.id, + ResponseEventFight(relation.fight), + relation.fromType, + relation.fromFight.map { ResponseEventFight(it) }.orElse(null), + relation.fromGroup.map { ResponseGroups(it) }.orElse(null), + relation.fromPlace + ) +} + @Serializable data class ResponseEvent( val id: Int, @@ -49,7 +89,6 @@ data class ResponseEvent( val maxTeamMembers: Int, val schemType: String?, val publicSchemsOnly: Boolean, - val referees: List, ) { constructor(event: Event) : this( event.eventID, @@ -60,7 +99,6 @@ data class ResponseEvent( event.maximumTeamMembers, event.schematicType?.toDB(), event.publicSchemsOnly(), - Referee.get(event.eventID).map { ResponseUser(SteamwarUser.get(it)) } ) } @@ -68,8 +106,20 @@ data class ResponseEvent( data class ExtendedResponseEvent( val event: ResponseEvent, val teams: List, - val fights: List -) + val groups: List, + val fights: List, + val referees: List, + val relations: List +) { + 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.get(it)) }, + EventRelation.get(event).map { ResponseRelation(it) } + ) +} @Serializable data class CreateEvent(val name: String, val start: Long, val end: Long) @@ -111,49 +161,11 @@ fun Route.configureEventsRoute() { } 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 - } + val event = call.receiveEvent() ?: return@get call.respond( - ExtendedResponseEvent( - ResponseEvent(event), - TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) }, - EventFight.getEvent(event.eventID).map { ResponseEventFight(it) }) + ExtendedResponseEvent(event) ) } - 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 @@ -164,7 +176,7 @@ fun Route.configureEventsRoute() { csv.appendLine() val blue = Team.get(it.teamBlue) val red = Team.get(it.teamRed) - val winner = when(it.ergebnis) { + val winner = when (it.ergebnis) { 1 -> blue.teamName 2 -> red.teamName 3 -> "Tie" @@ -176,7 +188,7 @@ fun Route.configureEventsRoute() { Team.get(it.teamBlue).teamName, Team.get(it.teamRed).teamName, winner, - Groups.getGroup(it.fightID)?.name ?: "Ungrouped" + it.group.map { it.name }.orElse("Ungrouped") ).joinToString(",") ) } @@ -200,7 +212,9 @@ fun Route.configureEventsRoute() { 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 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) { @@ -231,6 +245,11 @@ fun Route.configureEventsRoute() { event.delete() call.respond(HttpStatusCode.NoContent) } + configureEventFightRoutes() + configureEventTeams() + configureEventGroups() + configureEventRelations() + configureEventRefereesRouting() } } } diff --git a/WebsiteBackend/src/de/steamwar/routes/Page.kt b/WebsiteBackend/src/de/steamwar/routes/Page.kt index 6f9a9fc4..083d8dc4 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Page.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Page.kt @@ -24,7 +24,6 @@ import de.steamwar.plugins.SWAuthPrincipal import de.steamwar.plugins.SWPermissionCheck import de.steamwar.sql.UserPerm import io.ktor.client.* -import io.ktor.client.call.* import io.ktor.client.engine.java.* import io.ktor.client.plugins.* import io.ktor.client.plugins.contentnegotiation.* @@ -37,11 +36,13 @@ import io.ktor.server.auth.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import io.ktor.util.reflect.* import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* +import java.time.Instant +import java.time.LocalDate +import java.time.format.DateTimeFormatter import java.util.Base64 +import java.util.Date val pathPageIdMap = mutableMapOf() var pageId = 1 @@ -90,6 +91,9 @@ data class PageResponse( @Serializable data class CreatePageRequest(val path: String, val slug: String?, val title: String?) +@Serializable +data class AddImageRequest(val name: String, val data: String) + @Serializable data class CreateBranchRequest(val branch: String) @@ -102,6 +106,9 @@ data class MergeBranchRequest(val branch: String, val message: String) @Serializable data class DeletePageRequest(val sha: String, val message: String) +@Serializable +data class CreateGiteaPageRequest(val message: String, val content: String, val branch: String, val author: Identity) + fun Route.configurePage() { val client = HttpClient(Java) { install(ContentNegotiation) { @@ -113,69 +120,44 @@ fun Route.configurePage() { } } + suspend fun filesInDirectory(path: String, branch: String = "master", fileFilter: (name: String) -> Boolean = { true }): List { + val filesToCheck = mutableListOf(path) + val files = mutableListOf() + + while (filesToCheck.isNotEmpty()) { + val path = filesToCheck.removeAt(0) + val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch") + val fileJson = Json.parseToJsonElement(res.bodyAsText()) + + if (fileJson is JsonArray) { + fileJson.forEach { + val obj = it.jsonObject + if (obj["type"]?.jsonPrimitive?.content == "dir") { + filesToCheck.add(obj["path"]?.jsonPrimitive?.content!!) + } else if (obj["type"]?.jsonPrimitive?.content == "file" && fileFilter(obj["name"]!!.jsonPrimitive.content)) { + files.add(PageResponseList(obj, pathPageIdMap.computeIfAbsent(obj["path"]?.jsonPrimitive?.content!!) { pageId++ })) + } + } + } else { + files.add(PageResponseList(fileJson.jsonObject, pathPageIdMap.computeIfAbsent(fileJson.jsonObject["path"]?.jsonPrimitive?.content!!) { pageId++ })) + } + } + + return files + } + route("page") { install(SWPermissionCheck) { permission = UserPerm.MODERATION } get { val branch = call.request.queryParameters["branch"] ?: "master" - val filesToCheck = mutableListOf("src/content") - val files = mutableListOf() - while (filesToCheck.isNotEmpty()) { - val path = filesToCheck.removeAt(0) - val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch") - val fileJson = Json.parseToJsonElement(res.bodyAsText()) - - if (fileJson is JsonArray) { - fileJson.forEach { - val obj = it.jsonObject - if (obj["type"]?.jsonPrimitive?.content == "dir") { - filesToCheck.add(obj["path"]?.jsonPrimitive?.content!!) - } else if (obj["type"]?.jsonPrimitive?.content == "file" && (obj["name"]?.jsonPrimitive?.content?.endsWith(".md") == true || obj["name"]?.jsonPrimitive?.content?.endsWith(".json") == true)) { - files.add(PageResponseList(obj, pathPageIdMap.computeIfAbsent(obj["path"]?.jsonPrimitive?.content!!) { pageId++ })) - } - } - } else { - files.add(PageResponseList(fileJson.jsonObject, pathPageIdMap.computeIfAbsent(fileJson.jsonObject["path"]?.jsonPrimitive?.content!!) { pageId++ })) - } - } - - call.respond(files) - } - get("branch") { - val res = client.get("repos/SteamWar/Website/branches") - call.respond(res.status, Json.parseToJsonElement(res.bodyAsText()).jsonArray.map { it.jsonObject["name"]?.jsonPrimitive?.content!! }) - } - post("branch") { - @Serializable - data class CreateGiteaBranchRequest(val new_branch_name: String, val old_branch_name: String) - - val branch = call.receive().branch - val res = client.post("repos/SteamWar/Website/branches") { - contentType(ContentType.Application.Json) - setBody(CreateGiteaBranchRequest(branch, "master")) - } - - - @Serializable - data class CreateGiteaMergeRequest(val base: String, val head: String, val title: String) - - client.post("repos/SteamWar/Website/pulls") { - contentType(ContentType.Application.Json) - setBody(CreateGiteaMergeRequest("master", branch, "Merge branch $branch")) - } - - call.respond(res.status) - } - delete("branch") { - val branch = call.receive().branch - val res = client.delete("repos/SteamWar/Website/branches/$branch") - call.respond(res.status) + call.respond(filesInDirectory("/src/content", branch) { + it.endsWith(".md") || it.endsWith(".json") + }) } post { - @Serializable - data class CreateGiteaPageRequest(val message: String, val content: String, val branch: String, val author: Identity) val req = call.receive() if(req.path.startsWith("src/content/")) { @@ -190,62 +172,119 @@ fun Route.configurePage() { --- title: ${req.title ?: "[Enter Title]"} description: [Enter Description] - slug: ${req.slug ?: "[Enter Slug]"} + key: ${req.slug ?: "[Enter Slug]"} + created: ${LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)} + tags: + - test --- # ${req.path} """.trimIndent().toByteArray()), call.request.queryParameters["branch"] ?: "master", Identity(call.principal()!!.user.userName, "admin-tool@steamwar.de" - ))) + ))) } call.respond(res.status) } - get("{id}") { - val id = call.parameters["id"]?.toIntOrNull() ?: return@get call.respond(HttpStatusCode.BadRequest, "Invalid id") - val path = pathPageIdMap.entries.find { it.value == id }?.key ?: return@get call.respond(HttpStatusCode.NotFound, "Page not found") - - val branch = call.request.queryParameters["branch"] ?: "master" - val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch") - val fileJson = Json.parseToJsonElement(res.bodyAsText()) - if (fileJson is JsonArray) { - return@get call.respond(HttpStatusCode.BadRequest, "Invalid id") + route("branch") { + get { + val res = client.get("repos/SteamWar/Website/branches") + call.respond(res.status, Json.parseToJsonElement(res.bodyAsText()).jsonArray.map { it.jsonObject["name"]?.jsonPrimitive?.content!! }) } + post { + @Serializable + data class CreateGiteaBranchRequest(val new_branch_name: String, val old_branch_name: String) - val file = PageResponse(fileJson.jsonObject, id) - call.respond(file) + val branch = call.receive().branch + val res = client.post("repos/SteamWar/Website/branches") { + contentType(ContentType.Application.Json) + setBody(CreateGiteaBranchRequest(branch, "master")) + } + + + @Serializable + data class CreateGiteaMergeRequest(val base: String, val head: String, val title: String) + + client.post("repos/SteamWar/Website/pulls") { + contentType(ContentType.Application.Json) + setBody(CreateGiteaMergeRequest("master", branch, "Merge branch $branch")) + } + + call.respond(res.status) + } + delete { + val branch = call.receive().branch + val res = client.delete("repos/SteamWar/Website/branches/$branch") + call.respond(res.status) + } } + route("{id}") { + get { + val id = call.parameters["id"]?.toIntOrNull() ?: return@get call.respond(HttpStatusCode.BadRequest, "Invalid id") + val path = pathPageIdMap.entries.find { it.value == id }?.key ?: return@get call.respond(HttpStatusCode.NotFound, "Page not found") - delete("{id}") { - val data = call.receive() + val branch = call.request.queryParameters["branch"] ?: "master" + val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch") + val fileJson = Json.parseToJsonElement(res.bodyAsText()) + if (fileJson is JsonArray) { + return@get call.respond(HttpStatusCode.BadRequest, "Invalid id") + } - val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key ?: return@delete call.respond(HttpStatusCode.NotFound, "Page not found") - val branch = call.request.queryParameters["branch"] ?: "master" - - @Serializable - data class DeleteGiteaPageRequest(val sha: String, val message: String, val branch: String, val author: Identity) - - val res = client.delete("repos/SteamWar/Website/contents/$path") { - contentType(ContentType.Application.Json) - setBody(DeleteGiteaPageRequest(data.sha, data.message, branch, Identity(call.principal()!!.user.userName, "admin-tool@steamwar.de"))) + val file = PageResponse(fileJson.jsonObject, id) + call.respond(file) } + delete { + val data = call.receive() - call.respond(res.status) + val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key ?: return@delete call.respond(HttpStatusCode.NotFound, "Page not found") + val branch = call.request.queryParameters["branch"] ?: "master" + + @Serializable + data class DeleteGiteaPageRequest(val sha: String, val message: String, val branch: String, val author: Identity) + + val res = client.delete("repos/SteamWar/Website/contents/$path") { + contentType(ContentType.Application.Json) + setBody(DeleteGiteaPageRequest(data.sha, data.message, branch, Identity(call.principal()!!.user.userName, "admin-tool@steamwar.de"))) + } + + call.respond(res.status) + } + put { + @Serializable + data class UpdateGiteaPageRequest(val content: String, val sha: String, val message: String, val branch: String, val author: Identity) + + val data = call.receive() + val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key ?: return@put call.respond(HttpStatusCode.NotFound, "Page not found") + + val res = client.put("repos/SteamWar/Website/contents/$path") { + contentType(ContentType.Application.Json) + setBody(UpdateGiteaPageRequest(data.content, data.sha, data.message, (call.request.queryParameters["branch"] ?: "master"), Identity(call.principal()!!.user.userName, "admin-tool@steamwar.de"))) + } + + call.respond(res.status) + } } + route("images") { + get { + val branch = call.request.queryParameters["branch"] ?: "master" - put("{id}") { - @Serializable - data class UpdateGiteaPageRequest(val content: String, val sha: String, val message: String, val branch: String, val author: Identity) - - val data = call.receive() - val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key ?: return@put call.respond(HttpStatusCode.NotFound, "Page not found") - - val res = client.put("repos/SteamWar/Website/contents/$path") { - contentType(ContentType.Application.Json) - setBody(UpdateGiteaPageRequest(data.content, data.sha, data.message, (call.request.queryParameters["branch"] ?: "master"), Identity(call.principal()!!.user.userName, "admin-tool@steamwar.de"))) + call.respond(filesInDirectory("/src/images", branch)) } + post { + val req = call.receive() - call.respond(res.status) + client.post("repos/SteamWar/Website/contents/src/images/${req.name}") { + contentType(ContentType.Application.Json) + setBody(CreateGiteaPageRequest( + "Add Image ${req.name}", + req.data, + call.request.queryParameters["branch"] ?: "master", + Identity(call.principal()!!.user.userName, "admin-tool@steamwar.de" + ))) + } + + call.respond(HttpStatusCode.Created) + } } } } \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/Routes.kt b/WebsiteBackend/src/de/steamwar/routes/Routes.kt index 388f8055..f4e883ee 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Routes.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Routes.kt @@ -28,7 +28,6 @@ fun Application.configureRoutes() { authenticate("sw-auth", optional = true) { configureEventsRoute() configureDataRoutes() - configureEventFightRoutes() configureUserPerms() configureStats() configurePage()