- Fix 1 (CRITICAL): CS2 "Sync from API" button was always 401 because it POSTed to /admin/jobs/sync-matches which requires a cron secret header that browsers can't send. Added sync-matches intent to cs2-setup action. - Fix 2 (CRITICAL): loserAdvances was hardcoded false in match-sync/index.ts, breaking NBA Play-In scoring. Now calls doesLoserAdvance(). - Fix 3: ESPN score "0" was falsy → stored as null. Non-numeric strings like "F/OT" produced NaN. Added parseScore() helper. - Fix 4: ESPN limit=1000 silently truncated MLB/NBA playoff games. Added seasonType param to EspnScheduleAdapter; all *_bracket types pass 3 (postseason only). - Fix 5: Cron job was syncing completed seasons unnecessarily. Added active status filter. - Fix 6: hasLiveMatches triggered perpetual 30s polling for unseeded brackets. Added participant presence check. - Fix 7: autoCompleteRoundIfDone was duplicated between bracket.server.ts and scoring-calculator.ts. Removed inline copy, imported shared version. - Fix 8: MatchSchedule date grouping never ran for ESPN sports (no matchday field). Removed hasMatchdays gate, always call groupByMatchday with stable UTC date key. - Fix 9: Silent failure when bracket matches exist but no scoring events. Added warning log. - Fix 10: Added 3 tests — zero score preservation, NaN from non-numeric score, seasonType in URL. https://claude.ai/code/session_01WUUM7uWzFoSkGcZRhnEKG6
41 lines
1.2 KiB
TypeScript
41 lines
1.2 KiB
TypeScript
import { and, eq, 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: and(
|
|
isNotNull(schema.sportsSeasons.externalSeasonId),
|
|
eq(schema.sportsSeasons.status, "active")
|
|
),
|
|
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 });
|
|
}
|