- Add POST /admin/jobs/run-daily-snapshots (replaces 24h setInterval in server/snapshots.ts) - Add POST /admin/jobs/sync-and-simulate (syncs standings every 2h; only simulates when standings actually changed by comparing gamesPlayed + leagueRank before upserting) - Add standingsLastChangedAt + lastSimulatedAt columns to sportsSeasons (migration 0118) - Add GET /healthz route for Docker healthcheck (pings DB, returns 503 on failure) - Add app/lib/cron-auth.ts shared X-Cron-Secret header auth helper - Remove startSnapshotSystem() from server/socket.ts (no more in-process setInterval) - Add Forgejo Actions scheduled workflows: daily-snapshots.yml (00:05 UTC) and sync-and-simulate.yml (every 2h) — require CRON_SECRET repo secret Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
29 lines
993 B
TypeScript
29 lines
993 B
TypeScript
import { database } from "~/database/context";
|
|
import { eq, or } from "drizzle-orm";
|
|
import * as schema from "~/database/schema";
|
|
import { createDailySnapshot } from "~/models/standings";
|
|
import { requireCronSecret } from "~/lib/cron-auth";
|
|
|
|
export async function action({ request }: { request: Request }) {
|
|
requireCronSecret(request);
|
|
|
|
const db = database();
|
|
const activeSeasons = await db.query.seasons.findMany({
|
|
where: or(eq(schema.seasons.status, "active"), eq(schema.seasons.status, "draft")),
|
|
});
|
|
|
|
let succeeded = 0;
|
|
const errors: { id: string; error: string }[] = [];
|
|
|
|
for (const season of activeSeasons) {
|
|
try {
|
|
await createDailySnapshot(season.id, db);
|
|
succeeded++;
|
|
} catch (err) {
|
|
errors.push({ id: season.id, error: err instanceof Error ? err.message : String(err) });
|
|
}
|
|
}
|
|
|
|
const status = errors.length > 0 && succeeded === 0 ? 500 : 200;
|
|
return Response.json({ total: activeSeasons.length, succeeded, errors }, { status });
|
|
}
|