mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-27 14:04:01 -05:00
168 lines
4.6 KiB
TypeScript
168 lines
4.6 KiB
TypeScript
import { clearCombinedStreamsCache } from "~/features/core/streams/streams.server";
|
|
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
|
import { getTentativeTier } from "~/features/tournament-organization/core/tentativeTiers.server";
|
|
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
|
|
import { isAdmin } from "~/modules/permissions/utils";
|
|
import { notFoundIfFalsy } from "~/utils/remix.server";
|
|
import type { Unwrapped } from "~/utils/types";
|
|
import { getServerTournamentManager } from "./brackets-manager/manager.server";
|
|
import { RunningTournaments } from "./RunningTournaments.server";
|
|
import { Tournament } from "./Tournament";
|
|
|
|
const manager = getServerTournamentManager();
|
|
|
|
export const tournamentManagerData = (tournamentId: number) =>
|
|
manager.get.tournamentData(tournamentId);
|
|
|
|
const combinedTournamentData = async (tournamentId: number) => {
|
|
const ctx = await TournamentRepository.findById(tournamentId);
|
|
if (!ctx) return null;
|
|
|
|
return {
|
|
data: tournamentManagerData(tournamentId),
|
|
ctx,
|
|
};
|
|
};
|
|
|
|
export type TournamentData = NonNullable<Unwrapped<typeof tournamentData>>;
|
|
export type TournamentDataTeam = TournamentData["ctx"]["teams"][number];
|
|
export async function tournamentData({
|
|
user,
|
|
tournamentId,
|
|
}: {
|
|
user?: { id: number };
|
|
tournamentId: number;
|
|
}) {
|
|
const data = await combinedTournamentData(tournamentId);
|
|
if (!data) return null;
|
|
|
|
return dataMapped({ user, ...data });
|
|
}
|
|
|
|
function dataMapped({
|
|
data,
|
|
ctx,
|
|
user,
|
|
}: {
|
|
data: TournamentManagerDataSet;
|
|
ctx: TournamentRepository.FindById;
|
|
user?: { id: number };
|
|
}) {
|
|
const tournamentHasStarted = data.stage.length > 0;
|
|
const isOrganizer =
|
|
ctx.author.id === user?.id ||
|
|
ctx.staff.some(
|
|
(staff) => staff.id === user?.id && staff.role === "ORGANIZER",
|
|
) ||
|
|
isAdmin(user);
|
|
const revealInfo = tournamentHasStarted || isOrganizer;
|
|
|
|
const tentativeTier =
|
|
!ctx.tier && ctx.organization?.id
|
|
? getTentativeTier(ctx.organization.id, ctx.name)
|
|
: null;
|
|
|
|
return {
|
|
data,
|
|
ctx: {
|
|
...ctx,
|
|
tentativeTier,
|
|
teams: ctx.teams.map((team) => {
|
|
const isOwnTeam = team.members.some(
|
|
(member) => member.userId === user?.id,
|
|
);
|
|
|
|
return {
|
|
...team,
|
|
mapPool: revealInfo || isOwnTeam ? team.mapPool : null,
|
|
pickupAvatarUrl:
|
|
revealInfo || isOwnTeam ? team.pickupAvatarUrl : null,
|
|
inviteCode: isOwnTeam ? team.inviteCode : null,
|
|
};
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function tournamentFromDB(args: {
|
|
user: { id: number } | undefined;
|
|
tournamentId: number;
|
|
}) {
|
|
const data = notFoundIfFalsy(await tournamentData(args));
|
|
|
|
const tournament = new Tournament({ ...data, simulateBrackets: false });
|
|
syncTournamentToRegistry(tournament);
|
|
|
|
return tournament;
|
|
}
|
|
|
|
export async function tournamentFromDBCached(args: {
|
|
user: { id: number } | undefined;
|
|
tournamentId: number;
|
|
}) {
|
|
const data = notFoundIfFalsy(await tournamentDataCached(args));
|
|
|
|
return new Tournament({ ...data, simulateBrackets: false });
|
|
}
|
|
|
|
// caching promise ensures that if many requests are made for the same tournament
|
|
// at the same time they reuse the same resolving promise
|
|
const tournamentDataCache = new Map<
|
|
number,
|
|
ReturnType<typeof combinedTournamentData>
|
|
>();
|
|
export async function tournamentDataCached({
|
|
user,
|
|
tournamentId,
|
|
}: {
|
|
user?: { id: number };
|
|
tournamentId: number;
|
|
}) {
|
|
if (!tournamentDataCache.has(tournamentId)) {
|
|
tournamentDataCache.set(tournamentId, combinedTournamentData(tournamentId));
|
|
}
|
|
|
|
const data = notFoundIfFalsy(await tournamentDataCache.get(tournamentId));
|
|
|
|
return dataMapped({ user, ...data });
|
|
}
|
|
|
|
export function clearTournamentDataCache(tournamentId: number) {
|
|
tournamentDataCache.delete(tournamentId);
|
|
}
|
|
|
|
export function clearAllTournamentDataCache() {
|
|
tournamentDataCache.clear();
|
|
}
|
|
|
|
function syncTournamentToRegistry(tournament: Tournament) {
|
|
const isRunning = tournament.hasStarted && !tournament.everyBracketOver;
|
|
const wasInRegistry = RunningTournaments.has(tournament.ctx.id);
|
|
|
|
if (isRunning) {
|
|
RunningTournaments.add(tournament);
|
|
if (!wasInRegistry) {
|
|
clearCombinedStreamsCache();
|
|
}
|
|
} else {
|
|
if (wasInRegistry) {
|
|
clearCombinedStreamsCache();
|
|
}
|
|
RunningTournaments.remove(tournament.ctx.id);
|
|
}
|
|
}
|
|
|
|
async function primeRunningTournamentsCache() {
|
|
const tournamentIds = await TournamentRepository.findRunningTournamentIds();
|
|
|
|
for (const tournamentId of tournamentIds) {
|
|
const data = await tournamentData({ user: undefined, tournamentId });
|
|
if (!data) continue;
|
|
|
|
const tournament = new Tournament({ ...data, simulateBrackets: false });
|
|
syncTournamentToRegistry(tournament);
|
|
}
|
|
}
|
|
|
|
await primeRunningTournamentsCache();
|