better brackets, info types

This commit is contained in:
yohlo
2025-09-07 00:52:28 -05:00
parent cb83ea06fa
commit 2396464a19
36 changed files with 678 additions and 657 deletions

View File

@@ -0,0 +1,136 @@
import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger";
import { z } from "zod";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
import brackets from "@/features/bracket/utils";
import { MatchInput } from "@/features/matches/types";
const orderedTeamsSchema = z.object({
tournamentId: z.string(),
orderedTeamIds: z.array(z.string()),
});
export const generateTournamentBracket = createServerFn()
.validator(orderedTeamsSchema)
.middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data: { tournamentId, orderedTeamIds } }) =>
toServerResult(async () => {
logger.info('Generating tournament bracket', { tournamentId, teamCount: orderedTeamIds.length });
// Get tournament with teams
const tournament = await pbAdmin.getTournament(tournamentId);
if (!tournament) {
throw new Error('Tournament not found');
}
// Check if tournament already has matches
if (tournament.matches && tournament.matches.length > 0) {
throw new Error('Tournament already has matches generated');
}
// Get bracket template based on team count
const teamCount = orderedTeamIds.length;
if (!Object.keys(brackets).includes(teamCount.toString())) {
throw new Error(`Bracket not available for ${teamCount} teams`);
}
const bracketTemplate = brackets[teamCount as keyof typeof brackets] as any;
// Create seed to team mapping (index + 1 = seed)
const seedToTeamId = new Map<number, string>();
orderedTeamIds.forEach((teamId, index) => {
seedToTeamId.set(index + 1, teamId);
});
// Convert bracket template to match records
const matchInputs: MatchInput[] = [];
// Process winners bracket
bracketTemplate.winners.forEach((round: any[]) => {
round.forEach((match: any) => {
const matchInput: MatchInput = {
lid: match.lid,
round: match.round,
order: match.order || 0,
reset: match.reset || false,
bye: match.bye || false,
home_cups: 0,
away_cups: 0,
ot_count: 0,
home_from_lid: match.home_from_lid,
away_from_lid: match.away_from_lid,
home_from_loser: match.home_from_loser || false,
away_from_loser: match.away_from_loser || false,
is_losers_bracket: false,
tournament: tournamentId,
};
// Assign teams based on seeds
if (match.home_seed) {
const teamId = seedToTeamId.get(match.home_seed);
if (teamId) {
matchInput.home = teamId;
matchInput.home_seed = match.home_seed;
}
}
if (match.away_seed) {
const teamId = seedToTeamId.get(match.away_seed);
if (teamId) {
matchInput.away = teamId;
matchInput.away_seed = match.away_seed;
}
}
matchInputs.push(matchInput);
});
});
// Process losers bracket
bracketTemplate.losers.forEach((round: any[]) => {
round.forEach((match: any) => {
const matchInput: MatchInput = {
lid: match.lid,
round: match.round,
order: match.order || 0,
reset: match.reset || false,
bye: match.bye || false,
home_cups: 0,
away_cups: 0,
ot_count: 0,
home_from_lid: match.home_from_lid,
away_from_lid: match.away_from_lid,
home_from_loser: match.home_from_loser || false,
away_from_loser: match.away_from_loser || false,
is_losers_bracket: true,
tournament: tournamentId,
};
// Losers bracket matches don't start with teams
// Teams come from winners bracket losses
matchInputs.push(matchInput);
});
});
// Create all matches
const createdMatches = await pbAdmin.createMatches(matchInputs);
// Update tournament to include all match IDs in the matches relation
const matchIds = createdMatches.map(match => match.id);
await pbAdmin.updateTournamentMatches(tournamentId, matchIds);
logger.info('Tournament bracket generated', {
tournamentId,
matchCount: createdMatches.length
});
return {
tournament,
matchCount: createdMatches.length,
matches: createdMatches,
};
})
);