Fix server restarting mid-set changing map list

This commit is contained in:
Kalle
2026-08-09 13:25:14 +03:00
parent 2293110da2
commit 8c03043f08
6 changed files with 83 additions and 4 deletions

View File

@@ -44,7 +44,7 @@ const ROUND_MAPS = {
} satisfies RoundMaps;
/** The maps every round of a factory-started bracket is played on. */
type RoundMaps = Omit<Engine.RoundMapsInput, "roundId">;
export type RoundMaps = Omit<Engine.RoundMapsInput, "roundId">;
/** The wrapping calendar event is not the caller's to choose, so it is not an argument. */
type InsertArgs = Omit<

View File

@@ -123,6 +123,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
match.mapPickingStyle !== "TO"
? await TournamentTeamRepository.findRecentlyPlayedMapsByIds({
teamIds: [opponentOne.id, opponentTwo.id],
excludeMatchId: id,
}).catch((error) => {
logger.error("Failed to fetch recently played maps", error);
return [];

View File

@@ -117,6 +117,7 @@ export async function resolveMatchMapList({
match.mapPickingStyle !== "TO"
? await TournamentTeamRepository.findRecentlyPlayedMapsByIds({
teamIds: teams,
excludeMatchId: match.id,
}).catch((error) => {
logger.error("Failed to fetch recently played maps", error);
return [];

View File

@@ -143,6 +143,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
match.mapPickingStyle !== "TO"
? await TournamentTeamRepository.findRecentlyPlayedMapsByIds({
teamIds: [match.opponentOne.id, match.opponentTwo.id],
excludeMatchId: matchId,
}).catch((error) => {
logger.error("Failed to fetch recently played maps", error);
return [];

View File

@@ -3,9 +3,40 @@ import * as TournamentFactory from "~/db/seed/factories/TournamentFactory";
import * as TournamentTeamFactory from "~/db/seed/factories/TournamentTeamFactory";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import { db } from "~/db/sql";
import type { TournamentSettings } from "~/db/tables-json";
import { withUserId } from "~/utils/Test";
import * as TournamentTeamRepository from "./TournamentTeamRepository.server";
const TEAM_COUNT = 4;
/** Pools of two teams each, followed by a final between the two pool winners. */
const POOLS_TO_FINAL: TournamentSettings["bracketProgression"] = [
{
name: "Pools",
type: "round_robin",
requiresCheckIn: false,
settings: { teamsPerGroup: 2 },
},
{
name: "Final",
type: "single_elimination",
requiresCheckIn: false,
settings: { thirdPlaceMatch: false },
sources: [{ bracketIdx: 0, placements: [1] }],
},
];
const POOL_MAPS: TournamentFactory.RoundMaps = {
count: 1,
type: "BEST_OF",
list: [{ mode: "SZ", stageId: 1 }],
};
const FINAL_MAPS: TournamentFactory.RoundMaps = {
count: 1,
type: "BEST_OF",
list: [{ mode: "TC", stageId: 2 }],
};
let organizer: { id: number };
let owner: { id: number };
let member: { id: number };
@@ -285,4 +316,39 @@ describe("TournamentTeamRepository", () => {
).toBe(0);
});
});
describe("findRecentlyPlayedMapsByIds", () => {
test("leaves out the games of the match the maps are resolved for", async () => {
// the map list of an in-progress set is regenerated whenever its cache entry
// is lost, so counting the set's own games as recently played would change
// the maps the teams have left to play under them
const players = await UserFactory.createMany(TEAM_COUNT);
const tournament = await TournamentFactory.createPlayed(
{
authorId: organizer.id,
bracketProgression: POOLS_TO_FINAL,
minMembersPerTeam: 1,
},
{
teamRosters: players.map((player) => [player.id]),
playedOut: 0,
maps: POOL_MAPS,
},
);
const [final] = await TournamentFactory.playOut(tournament.id, 1, {
maps: FINAL_MAPS,
});
const recentMaps =
await TournamentTeamRepository.findRecentlyPlayedMapsByIds({
teamIds: [final.winnerTeamId, final.loserTeamId],
excludeMatchId: final.id,
});
expect(recentMaps).toEqual([
{ mode: "SZ", stageId: 1 },
{ mode: "SZ", stageId: 1 },
]);
});
});
});

View File

@@ -999,7 +999,11 @@ async function replaceCounterpickMaps(
.execute();
}
async function findTeamRecentMaps(teamId: number, limit: number) {
async function findTeamRecentMaps(
teamId: number,
excludeMatchId: number,
limit: number,
) {
return db
.selectFrom("TournamentMatchGameResult")
.innerJoin(
@@ -1012,6 +1016,7 @@ async function findTeamRecentMaps(teamId: number, limit: number) {
"TournamentMatchGameResult.stageId",
])
.where("TournamentMatchGameResultParticipant.tournamentTeamId", "=", teamId)
.where("TournamentMatchGameResult.matchId", "!=", excludeMatchId)
.orderBy("TournamentMatchGameResult.createdAt", "desc")
.limit(limit)
.execute();
@@ -1058,10 +1063,15 @@ export async function findMapPoolsByTeamIds(tournamentTeamIds: number[]) {
export async function findRecentlyPlayedMapsByIds({
teamIds,
excludeMatchId,
limit = 5,
}: {
/** Team IDs to retrieve recent maps for */
teamIds: [number, number];
/** Match whose own games are left out, being the match the maps are resolved for.
* Without it a set's map list changes under the teams mid-set, as the games they
* already played would count as recently played when it is regenerated. */
excludeMatchId: number;
/** Limit of recent maps to retrieve per team
*
* @default 5
@@ -1069,8 +1079,8 @@ export async function findRecentlyPlayedMapsByIds({
limit?: number;
}): Promise<Array<{ mode: ModeShort; stageId: StageId }>> {
const [teamOneMaps, teamTwoMaps] = await Promise.all([
findTeamRecentMaps(teamIds[0], limit),
findTeamRecentMaps(teamIds[1], limit),
findTeamRecentMaps(teamIds[0], excludeMatchId, limit),
findTeamRecentMaps(teamIds[1], excludeMatchId, limit),
]);
return flatZip(teamOneMaps, teamTwoMaps);