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 }); const tournament = await pbAdmin.getTournament(tournamentId); if (!tournament) { throw new Error('Tournament not found'); } if (tournament.matches && tournament.matches.length > 0) { throw new Error('Tournament already has matches generated'); } 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; const seedToTeamId = new Map(); orderedTeamIds.forEach((teamId, index) => { seedToTeamId.set(index + 1, teamId); }); const matchInputs: MatchInput[] = []; 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, status: "tbd", tournament: tournamentId, }; 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; } } if (matchInput.home && matchInput.away) { matchInput.status = "ready" } matchInputs.push(matchInput); }); }); 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, status: "tbd", tournament: tournamentId, }; matchInputs.push(matchInput); }); }); const createdMatches = await pbAdmin.createMatches(matchInputs); 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, }; }) ); export const startMatch = createServerFn() .validator(z.string()) .middleware([superTokensAdminFunctionMiddleware]) .handler(async ({ data }) => toServerResult(async () => { logger.info('Starting match', data); let match = await pbAdmin.getMatch(data); if (!match) { throw new Error('Match not found'); } match = await pbAdmin.updateMatch(data, { start_time: new Date().toISOString(), status: "started" }); return match; } )); const endMatchSchema = z.object({ matchId: z.string(), home_cups: z.number(), away_cups: z.number(), ot_count: z.number() }); export const endMatch = createServerFn() .validator(endMatchSchema) .middleware([superTokensAdminFunctionMiddleware]) .handler(async ({ data: { matchId, home_cups, away_cups, ot_count } }) => toServerResult(async () => { logger.info('Ending match', matchId); let match = await pbAdmin.getMatch(matchId); if (!match) { throw new Error('Match not found'); } match = await pbAdmin.updateMatch(matchId, { end_time: new Date().toISOString(), status: "ended", home_cups, away_cups, ot_count }) } ));