import * as schema from "~/database/schema"; import { eq } from "drizzle-orm"; import { logger } from "~/lib/logger"; import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; import type { Server as SocketIOServer } from "socket.io"; type Db = PostgresJsDatabase; export interface StartDraftOptions { seasonId: string; actorUserId: string; actorDisplayName: string; db: Db; io: SocketIOServer; } export interface StartDraftResult { success: boolean; error?: string; } /** * Core draft-start logic shared by the commissioner HTTP route and the * auto-start timer. Accepts `db` directly (not via AsyncLocalStorage) so it * works both inside React Router request context and in the server timer loop. */ export async function startDraft(options: StartDraftOptions): Promise { const { seasonId, actorUserId, actorDisplayName, db, io } = options; const season = await db.query.seasons.findFirst({ where: eq(schema.seasons.id, seasonId), }); if (!season) { return { success: false, error: "Season not found" }; } // Idempotency guard — status may have changed between the outer check and here if (season.status !== "pre_draft") { return { success: false, error: "Draft already started or completed" }; } const draftSlots = await db.query.draftSlots.findMany({ where: eq(schema.draftSlots.seasonId, seasonId), }); if (draftSlots.length === 0) { return { success: false, error: "No draft slots found for this season" }; } await db .update(schema.seasons) .set({ status: "draft", currentPickNumber: 1, draftStartedAt: new Date(), }) .where(eq(schema.seasons.id, seasonId)); // Standard mode: each pick gets exactly the increment time. // Chess clock mode: each team starts with the full initial bank. const initialTime = season.draftTimerMode === "standard" ? season.draftIncrementTime || 30 : season.draftInitialTime || 120; await db.delete(schema.draftTimers).where(eq(schema.draftTimers.seasonId, seasonId)); await db.insert(schema.draftTimers).values( draftSlots.map((slot) => ({ seasonId, teamId: slot.teamId, timeRemaining: initialTime, })) ); await db.insert(schema.commissionerAuditLog).values({ seasonId, leagueId: season.leagueId, actorUserId, actorDisplayName, action: "draft_started", affectedTeamIds: [], details: { pickNumber: 1, autoStarted: actorUserId === "system" }, }); try { io.to(`draft-${seasonId}`).emit("draft-started", { seasonId, currentPickNumber: 1, }); } catch (err) { // Socket emit failure is non-fatal; DB state is already committed logger.error("[startDraft] Socket emit failed:", err); } return { success: true }; }