import { getAuth } from "@clerk/react-router/server"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq } from "drizzle-orm"; import { deleteSeasonTimers, initializeDraftTimers } from "~/models/draft-timer"; import { isCommissioner } from "~/models/commissioner"; import { logCommissionerAction } from "~/models/audit-log"; import { getSocketIO } from "../../../server/socket"; import { logger } from "~/lib/logger"; import type { ActionFunctionArgs } from "react-router"; export async function action(args: ActionFunctionArgs) { const { request } = args; const { userId } = await getAuth(args); 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(); // Get season details const season = await db.query.seasons.findFirst({ where: eq(schema.seasons.id, seasonId), }); if (!season) { return Response.json({ error: "Season not found" }, { status: 404 }); } // Check if user is commissioner if (!(await isCommissioner(season.leagueId, userId))) { return Response.json({ error: "Only commissioners can start the draft" }, { status: 403 }); } // Check if draft already started if (season.status === "draft" || season.status === "active" || season.status === "completed") { return Response.json({ error: "Draft already started or completed" }, { status: 400 }); } // Validate draft slots exist before modifying any state const draftSlots = await db.query.draftSlots.findMany({ where: eq(schema.draftSlots.seasonId, seasonId), }); if (draftSlots.length === 0) { return Response.json({ error: "No draft slots found for this season" }, { status: 400 }); } // Update season status to draft await db .update(schema.seasons) .set({ status: "draft", currentPickNumber: 1, }) .where(eq(schema.seasons.id, seasonId)); // Standard mode: each pick starts with exactly the increment (no carry-over bank). // Chess clock mode: each team starts with the full initial time bank. const initialTime = season.draftTimerMode === "standard" ? season.draftIncrementTime || 30 : season.draftInitialTime || 120; // Reset timers for all teams await deleteSeasonTimers(seasonId); await initializeDraftTimers( seasonId, draftSlots.map((slot) => ({ id: slot.teamId })), initialTime ); await logCommissionerAction({ seasonId, leagueId: season.leagueId, actorClerkId: userId, action: "draft_started", details: { pickNumber: 1 }, }); // Emit socket event try { getSocketIO().to(`draft-${seasonId}`).emit("draft-started", { seasonId, currentPickNumber: 1, }); } catch (error) { logger.error("Socket.IO error:", error); } return Response.json({ success: true }); }