Commissioners can now opt in to having a draft start automatically at its scheduled draftDateTime rather than requiring a manual "Start Draft" click. The 1-second timer loop checks for eligible seasons each tick and calls the new shared startDraft() service, which is also used by the existing commissioner HTTP route. - Add autoStartDraft boolean to seasons table (migration 0108) - Extract core start logic into app/services/draft-autostart.ts so both the API route and the timer can call it without AsyncLocalStorage - Add checkAndAutoStartDrafts() to the timer tick; disables autoStartDraft and stops retrying if no draft order is set at fire time - Guard against null draftDateTime in both the timer query (isNotNull) and server actions (autoStartDraft forced false when datetime is null) - Add "Auto-start at scheduled time" checkbox to league creation wizard (step 3) and league settings, gated on date+time being set - Show countdown + "Start Now" button in draft room when auto-start is scheduled; reuses the existing nowForPauseCheck 1-second ticker - Show orange warning banner on league homepage to commissioners when auto-start is within 1 hour and no draft order has been configured Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
62 lines
1.9 KiB
TypeScript
62 lines
1.9 KiB
TypeScript
import { auth } from "~/lib/auth.server";
|
|
import { database } from "~/database/context";
|
|
import * as schema from "~/database/schema";
|
|
import { eq } from "drizzle-orm";
|
|
import { isCommissioner } from "~/models/commissioner";
|
|
import { findUserById, getUserDisplayName } from "~/models/user";
|
|
import { getSocketIO } from "../../../server/socket";
|
|
import { startDraft } from "~/services/draft-autostart";
|
|
|
|
import type { ActionFunctionArgs } from "react-router";
|
|
|
|
export async function action(args: ActionFunctionArgs) {
|
|
const { request } = args;
|
|
const session = await auth.api.getSession({ headers: request.headers });
|
|
const userId = session?.user.id ?? null;
|
|
|
|
if (!userId) {
|
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const formData = await request.formData();
|
|
const seasonId = formData.get("seasonId") as string;
|
|
|
|
if (!seasonId) {
|
|
return Response.json({ error: "Missing seasonId" }, { status: 400 });
|
|
}
|
|
|
|
const db = database();
|
|
|
|
const season = await db.query.seasons.findFirst({
|
|
where: eq(schema.seasons.id, seasonId),
|
|
});
|
|
|
|
if (!season) {
|
|
return Response.json({ error: "Season not found" }, { status: 404 });
|
|
}
|
|
|
|
if (!(await isCommissioner(season.leagueId, userId))) {
|
|
return Response.json({ error: "Only commissioners can start the draft" }, { status: 403 });
|
|
}
|
|
|
|
const user = await findUserById(userId);
|
|
const actorDisplayName = user ? (getUserDisplayName(user) ?? userId) : userId;
|
|
|
|
const result = await startDraft({
|
|
seasonId,
|
|
actorUserId: userId,
|
|
actorDisplayName,
|
|
db,
|
|
io: getSocketIO(),
|
|
});
|
|
|
|
if (!result.success) {
|
|
const status =
|
|
result.error === "Draft already started or completed" ? 400
|
|
: result.error === "No draft slots found for this season" ? 400
|
|
: 500;
|
|
return Response.json({ error: result.error }, { status });
|
|
}
|
|
|
|
return Response.json({ success: true });
|
|
}
|