import { auth } from "~/lib/auth.server"; import type { Route } from "./+types/$leagueId.sports-seasons.$sportsSeasonId.events.$eventId"; import { findLeagueById, isUserLeagueMember, isCommissioner, findTeamsBySeasonId, getUserDisplayName, } from "~/models"; import { getDraftPicks } from "~/models/draft-pick"; import { getScoringEventById, getPrimaryEventForTournament, isReadOnlySibling } from "~/models/scoring-event"; import { findParticipantsBySportsSeasonId } from "~/models/season-participant"; import { getCs2StageResultsForEvent } from "~/models/cs2-major-stage"; import { findSeasonMatchesByScoringEventId } from "~/models/season-match"; import { getEventResults } from "~/models/event-result"; import { getBracketTemplate, getOrderedRoundsFromMatches } from "~/lib/bracket-templates"; import { findGroupsByEventId } from "~/models/tournament-group"; import { findMatchesByGroupIds, computeGroupStandings } from "~/models/group-stage-match"; import type { PlayoffMatch } from "~/models/playoff-match"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq, and, inArray } from "drizzle-orm"; export async function loader({ params, request }: Route.LoaderArgs) { const session = await auth.api.getSession({ headers: request.headers }); const userId = session?.user.id ?? null; const { leagueId, sportsSeasonId, eventId } = params; if (!userId) { throw new Response("You must be logged in to view this page", { status: 401 }); } const league = await findLeagueById(leagueId); if (!league) throw new Response("League not found", { status: 404 }); const [isUserCommissioner, isUserMember] = await Promise.all([ isCommissioner(leagueId, userId), isUserLeagueMember(leagueId, userId), ]); if (!isUserCommissioner && !isUserMember) { throw new Response("You do not have access to this league", { status: 403 }); } const db = database(); const [scoringEvent, sportsSeason] = await Promise.all([ getScoringEventById(eventId), db.query.sportsSeasons.findFirst({ where: eq(schema.sportsSeasons.id, sportsSeasonId), with: { sport: true, seasonSports: { with: { season: true } } }, }), ]); if (!scoringEvent) throw new Response("Event not found", { status: 404 }); if (!sportsSeason) throw new Response("Sports season not found", { status: 404 }); if (scoringEvent.sportsSeasonId !== sportsSeasonId) { throw new Response("Event does not belong to this sports season", { status: 404 }); } const seasonSport = sportsSeason.seasonSports?.find( (ss) => ss.season.leagueId === leagueId ); if (!seasonSport) { throw new Response("This sports season is not linked to this league", { status: 404 }); } const season = seasonSport.season; // Build team ownership map const teams = await findTeamsBySeasonId(season.id); const draftPicks = await getDraftPicks(season.id); const ownerIds = [...new Set(teams.map((t) => t.ownerId).filter(Boolean))] as string[]; const ownerUserRows = ownerIds.length > 0 ? await db.query.users.findMany({ where: inArray(schema.users.id, ownerIds), columns: { id: true, username: true, displayName: true }, }) : []; const ownerMap = new Map(ownerUserRows.map((u) => [u.id, getUserDisplayName(u) ?? "Unknown"])); const ownershipMap = new Map(); for (const pick of draftPicks) { const team = teams.find((t) => t.id === pick.teamId); if (team && pick.participantId) { ownershipMap.set(pick.participantId, { teamName: team.name, teamId: team.id, ownerName: team.ownerId ? ownerMap.get(team.ownerId) : undefined, }); } } const teamOwnerships = Array.from(ownershipMap.entries()).map(([participantId, data]) => ({ participantId, ...data, })); const userTeams = teams.filter((t) => t.ownerId === userId); const userTeamIds = new Set(userTeams.map((t) => t.id)); const userParticipantIds = draftPicks .filter((p) => p.teamId && userTeamIds.has(p.teamId) && p.participantId) .map((p) => p.participantId as string); const simulatorType = sportsSeason.sport?.simulatorType ?? null; // Shared-major fan-out for DISPLAY: a tournament-linked, non-primary event has // no bracket/stage rows of its own — those live on the primary window. Read the // structure from the primary, then re-key this league's ownership overlay from // its own season_participant ids to the PRIMARY's season_participant ids (the // ids the bracket structure uses) via the shared canonical participant. QP/ // results branches keep using this window's local rows and are untouched. let structureEventId = eventId; let structureBracketTemplateId = scoringEvent.bracketTemplateId; let bracketTeamOwnerships = teamOwnerships; let bracketUserParticipantIds = userParticipantIds; if (isReadOnlySibling(scoringEvent) && scoringEvent.tournamentId) { const primaryEvent = await getPrimaryEventForTournament(scoringEvent.tournamentId); if (primaryEvent && primaryEvent.id !== eventId) { structureEventId = primaryEvent.id; structureBracketTemplateId = primaryEvent.bracketTemplateId; const [siblingParticipants, primaryParticipants] = await Promise.all([ findParticipantsBySportsSeasonId(sportsSeasonId), findParticipantsBySportsSeasonId(primaryEvent.sportsSeasonId), ]); // sibling season_participant id → canonical participant id const siblingSpToCanonical = new Map( siblingParticipants .filter((p) => p.participantId) .map((p) => [p.id, p.participantId as string]) ); // canonical participant id → primary season_participant id const canonicalToPrimarySp = new Map( primaryParticipants .filter((p) => p.participantId) .map((p) => [p.participantId as string, p.id]) ); const toPrimarySp = (siblingSpId: string): string | undefined => { const canonical = siblingSpToCanonical.get(siblingSpId); return canonical ? canonicalToPrimarySp.get(canonical) : undefined; }; bracketTeamOwnerships = teamOwnerships.flatMap((o) => { const primarySpId = toPrimarySp(o.participantId); return primarySpId ? [{ ...o, participantId: primarySpId }] : []; }); bracketUserParticipantIds = userParticipantIds.flatMap((id) => { const primarySpId = toPrimarySp(id); return primarySpId ? [primarySpId] : []; }); } } type PlayoffMatchWithRelations = Omit & { createdAt: string; updatedAt: string; participant1: { id: string; name: string } | null; participant2: { id: string; name: string } | null; winner: { id: string; name: string } | null; loser: { id: string; name: string } | null; }; type RawGroupMatch = Awaited> extends Map> ? T : never; type GroupStandingData = { groupName: string; standings: ReturnType; matches: Array & { scheduledAt: string | null }>; }; // CS2 Major — load Swiss stage data + bracket if (simulatorType === "cs2_major_qualifying_points") { const [cs2StageResults, seasonMatches, playoffMatchRows] = await Promise.all([ getCs2StageResultsForEvent(structureEventId), findSeasonMatchesByScoringEventId(structureEventId), db.query.playoffMatches.findMany({ where: eq(schema.playoffMatches.scoringEventId, structureEventId), with: { participant1: true, participant2: true, winner: true, loser: true }, }), ]); const playoffMatches: PlayoffMatchWithRelations[] = playoffMatchRows.map((m) => ({ ...m, createdAt: m.createdAt.toISOString(), updatedAt: m.updatedAt.toISOString(), })); const templateId = structureBracketTemplateId ?? undefined; const template = templateId ? getBracketTemplate(templateId) : undefined; const playoffRounds = getOrderedRoundsFromMatches(playoffMatches, template); return { kind: "cs2" as const, league, sportsSeason, scoringEvent, teamOwnerships: bracketTeamOwnerships, userParticipantIds: bracketUserParticipantIds, cs2StageResults, seasonMatches: seasonMatches.map((m) => ({ ...m, scheduledAt: m.scheduledAt ? m.scheduledAt.toISOString() : null, startedAt: m.startedAt ? m.startedAt.toISOString() : null, completedAt: m.completedAt ? m.completedAt.toISOString() : null, })), playoffMatches, playoffRounds, bracketTemplateId: templateId ?? null, }; } // Bracket events (tennis/golf bracket majors, soccer/UCL group+bracket) if (scoringEvent.eventType === "playoff_game") { const playoffMatchRows = await db.query.playoffMatches.findMany({ where: eq(schema.playoffMatches.scoringEventId, structureEventId), with: { participant1: true, participant2: true, winner: true, loser: true }, }); const playoffMatches: PlayoffMatchWithRelations[] = playoffMatchRows.map((m) => ({ ...m, createdAt: m.createdAt.toISOString(), updatedAt: m.updatedAt.toISOString(), })); const templateId = structureBracketTemplateId ?? undefined; const template = templateId ? getBracketTemplate(templateId) : undefined; const playoffRounds = getOrderedRoundsFromMatches(playoffMatches, template); let groupStandings: GroupStandingData[] = []; if (template?.groupStage) { const groups = await findGroupsByEventId(structureEventId); const matchesByGroupId = await findMatchesByGroupIds(groups.map((g) => g.id)); groupStandings = groups.map((group) => { const groupMatches = matchesByGroupId.get(group.id) ?? []; const members = (group.members ?? []) .filter((m): m is typeof m & { participant: NonNullable } => m.participant !== null) .map((m) => ({ participantId: m.participant.id, participantName: m.participant.name })); return { groupName: group.groupName, standings: computeGroupStandings( members, groupMatches.map((m) => ({ participant1Id: m.participant1Id, participant2Id: m.participant2Id, participant1Score: m.participant1Score, participant2Score: m.participant2Score, isComplete: m.isComplete, })) ), matches: groupMatches.map((m) => ({ ...m, scheduledAt: m.scheduledAt ? m.scheduledAt.toISOString() : null, })), }; }); } // Season participant results for scoring display const allResults = await db.query.seasonParticipantResults.findMany({ where: and( eq(schema.seasonParticipantResults.sportsSeasonId, sportsSeasonId), eq(schema.seasonParticipantResults.finalPosition, 0) ), with: { participant: true }, }); const preEliminatedParticipants = allResults .filter((r): r is typeof r & { participant: NonNullable } => r.participant !== null) .map((r) => ({ id: r.participant.id, name: r.participant.name })); return { kind: "bracket" as const, league, sportsSeason, scoringEvent, teamOwnerships: bracketTeamOwnerships, userParticipantIds: bracketUserParticipantIds, playoffMatches, playoffRounds, bracketTemplateId: templateId ?? null, groupStandings, preEliminatedParticipants, }; } // QP major tournament / racing / other const eventResultRows = await getEventResults(eventId); const eventResults = eventResultRows .filter((r): r is typeof r & { placement: number } => r.placement !== null && !r.notParticipating) .map((r) => ({ id: r.id, placement: r.placement, qualifyingPointsAwarded: r.qualifyingPointsAwarded, rawScore: r.rawScore, seasonParticipantId: r.seasonParticipantId, participantName: r.seasonParticipant?.name ?? null, })); return { kind: "results" as const, league, sportsSeason, scoringEvent, teamOwnerships, userParticipantIds, eventResults, }; }