Phase 2 — Match Sync Adapters + Cron Job: - PandaScoreMatchSyncAdapter (CS2): fetches matches with map-level sub-games, stage detection - EspnScheduleAdapter (MLB/NBA/MLS/WNBA/NHL): fetches scoreboard with live scores - syncMatches() orchestrator: resolves participants by externalId+name, bulk-upserts season_matches, syncs playoff bracket results through existing setMatchWinner/processMatchResult pipeline - POST /admin/jobs/sync-matches cron endpoint (mirrors sync-and-simulate pattern) - External Season ID field added to sports season create/edit admin forms - Sync from API button wired in CS2 setup page (enabled when externalSeasonId set) Phase 3 — Public Tournament & Schedule Display: - MatchSchedule component: generic match list with live/scheduled/complete status badges, matchday grouping - Cs2TournamentBracket component: tab layout (Opening/Challengers/Legends/Champions Stage), map scores per match - /sports-seasons/:sportsSeasonId/tournament public route with 30-second live polling Phase 4 — Playoff Bracket Auto-Sync: - externalMatchId column added to playoff_matches table (migration 0121) - Bracket matches (matchStage=null) auto-synced: matches existing playoff_match rows by externalMatchId then participant IDs, calls full scoring pipeline - autoCompleteRoundIfDone extracted to scoring-calculator.ts for shared use All 2365 tests pass; typecheck clean. https://claude.ai/code/session_01WUUM7uWzFoSkGcZRhnEKG6
38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
import { isNotNull } from "drizzle-orm";
|
|
import { database } from "~/database/context";
|
|
import * as schema from "~/database/schema";
|
|
import { requireCronSecret } from "~/lib/cron-auth";
|
|
import { syncMatches } from "~/services/match-sync";
|
|
|
|
export async function action({ request }: { request: Request }) {
|
|
requireCronSecret(request);
|
|
|
|
const db = database();
|
|
const seasons = await db.query.sportsSeasons.findMany({
|
|
where: isNotNull(schema.sportsSeasons.externalSeasonId),
|
|
with: { sport: true },
|
|
});
|
|
|
|
const synced: string[] = [];
|
|
const errors: { id: string; name: string; error: string }[] = [];
|
|
|
|
for (const season of seasons) {
|
|
try {
|
|
const result = await syncMatches(season.id);
|
|
synced.push(season.id);
|
|
if (result.errors.length > 0) {
|
|
for (const e of result.errors) {
|
|
errors.push({ id: season.id, name: season.name, error: e.error });
|
|
}
|
|
}
|
|
} catch (err) {
|
|
errors.push({
|
|
id: season.id,
|
|
name: season.name,
|
|
error: err instanceof Error ? err.message : String(err),
|
|
});
|
|
}
|
|
}
|
|
|
|
return Response.json({ synced, errors }, { status: errors.length > 0 && synced.length === 0 ? 500 : 200 });
|
|
}
|