From f387805b4023c27f5f6f51e13e61caaaf5176830 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 7 May 2025 13:51:13 +0200 Subject: [PATCH 1/9] Add event grouping --- .../SQL/src/de/steamwar/sql/EventFight.java | 23 +++ .../SQL/src/de/steamwar/sql/EventGroup.java | 117 +++++++++++++++ .../src/de/steamwar/sql/EventRelation.java | 133 ++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 CommonCore/SQL/src/de/steamwar/sql/EventGroup.java create mode 100644 CommonCore/SQL/src/de/steamwar/sql/EventRelation.java diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java index fe91e3be..c595149b 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java @@ -37,6 +37,7 @@ 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 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 Statement reschedule = table.update(Table.PRIMARY, "StartTime"); @@ -54,6 +55,10 @@ public class EventFight implements Comparable { return byId.select(fightID); } + public static List get(EventGroup group) { + return byGroup.listSelect(group.getId()); + } + public static void loadAllComingFights() { fights.clear(); fights.addAll(allComing.listSelect()); @@ -75,6 +80,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 @@ -98,11 +107,25 @@ 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 void setErgebnis(int winner) { this.ergebnis = winner; setResult.update(winner, fightID); 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..3ea62738 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java @@ -0,0 +1,117 @@ +/* + * 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.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.SqlTypeMapper; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@AllArgsConstructor +@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 = ?"); + + public static List get(Event eventID) { + return byEvent.listSelect(eventID); + } + + public static Optional get(int id) { + return Optional.ofNullable(get.select(id)); + } + + @Field(keys = Table.PRIMARY) + private final int id; + + @Field + private String name; + + @Field + private EventGroupType type; + + @Field + private int pointsPerWin; + + @Field + private int pointsPerLoss; + + @Field + private int pointsPerDraw; + + public List getFights() { + return EventFight.get(this); + } + + public Map calculatePoints() { + Map teams = new HashMap<>(); + Map points = new HashMap<>(); + + for(EventFight fight : getFights()) { + int blueTeamAdd = 0; + int redTeamAdd = 0; + + 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; + } + + Team blueTeam = teams.computeIfAbsent(fight.getTeamBlue(), Team::get); + Team redTeam = teams.computeIfAbsent(fight.getTeamRed(), Team::get); + + points.put(blueTeam, points.getOrDefault(blueTeam, 0) + blueTeamAdd); + points.put(redTeam, points.getOrDefault(redTeam, 0) + redTeamAdd); + } + + return points; + } + + 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..1c154d56 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java @@ -0,0 +1,133 @@ +/* + * 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.Field; +import de.steamwar.sql.internal.SqlTypeMapper; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +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); + + @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 Optional getAdvancingTeam() { + if (fromType == FromType.FIGHT) { + return getFromFight().flatMap(EventFight::getWinner); + } else if (fromType == FromType.GROUP) { + return getFromGroup().map(EventGroup::calculatePoints) + .flatMap(points -> points.entrySet().stream() + .max(Map.Entry.comparingByValue()) + .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 + } +} From c633694222734855660cdc947c85ee12153099b6 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 7 May 2025 16:16:36 +0200 Subject: [PATCH 2/9] Refactor event handling and tie-break logic implementation Introduced new methods and structures in EventGroup, EventRelation, and EventFight to streamline point calculations, tie-break detection, and dependency resolution. Improved modularity by adding methods like getLastFight, needsTieBreak, and getDependents while optimizing the event result setting process. This refactor enhances clarity, reduces redundancy, and supports better maintainability of event-related logic. --- .../SQL/src/de/steamwar/sql/EventFight.java | 15 +++++ .../SQL/src/de/steamwar/sql/EventGroup.java | 67 ++++++++++++------- .../src/de/steamwar/sql/EventRelation.java | 59 ++++++++++++++-- .../fightsystem/utils/FightStatistics.java | 17 ++++- 4 files changed, 125 insertions(+), 33 deletions(-) diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java index c595149b..e231fc70 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java @@ -38,6 +38,7 @@ 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 Statement reschedule = table.update(Table.PRIMARY, "StartTime"); @@ -59,6 +60,10 @@ public class EventFight implements Comparable { 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()); @@ -126,6 +131,16 @@ public class EventFight implements Comparable { 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); diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java index 3ea62738..c5c00580 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java @@ -71,45 +71,60 @@ public class EventGroup { @Field private int pointsPerDraw; + private Map points; + public List getFights() { return EventFight.get(this); } + public Optional getLastFight() { + return EventFight.getLast(this); + } + + public List getDependents() { + return EventRelation.getGroupRelations(this); + } + public Map calculatePoints() { - Map teams = new HashMap<>(); - Map points = new HashMap<>(); + if (points == null) { + Map teams = new HashMap<>(); - for(EventFight fight : getFights()) { - int blueTeamAdd = 0; - int redTeamAdd = 0; + for(EventFight fight : getFights()) { + int blueTeamAdd = 0; + int redTeamAdd = 0; - 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; + 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; + } + + Team blueTeam = teams.computeIfAbsent(fight.getTeamBlue(), Team::get); + Team redTeam = teams.computeIfAbsent(fight.getTeamRed(), Team::get); + + points.put(blueTeam, points.getOrDefault(blueTeam, 0) + blueTeamAdd); + points.put(redTeam, points.getOrDefault(redTeam, 0) + redTeamAdd); } - - Team blueTeam = teams.computeIfAbsent(fight.getTeamBlue(), Team::get); - Team redTeam = teams.computeIfAbsent(fight.getTeamRed(), Team::get); - - points.put(blueTeam, points.getOrDefault(blueTeam, 0) + blueTeamAdd); - points.put(redTeam, points.getOrDefault(redTeam, 0) + redTeamAdd); } return points; } + public boolean needsTieBreak() { + return calculatePoints().values().stream().sorted().limit(2).distinct().count() < 2; + } + 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 index 1c154d56..13b35f02 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java @@ -19,13 +19,12 @@ package de.steamwar.sql; -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SqlTypeMapper; -import de.steamwar.sql.internal.Table; +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; @@ -41,6 +40,30 @@ public class EventRelation { 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 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 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; @@ -79,9 +102,37 @@ public class EventRelation { } } + 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) { - return getFromFight().flatMap(EventFight::getWinner); + if (fromPlace == 1) { + 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() 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() { From e3179c69aaea55cad49617baaf62770973d3b98c Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 8 May 2025 17:32:12 +0200 Subject: [PATCH 3/9] Refactor event group management and routing system --- .../SQL/src/de/steamwar/sql/EventFight.java | 6 + .../SQL/src/de/steamwar/sql/EventGroup.java | 49 ++++++-- .../src/de/steamwar/sql/EventRelation.java | 5 + WebsiteBackend/src/de/steamwar/data/Groups.kt | 89 -------------- WebsiteBackend/src/de/steamwar/routes/Data.kt | 4 - .../src/de/steamwar/routes/EventFights.kt | 32 ++--- .../src/de/steamwar/routes/EventGroups.kt | 94 +++++++++++++++ .../src/de/steamwar/routes/EventRelations.kt | 109 +++++++++++++++++ .../src/de/steamwar/routes/EventTeams.kt | 52 ++++++++ .../src/de/steamwar/routes/Events.kt | 114 ++++++++++-------- .../src/de/steamwar/routes/Routes.kt | 1 - 11 files changed, 385 insertions(+), 170 deletions(-) delete mode 100644 WebsiteBackend/src/de/steamwar/data/Groups.kt create mode 100644 WebsiteBackend/src/de/steamwar/routes/EventGroups.kt create mode 100644 WebsiteBackend/src/de/steamwar/routes/EventRelations.kt create mode 100644 WebsiteBackend/src/de/steamwar/routes/EventTeams.kt diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java index e231fc70..d23e7411 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java @@ -47,6 +47,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 @@ -152,6 +153,11 @@ public class EventFight implements Comparable { setFight.update(fight, fightID); } + public void setGroup(EventGroup group) { + setGroup.update(group.getId(), fightID); + this.groupId = group.getId(); + } + 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 index c5c00580..166e7eb1 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java @@ -19,11 +19,7 @@ package de.steamwar.sql; -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.SqlTypeMapper; -import de.steamwar.sql.internal.Table; -import lombok.AllArgsConstructor; +import de.steamwar.sql.internal.*; import lombok.Getter; import lombok.Setter; @@ -32,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -@AllArgsConstructor @Getter @Setter public class EventGroup { @@ -45,8 +40,17 @@ public class EventGroup { 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); + 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) { @@ -56,7 +60,10 @@ public class EventGroup { @Field(keys = Table.PRIMARY) private final int id; - @Field + @Field(keys = "EVENT_NAME") + private int eventID; + + @Field(keys = "EVENT_NAME") private String name; @Field @@ -71,6 +78,16 @@ public class EventGroup { @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() { @@ -88,8 +105,9 @@ public class EventGroup { public Map calculatePoints() { if (points == null) { Map teams = new HashMap<>(); + points = new HashMap<>(); - for(EventFight fight : getFights()) { + for (EventFight fight : getFights()) { int blueTeamAdd = 0; int redTeamAdd = 0; @@ -121,10 +139,23 @@ public class EventGroup { return points; } + public void update(String name, EventGroupType type, int pointsPerWin, int pointsPerLoss, int pointsPerDraw) { + update.update(id, name, type, pointsPerWin, pointsPerLoss, pointsPerDraw); + 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 index 13b35f02..28c756ee 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java @@ -42,11 +42,16 @@ public class EventRelation { 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); } 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..6af82bfe 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Data.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Data.kt @@ -20,7 +20,6 @@ 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 @@ -102,9 +101,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..1f0e63c5 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,7 @@ data class ResponseEventFight( val start: Long, val ergebnis: Int, val spectatePort: Int?, - val group: String? + val group: ResponseGroups? ) { constructor(eventFight: EventFight) : this( eventFight.fightID, @@ -56,7 +51,7 @@ data class ResponseEventFight( eventFight.startTime.time, eventFight.ergebnis, eventFight.spectatePort, - Groups.getGroup(eventFight.fightID)?.name + eventFight.group.orElse(null)?.let { ResponseGroups(it) } ) } @@ -72,7 +67,7 @@ data class UpdateEventFight( val start: Long? = null, val spielmodus: String? = null, val map: String? = null, - val group: String? = null, + val group: Int? = null, val spectatePort: Int? = null ) @@ -85,14 +80,14 @@ data class CreateEventFight( 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 fight = call.receiveNullable() @@ -100,6 +95,7 @@ fun Route.configureEventFightRoutes() { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid body")) return@post } + val eventFight = EventFight.create( fight.event, Timestamp.from(Instant.ofEpochMilli(fight.start)), @@ -110,9 +106,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,10 +127,10 @@ 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.groupId = null } else { - Groups.setGroup(fight.fightID, updateFight.group) + fight.groupId = updateFight.group } } fight.update(start, spielmodus, map, teamBlue, teamRed, spectatePort) 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/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..1ef7ba83 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,10 @@ fun Route.configureEventsRoute() { event.delete() call.respond(HttpStatusCode.NoContent) } + configureEventFightRoutes() + configureEventTeams() + configureEventGroups() + configureEventRelations() } } } 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() From 6e9db276efb94c5eca3e7473af85da777ea6a693 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sat, 10 May 2025 22:22:43 +0200 Subject: [PATCH 4/9] Add event referees management and teams endpoint. Introduced a new route for managing event referees with get, put, and delete operations. Also added an endpoint to fetch all teams, and integrated the referees routing into event configuration. --- WebsiteBackend/src/de/steamwar/routes/Data.kt | 4 ++ .../src/de/steamwar/routes/EventReferees.kt | 54 +++++++++++++++++++ .../src/de/steamwar/routes/Events.kt | 1 + 3 files changed, 59 insertions(+) create mode 100644 WebsiteBackend/src/de/steamwar/routes/EventReferees.kt diff --git a/WebsiteBackend/src/de/steamwar/routes/Data.kt b/WebsiteBackend/src/de/steamwar/routes/Data.kt index 6af82bfe..a7672063 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Data.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Data.kt @@ -25,6 +25,7 @@ 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 @@ -77,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()) 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/Events.kt b/WebsiteBackend/src/de/steamwar/routes/Events.kt index 1ef7ba83..8dbb3993 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Events.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Events.kt @@ -249,6 +249,7 @@ fun Route.configureEventsRoute() { configureEventTeams() configureEventGroups() configureEventRelations() + configureEventRefereesRouting() } } } From b0be06136d96ea6415457136cb0bffcf9accc150 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 18 May 2025 13:31:14 +0200 Subject: [PATCH 5/9] Add 'ergebnis' field to EventFights data model and update logic Introduced a new 'ergebnis' field to the EventFights data model to handle fight results. Updated the logic to support processing and updating this field when provided. This ensures better tracking and management of event fight outcomes. --- WebsiteBackend/src/de/steamwar/routes/EventFights.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt index 1f0e63c5..bcdb1b6e 100644 --- a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt +++ b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt @@ -68,7 +68,8 @@ data class UpdateEventFight( val spielmodus: String? = null, val map: String? = null, val group: Int? = null, - val spectatePort: Int? = null + val spectatePort: Int? = null, + val ergebnis: Int? = null, ) @Serializable @@ -133,6 +134,11 @@ fun Route.configureEventFightRoutes() { fight.groupId = updateFight.group } } + + if (updateFight.ergebnis != null) { + fight.ergebnis = updateFight.ergebnis + } + fight.update(start, spielmodus, map, teamBlue, teamRed, spectatePort) call.respond(HttpStatusCode.OK, ResponseEventFight(fight)) } From 8768fd7d8158e1697f39adb9919b2a4c4d634a0c Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 22 May 2025 19:42:49 +0200 Subject: [PATCH 6/9] Refactor event handling and group assignment logic Replaced `fight.event` with `event.eventID` for consistency and improved event handling. Adjusted `setGroup` to accept `Integer` instead of `EventGroup` to simplify group assignment logic. Removed unused `event` field in `CreateEventFight` and streamlined related processing. --- CommonCore/SQL/src/de/steamwar/sql/EventFight.java | 6 +++--- WebsiteBackend/src/de/steamwar/routes/EventFights.kt | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java index d23e7411..72b19b42 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventFight.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventFight.java @@ -153,9 +153,9 @@ public class EventFight implements Comparable { setFight.update(fight, fightID); } - public void setGroup(EventGroup group) { - setGroup.update(group.getId(), fightID); - this.groupId = group.getId(); + public void setGroup(Integer group) { + setGroup.update(group, fightID); + this.groupId = group; } public boolean hasFinished() { diff --git a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt index bcdb1b6e..4823619f 100644 --- a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt +++ b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt @@ -74,7 +74,6 @@ data class UpdateEventFight( @Serializable data class CreateEventFight( - val event: Int, val spielmodus: String, val map: String, val blueTeam: Int, @@ -91,6 +90,8 @@ fun Route.configureEventFightRoutes() { 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")) @@ -98,7 +99,7 @@ fun Route.configureEventFightRoutes() { } val eventFight = EventFight.create( - fight.event, + event.eventID, Timestamp.from(Instant.ofEpochMilli(fight.start)), fight.spielmodus, fight.map, @@ -129,9 +130,9 @@ fun Route.configureEventFightRoutes() { if (updateFight.group != null) { if (updateFight.group == -1) { - fight.groupId = null + fight.setGroup(null) } else { - fight.groupId = updateFight.group + fight.setGroup(updateFight.group) } } From a5bb62590ca66dcd9aca71bf8ac185653641867b Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 28 May 2025 23:57:29 +0200 Subject: [PATCH 7/9] Refactor page routing and point calculation logic Streamlined the `page` routing structure by optimizing branch and file handling, introducing a reusable `filesInDirectory` method, and cleaning up redundancies. Enhanced `EventGroup` point calculation with incremental updates, new helper methods (`getTeams`, `getTeamsId`), and better handling of unfinished fights. --- .../SQL/src/de/steamwar/sql/EventGroup.java | 34 +-- .../src/de/steamwar/routes/EventFights.kt | 6 +- WebsiteBackend/src/de/steamwar/routes/Page.kt | 195 ++++++++++-------- 3 files changed, 130 insertions(+), 105 deletions(-) diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java index 166e7eb1..68934765 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventGroup.java @@ -23,10 +23,9 @@ import de.steamwar.sql.internal.*; import lombok.Getter; import lombok.Setter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Getter @Setter @@ -94,6 +93,15 @@ public class EventGroup { 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); } @@ -104,13 +112,16 @@ public class EventGroup { public Map calculatePoints() { if (points == null) { - Map teams = new HashMap<>(); - points = new HashMap<>(); + 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; @@ -128,19 +139,18 @@ public class EventGroup { break; } - Team blueTeam = teams.computeIfAbsent(fight.getTeamBlue(), Team::get); - Team redTeam = teams.computeIfAbsent(fight.getTeamRed(), Team::get); - - points.put(blueTeam, points.getOrDefault(blueTeam, 0) + blueTeamAdd); - points.put(redTeam, points.getOrDefault(redTeam, 0) + redTeamAdd); + 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(id, name, type, pointsPerWin, pointsPerLoss, pointsPerDraw); + update.update(name, type, pointsPerWin, pointsPerLoss, pointsPerDraw, id); this.name = name; this.type = type; this.pointsPerWin = pointsPerWin; diff --git a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt index 4823619f..d1974686 100644 --- a/WebsiteBackend/src/de/steamwar/routes/EventFights.kt +++ b/WebsiteBackend/src/de/steamwar/routes/EventFights.kt @@ -40,7 +40,8 @@ data class ResponseEventFight( val start: Long, val ergebnis: Int, val spectatePort: Int?, - val group: ResponseGroups? + val group: ResponseGroups?, + val hasFinished: Boolean ) { constructor(eventFight: EventFight) : this( eventFight.fightID, @@ -51,7 +52,8 @@ data class ResponseEventFight( eventFight.startTime.time, eventFight.ergebnis, eventFight.spectatePort, - eventFight.group.orElse(null)?.let { ResponseGroups(it) } + eventFight.group.orElse(null)?.let { ResponseGroups(it, short = true) }, + eventFight.hasFinished() ) } diff --git a/WebsiteBackend/src/de/steamwar/routes/Page.kt b/WebsiteBackend/src/de/steamwar/routes/Page.kt index 6f9a9fc4..139814c7 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,9 +36,7 @@ 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.util.Base64 @@ -113,65 +110,42 @@ 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 @@ -197,55 +171,94 @@ fun Route.configurePage() { """.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)) } - - call.respond(res.status) } } } \ No newline at end of file From 2f93f336c9df7da583569bd03180763cf3b47702 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 29 May 2025 14:30:06 +0200 Subject: [PATCH 8/9] Add image upload functionality and enhance page metadata creation. --- WebsiteBackend/src/de/steamwar/routes/Page.kt | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/WebsiteBackend/src/de/steamwar/routes/Page.kt b/WebsiteBackend/src/de/steamwar/routes/Page.kt index 139814c7..083d8dc4 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Page.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Page.kt @@ -38,7 +38,11 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.Serializable 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 @@ -87,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) @@ -99,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) { @@ -148,8 +158,6 @@ fun Route.configurePage() { }) } 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/")) { @@ -164,7 +172,10 @@ 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} @@ -259,6 +270,21 @@ fun Route.configurePage() { call.respond(filesInDirectory("/src/images", branch)) } + post { + val req = call.receive() + + 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 From 1f1f99f8f318944b008b843b6533f4f63fec4613 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 26 Jun 2025 23:38:19 +0200 Subject: [PATCH 9/9] Adjust advancing team logic in EventRelation to fix `fromPlace` handling --- CommonCore/SQL/src/de/steamwar/sql/EventRelation.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java index 28c756ee..d9fde245 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java +++ b/CommonCore/SQL/src/de/steamwar/sql/EventRelation.java @@ -133,7 +133,7 @@ public class EventRelation { public Optional getAdvancingTeam() { if (fromType == FromType.FIGHT) { - if (fromPlace == 1) { + if (fromPlace == 0) { return getFromFight().flatMap(EventFight::getWinner); } else { return getFromFight().flatMap(EventFight::getLosser); @@ -141,7 +141,9 @@ public class EventRelation { } else if (fromType == FromType.GROUP) { return getFromGroup().map(EventGroup::calculatePoints) .flatMap(points -> points.entrySet().stream() - .max(Map.Entry.comparingByValue()) + .sorted(Map.Entry.comparingByValue()) + .skip(fromPlace) + .findFirst() .map(Map.Entry::getKey)); } else { return Optional.empty();