Merge pull request 'Add event grouping' (#57) from event-brackets into main

Reviewed-on: SteamWar/SteamWar#57
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
This commit is contained in:
2025-06-26 23:40:51 +02:00
14 changed files with 959 additions and 260 deletions
@@ -37,6 +37,8 @@ public class EventFight implements Comparable<EventFight> {
private static final Table<EventFight> table = new Table<>(EventFight.class); private static final Table<EventFight> table = new Table<>(EventFight.class);
private static final SelectStatement<EventFight> byId = table.select(Table.PRIMARY); private static final SelectStatement<EventFight> byId = table.select(Table.PRIMARY);
private static final SelectStatement<EventFight> byGroup = new SelectStatement<EventFight>(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime ASC");
private static final SelectStatement<EventFight> byGroupLast = new SelectStatement<EventFight>(table, "SELECT * FROM EventFight WHERE GroupID = ? ORDER BY StartTime DESC LIMIT 1");
private static final SelectStatement<EventFight> allComing = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE StartTime > now() ORDER BY StartTime ASC"); private static final SelectStatement<EventFight> allComing = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE StartTime > now() ORDER BY StartTime ASC");
private static final SelectStatement<EventFight> event = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE EventID = ? ORDER BY StartTime ASC"); private static final SelectStatement<EventFight> event = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE EventID = ? ORDER BY StartTime ASC");
private static final SelectStatement<EventFight> activeFights = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE Fight IS NOT NULL AND StartTime < now() AND DATEDIFF(StartTime, now()) < 0"); private static final SelectStatement<EventFight> 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<EventFight> {
private static final Statement create = table.insertFields(true, "eventID", "startTime", "spielmodus", "map", "teamBlue", "teamRed", "spectatePort"); 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 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); private static final Statement delete = table.delete(Table.PRIMARY);
@Getter @Getter
@@ -55,6 +58,14 @@ public class EventFight implements Comparable<EventFight> {
return byId.select(fightID); return byId.select(fightID);
} }
public static List<EventFight> get(EventGroup group) {
return byGroup.listSelect(group.getId());
}
public static Optional<EventFight> getLast(EventGroup group) {
return Optional.ofNullable(byGroupLast.select(group.getId()));
}
public static void loadAllComingFights() { public static void loadAllComingFights() {
fights.clear(); fights.clear();
fights.addAll(allComing.listSelect()); fights.addAll(allComing.listSelect());
@@ -89,6 +100,10 @@ public class EventFight implements Comparable<EventFight> {
private final int fightID; private final int fightID;
@Getter @Getter
@Setter @Setter
@Field(nullable = true, def = "null")
private Integer groupId;
@Getter
@Setter
@Field @Field
private Timestamp startTime; private Timestamp startTime;
@Getter @Getter
@@ -112,11 +127,35 @@ public class EventFight implements Comparable<EventFight> {
@Field(nullable = true) @Field(nullable = true)
private Integer spectatePort; private Integer spectatePort;
@Getter @Getter
@Setter
@Field(def = "1")
private int bestOf;
@Getter
@Field(def = "0") @Field(def = "0")
private int ergebnis; private int ergebnis;
@Field(nullable = true) @Field(nullable = true)
private int fight; private int fight;
public Optional<EventGroup> getGroup() {
return Optional.ofNullable(groupId).flatMap(EventGroup::get);
}
public Optional<Team> getWinner() {
if(ergebnis == 0)
return Optional.empty();
return Optional.ofNullable(ergebnis == 1 ? Team.get(teamBlue) : Team.get(teamRed));
}
public Optional<Team> getLosser() {
if(ergebnis == 0)
return Optional.empty();
return Optional.ofNullable(ergebnis == 1 ? Team.get(teamRed) : Team.get(teamBlue));
}
public List<EventRelation> getDependents() {
return EventRelation.getFightRelations(this);
}
public void setErgebnis(int winner) { public void setErgebnis(int winner) {
this.ergebnis = winner; this.ergebnis = winner;
setResult.update(winner, fightID); setResult.update(winner, fightID);
@@ -128,6 +167,11 @@ public class EventFight implements Comparable<EventFight> {
setFight.update(fight, fightID); setFight.update(fight, fightID);
} }
public void setGroup(Integer group) {
setGroup.update(group, fightID);
this.groupId = group;
}
public boolean hasFinished() { public boolean hasFinished() {
return fight != 0 || ergebnis != 0; return fight != 0 || ergebnis != 0;
} }
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<EventGroup> table = new Table<>(EventGroup.class);
private static final SelectStatement<EventGroup> get = table.select(Table.PRIMARY);
private static final SelectStatement<EventGroup> 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<EventGroup> 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<EventGroup> 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<Team, Integer> points;
public List<EventFight> getFights() {
return EventFight.get(this);
}
public Set<Integer> getTeamsId() {
return getFights().stream().flatMap(fight -> Stream.of(fight.getTeamBlue(), fight.getTeamRed()))
.collect(Collectors.toSet());
}
public Set<Team> getTeams() {
return getTeamsId().stream().map(Team::get).collect(Collectors.toSet());
}
public Optional<EventFight> getLastFight() {
return EventFight.getLast(this);
}
public List<EventRelation> getDependents() {
return EventRelation.getGroupRelations(this);
}
public Map<Team, Integer> calculatePoints() {
if (points == null) {
Map<Integer, Integer> 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
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<EventRelation> table = new Table<>(EventRelation.class);
private static final SelectStatement<EventRelation> get = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE FromType = ? AND FromId = ?");
private static final SelectStatement<EventRelation> byId = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE id = ?");
private static final SelectStatement<EventRelation> 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<EventRelation> get(Event event) {
return byId.listSelect(event.getEventID());
}
public static EventRelation get(int id) {
return byId.select(id);
}
public static List<EventRelation> getFightRelations(EventFight fight) {
return get.listSelect(FromType.FIGHT, fight.getFightID());
}
public static List<EventRelation> 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<EventFight> getFromFight() {
if(fromType == FromType.FIGHT) {
return Optional.of(EventFight.get(fromId));
} else {
return Optional.empty();
}
}
public Optional<EventGroup> 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<Team> 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<Integer> 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
}
}
@@ -33,6 +33,8 @@ import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.winconditions.Wincondition; import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.network.NetworkSender; import de.steamwar.network.NetworkSender;
import de.steamwar.network.packets.common.FightEndsPacket; 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.SchematicNode;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import lombok.Getter; import lombok.Getter;
@@ -70,12 +72,21 @@ public class FightStatistics {
} }
private void setEventResult() { private void setEventResult() {
if (FightSystem.getLastWinner() == null) if (FightSystem.getLastWinner() == null) {
Config.EventKampf.setErgebnis(0); Config.EventKampf.setErgebnis(0);
else if (FightSystem.getLastWinner().isBlue()) } else if (FightSystem.getLastWinner().isBlue()) {
Config.EventKampf.setErgebnis(1); Config.EventKampf.setErgebnis(1);
else } else {
Config.EventKampf.setErgebnis(2); 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() { private void disable() {
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<GroupData>)
@Serializable
data class GroupData(val name: String, val fights: MutableList<Int>)
@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<String> {
return groups.groups.map { it.name }
}
}
}
@@ -20,12 +20,12 @@
package de.steamwar.routes package de.steamwar.routes
import de.steamwar.ResponseError import de.steamwar.ResponseError
import de.steamwar.data.Groups
import de.steamwar.data.getCachedSkin import de.steamwar.data.getCachedSkin
import de.steamwar.plugins.SWAuthPrincipal import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.SchematicType import de.steamwar.sql.SchematicType
import de.steamwar.sql.SteamwarUser import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.Team
import de.steamwar.sql.UserPerm import de.steamwar.sql.UserPerm
import de.steamwar.sql.loadSchematicTypes import de.steamwar.sql.loadSchematicTypes
import de.steamwar.util.fetchData import de.steamwar.util.fetchData
@@ -78,6 +78,9 @@ fun Route.configureDataRoutes() {
get("/users") { get("/users") {
call.respond(SteamwarUser.getAll().map { ResponseUser(it) }) call.respond(SteamwarUser.getAll().map { ResponseUser(it) })
} }
get("/teams") {
call.respond(Team.getAll().map { ResponseTeam(it) })
}
get("/schematicTypes") { get("/schematicTypes") {
val types = mutableListOf<SchematicType>() val types = mutableListOf<SchematicType>()
loadSchematicTypes(types, mutableMapOf()) loadSchematicTypes(types, mutableMapOf())
@@ -102,9 +105,6 @@ fun Route.configureDataRoutes() {
} }
call.respond(YamlConfiguration.loadConfiguration(file).getStringList("Server.Maps")) call.respond(YamlConfiguration.loadConfiguration(file).getStringList("Server.Maps"))
} }
get("/groups") {
call.respond(Groups.getAllGroups())
}
} }
get("/server") { get("/server") {
try { try {
@@ -20,12 +20,7 @@
package de.steamwar.routes package de.steamwar.routes
import de.steamwar.ResponseError import de.steamwar.ResponseError
import de.steamwar.data.Groups import de.steamwar.sql.*
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 io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.request.* import io.ktor.server.request.*
@@ -45,7 +40,8 @@ data class ResponseEventFight(
val start: Long, val start: Long,
val ergebnis: Int, val ergebnis: Int,
val spectatePort: Int?, val spectatePort: Int?,
val group: String? val group: ResponseGroups?,
val hasFinished: Boolean
) { ) {
constructor(eventFight: EventFight) : this( constructor(eventFight: EventFight) : this(
eventFight.fightID, eventFight.fightID,
@@ -56,7 +52,8 @@ data class ResponseEventFight(
eventFight.startTime.time, eventFight.startTime.time,
eventFight.ergebnis, eventFight.ergebnis,
eventFight.spectatePort, 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 start: Long? = null,
val spielmodus: String? = null, val spielmodus: String? = null,
val map: String? = null, val map: String? = null,
val group: String? = null, val group: Int? = null,
val spectatePort: Int? = null val spectatePort: Int? = null,
val ergebnis: Int? = null,
) )
@Serializable @Serializable
data class CreateEventFight( data class CreateEventFight(
val event: Int,
val spielmodus: String, val spielmodus: String,
val map: String, val map: String,
val blueTeam: Int, val blueTeam: Int,
val redTeam: Int, val redTeam: Int,
val start: Long, val start: Long,
val spectatePort: Int? = null, val spectatePort: Int? = null,
val group: String? = null val group: Int? = null
) )
fun Route.configureEventFightRoutes() { fun Route.configureEventFightRoutes() {
route("/fights") { route("/fights") {
install(SWPermissionCheck) { get {
allowMethod(HttpMethod.Get) val event = call.receiveEvent() ?: return@get
permission = UserPerm.MODERATION call.respond(EventFight.getEvent(event.eventID).map { ResponseEventFight(it) })
} }
post { post {
val event = call.receiveEvent() ?: return@post
val fight = call.receiveNullable<CreateEventFight>() val fight = call.receiveNullable<CreateEventFight>()
if (fight == null) { if (fight == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid body")) call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid body"))
return@post return@post
} }
val eventFight = EventFight.create( val eventFight = EventFight.create(
fight.event, event.eventID,
Timestamp.from(Instant.ofEpochMilli(fight.start)), Timestamp.from(Instant.ofEpochMilli(fight.start)),
fight.spielmodus, fight.spielmodus,
fight.map, fight.map,
@@ -110,9 +110,7 @@ fun Route.configureEventFightRoutes() {
fight.spectatePort fight.spectatePort
) )
if (fight.group != null) { if (fight.group != null) {
if (fight.group != "null") { eventFight.groupId = fight.group
Groups.setGroup(eventFight.fightID, fight.group)
}
} }
call.respond(HttpStatusCode.Created, ResponseEventFight(eventFight)) call.respond(HttpStatusCode.Created, ResponseEventFight(eventFight))
} }
@@ -133,12 +131,17 @@ fun Route.configureEventFightRoutes() {
val spectatePort = updateFight.spectatePort ?: fight.spectatePort val spectatePort = updateFight.spectatePort ?: fight.spectatePort
if (updateFight.group != null) { if (updateFight.group != null) {
if (updateFight.group == "null") { if (updateFight.group == -1) {
Groups.resetGroup(fight.fightID, true) fight.setGroup(null)
} else { } 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) fight.update(start, spielmodus, map, teamBlue, teamRed, spectatePort)
call.respond(HttpStatusCode.OK, ResponseEventFight(fight)) call.respond(HttpStatusCode.OK, ResponseEventFight(fight))
} }
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CreateEventGroup>()
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<UpdateEventGroup>()
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
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<List<String>>()
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<List<String>>()
referees.forEach {
Referee.remove(event.eventID, SteamwarUser.get(UUID.fromString(it)).id)
}
call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.get(it)) })
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CreateEventRelation>()
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<UpdateEventRelation>()
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
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<List<Int>>()
team.forEach {
TeamTeilnahme.teilnehmen(it, event.eventID)
}
call.respond(HttpStatusCode.NoContent)
}
delete {
val event = call.receiveEvent() ?: return@delete
val team = call.receive<List<Int>>()
team.forEach {
TeamTeilnahme.notTeilnehmen(it, event.eventID)
}
call.respond(HttpStatusCode.NoContent)
}
}
}
+67 -48
View File
@@ -20,9 +20,10 @@
package de.steamwar.routes package de.steamwar.routes
import de.steamwar.ResponseError import de.steamwar.ResponseError
import de.steamwar.data.Groups
import de.steamwar.plugins.SWPermissionCheck import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.* import de.steamwar.sql.*
import de.steamwar.sql.EventGroup.EventGroupType
import de.steamwar.sql.EventRelation.FromType
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.request.* 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) 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.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 @Serializable
data class ResponseEvent( data class ResponseEvent(
val id: Int, val id: Int,
@@ -49,7 +89,6 @@ data class ResponseEvent(
val maxTeamMembers: Int, val maxTeamMembers: Int,
val schemType: String?, val schemType: String?,
val publicSchemsOnly: Boolean, val publicSchemsOnly: Boolean,
val referees: List<ResponseUser>,
) { ) {
constructor(event: Event) : this( constructor(event: Event) : this(
event.eventID, event.eventID,
@@ -60,7 +99,6 @@ data class ResponseEvent(
event.maximumTeamMembers, event.maximumTeamMembers,
event.schematicType?.toDB(), event.schematicType?.toDB(),
event.publicSchemsOnly(), event.publicSchemsOnly(),
Referee.get(event.eventID).map { ResponseUser(SteamwarUser.get(it)) }
) )
} }
@@ -68,8 +106,20 @@ data class ResponseEvent(
data class ExtendedResponseEvent( data class ExtendedResponseEvent(
val event: ResponseEvent, val event: ResponseEvent,
val teams: List<ResponseTeam>, val teams: List<ResponseTeam>,
val fights: List<ResponseEventFight> 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.get(it)) },
EventRelation.get(event).map { ResponseRelation(it) }
)
}
@Serializable @Serializable
data class CreateEvent(val name: String, val start: Long, val end: Long) data class CreateEvent(val name: String, val start: Long, val end: Long)
@@ -111,49 +161,11 @@ fun Route.configureEventsRoute() {
} }
route("/{id}") { route("/{id}") {
get { get {
val id = call.parameters["id"]?.toIntOrNull() val event = call.receiveEvent() ?: return@get
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( call.respond(
ExtendedResponseEvent( ExtendedResponseEvent(event)
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") { get("/csv") {
val event = call.receiveEvent() ?: return@get val event = call.receiveEvent() ?: return@get
@@ -164,7 +176,7 @@ fun Route.configureEventsRoute() {
csv.appendLine() csv.appendLine()
val blue = Team.get(it.teamBlue) val blue = Team.get(it.teamBlue)
val red = Team.get(it.teamRed) val red = Team.get(it.teamRed)
val winner = when(it.ergebnis) { val winner = when (it.ergebnis) {
1 -> blue.teamName 1 -> blue.teamName
2 -> red.teamName 2 -> red.teamName
3 -> "Tie" 3 -> "Tie"
@@ -176,7 +188,7 @@ fun Route.configureEventsRoute() {
Team.get(it.teamBlue).teamName, Team.get(it.teamBlue).teamName,
Team.get(it.teamRed).teamName, Team.get(it.teamRed).teamName,
winner, winner,
Groups.getGroup(it.fightID)?.name ?: "Ungrouped" it.group.map { it.name }.orElse("Ungrouped")
).joinToString(",") ).joinToString(",")
) )
} }
@@ -200,7 +212,9 @@ fun Route.configureEventsRoute() {
val end = updateEvent.end?.let { Timestamp.from(Instant.ofEpochMilli(it)) } ?: event.end val end = updateEvent.end?.let { Timestamp.from(Instant.ofEpochMilli(it)) } ?: event.end
val maxTeamMembers = updateEvent.maxTeamMembers ?: event.maximumTeamMembers 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() val publicSchemsOnly = updateEvent.publicSchemsOnly ?: event.publicSchemsOnly()
if (updateEvent.addReferee != null) { if (updateEvent.addReferee != null) {
@@ -231,6 +245,11 @@ fun Route.configureEventsRoute() {
event.delete() event.delete()
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }
configureEventFightRoutes()
configureEventTeams()
configureEventGroups()
configureEventRelations()
configureEventRefereesRouting()
} }
} }
} }
+132 -93
View File
@@ -24,7 +24,6 @@ import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.UserPerm import de.steamwar.sql.UserPerm
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.java.* import io.ktor.client.engine.java.*
import io.ktor.client.plugins.* import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.*
@@ -37,11 +36,13 @@ import io.ktor.server.auth.*
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 io.ktor.util.reflect.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.time.Instant
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Base64 import java.util.Base64
import java.util.Date
val pathPageIdMap = mutableMapOf<String, Int>() val pathPageIdMap = mutableMapOf<String, Int>()
var pageId = 1 var pageId = 1
@@ -90,6 +91,9 @@ data class PageResponse(
@Serializable @Serializable
data class CreatePageRequest(val path: String, val slug: String?, val title: String?) data class CreatePageRequest(val path: String, val slug: String?, val title: String?)
@Serializable
data class AddImageRequest(val name: String, val data: String)
@Serializable @Serializable
data class CreateBranchRequest(val branch: String) data class CreateBranchRequest(val branch: String)
@@ -102,6 +106,9 @@ data class MergeBranchRequest(val branch: String, val message: String)
@Serializable @Serializable
data class DeletePageRequest(val sha: String, val message: String) 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() { fun Route.configurePage() {
val client = HttpClient(Java) { val client = HttpClient(Java) {
install(ContentNegotiation) { install(ContentNegotiation) {
@@ -113,69 +120,44 @@ fun Route.configurePage() {
} }
} }
suspend fun filesInDirectory(path: String, branch: String = "master", fileFilter: (name: String) -> Boolean = { true }): List<PageResponseList> {
val filesToCheck = mutableListOf(path)
val files = mutableListOf<PageResponseList>()
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") { route("page") {
install(SWPermissionCheck) { install(SWPermissionCheck) {
permission = UserPerm.MODERATION permission = UserPerm.MODERATION
} }
get { get {
val branch = call.request.queryParameters["branch"] ?: "master" val branch = call.request.queryParameters["branch"] ?: "master"
val filesToCheck = mutableListOf("src/content")
val files = mutableListOf<PageResponseList>()
while (filesToCheck.isNotEmpty()) { call.respond(filesInDirectory("/src/content", branch) {
val path = filesToCheck.removeAt(0) it.endsWith(".md") || it.endsWith(".json")
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<CreateBranchRequest>().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<CreateBranchRequest>().branch
val res = client.delete("repos/SteamWar/Website/branches/$branch")
call.respond(res.status)
} }
post { post {
@Serializable
data class CreateGiteaPageRequest(val message: String, val content: String, val branch: String, val author: Identity)
val req = call.receive<CreatePageRequest>() val req = call.receive<CreatePageRequest>()
if(req.path.startsWith("src/content/")) { if(req.path.startsWith("src/content/")) {
@@ -190,62 +172,119 @@ fun Route.configurePage() {
--- ---
title: ${req.title ?: "[Enter Title]"} title: ${req.title ?: "[Enter Title]"}
description: [Enter Description] 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} # ${req.path}
""".trimIndent().toByteArray()), """.trimIndent().toByteArray()),
call.request.queryParameters["branch"] ?: "master", call.request.queryParameters["branch"] ?: "master",
Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de" Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de"
))) )))
} }
call.respond(res.status) call.respond(res.status)
} }
get("{id}") { route("branch") {
val id = call.parameters["id"]?.toIntOrNull() ?: return@get call.respond(HttpStatusCode.BadRequest, "Invalid id") get {
val path = pathPageIdMap.entries.find { it.value == id }?.key ?: return@get call.respond(HttpStatusCode.NotFound, "Page not found") val res = client.get("repos/SteamWar/Website/branches")
call.respond(res.status, Json.parseToJsonElement(res.bodyAsText()).jsonArray.map { it.jsonObject["name"]?.jsonPrimitive?.content!! })
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")
} }
post {
@Serializable
data class CreateGiteaBranchRequest(val new_branch_name: String, val old_branch_name: String)
val file = PageResponse(fileJson.jsonObject, id) val branch = call.receive<CreateBranchRequest>().branch
call.respond(file) 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<CreateBranchRequest>().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 branch = call.request.queryParameters["branch"] ?: "master"
val data = call.receive<DeletePageRequest>() 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 file = PageResponse(fileJson.jsonObject, id)
val branch = call.request.queryParameters["branch"] ?: "master" call.respond(file)
@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<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de")))
} }
delete {
val data = call.receive<DeletePageRequest>()
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<SWAuthPrincipal>()!!.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<UpdatePageRequest>()
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<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de")))
}
call.respond(res.status)
}
} }
route("images") {
get {
val branch = call.request.queryParameters["branch"] ?: "master"
put("{id}") { call.respond(filesInDirectory("/src/images", branch))
@Serializable
data class UpdateGiteaPageRequest(val content: String, val sha: String, val message: String, val branch: String, val author: Identity)
val data = call.receive<UpdatePageRequest>()
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<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de")))
} }
post {
val req = call.receive<AddImageRequest>()
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<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de"
)))
}
call.respond(HttpStatusCode.Created)
}
} }
} }
} }
@@ -28,7 +28,6 @@ fun Application.configureRoutes() {
authenticate("sw-auth", optional = true) { authenticate("sw-auth", optional = true) {
configureEventsRoute() configureEventsRoute()
configureDataRoutes() configureDataRoutes()
configureEventFightRoutes()
configureUserPerms() configureUserPerms()
configureStats() configureStats()
configurePage() configurePage()