brackt/app/routes/admin/jobs.sync-and-simulate.ts
Chris Parsons 52eb3abac1
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m45s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m29s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
Fix three code review findings from Phase A cron jobs
- Remove `| cat` from both Forgejo workflow curl commands so HTTP 4xx/5xx
  causes the step to fail (bash pipeline exit code was always 0 via cat)
- Simplify sync-and-simulate: drop redundant pre-check DB query for
  standingsLastChangedAt; changed===true already means standings updated
  this run, so always simulate (runner's simulationStatus guard handles overlap)
- Delete server/snapshots.ts — all exports were orphaned after startSnapshotSystem
  was removed from server/socket.ts in the Phase A commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 00:35:02 -07:00

61 lines
2.2 KiB
TypeScript

import { database } from "~/database/context";
import { eq } from "drizzle-orm";
import * as schema from "~/database/schema";
import { findSportsSeasonsByStatus } from "~/models/sports-season";
import { syncStandings } from "~/services/standings-sync";
import { runSportsSeasonSimulation } from "~/services/simulations/runner";
import { requireCronSecret } from "~/lib/cron-auth";
export async function action({ request }: { request: Request }) {
requireCronSecret(request);
const activeSeasons = await findSportsSeasonsByStatus("active");
const synced: string[] = [];
const unchanged: string[] = [];
const simulated: string[] = [];
const syncErrors: { id: string; error: string }[] = [];
const simErrors: { id: string; error: string }[] = [];
for (const season of activeSeasons) {
let changed = false;
try {
const result = await syncStandings(season.id);
changed = result.changed;
if (changed) {
synced.push(season.id);
} else {
unchanged.push(season.id);
}
} catch (err) {
// Sports without a sync adapter (F1, IndyCar, etc.) throw — skip gracefully
syncErrors.push({ id: season.id, error: err instanceof Error ? err.message : String(err) });
continue;
}
if (!changed) continue;
// changed === true means syncStandings() detected new data this run, so simulate.
// Overlap is guarded inside runSportsSeasonSimulation (throws if simulationStatus === "running").
try {
await runSportsSeasonSimulation(season.id);
await database()
.update(schema.sportsSeasons)
.set({ lastSimulatedAt: new Date() })
.where(eq(schema.sportsSeasons.id, season.id));
simulated.push(season.id);
} catch (err) {
// Seasons without a simulator config or that fail readiness checks throw — skip gracefully
simErrors.push({ id: season.id, error: err instanceof Error ? err.message : String(err) });
}
}
const hasErrors = syncErrors.length > 0 || simErrors.length > 0;
const hasSuccess = synced.length > 0 || unchanged.length > 0;
const status = hasErrors && !hasSuccess ? 500 : 200;
return Response.json(
{ synced, unchanged, simulated, syncErrors, simErrors },
{ status }
);
}