From 34da0594d1313bcda3b2905be797e42be244dfef Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:27:16 -0800 Subject: [PATCH] Fix draft timer creation for teams without existing timers (#26) * Create timer when adding time to a team with no existing timer When a commissioner tries to add time to a team that has no draft timer, instead of returning a 404 error, create a new timer record with the specified amount of time. Removing time still returns a 404 if no timer exists (nothing to remove from). https://claude.ai/code/session_016VpJKZZFNQQqzfmLu8pHoc * Fix silent timer failures and missing seasonId filter in timer model - draft.make-pick.ts: create a timer with the increment amount instead of logging a warning and silently skipping when no timer exists for the picking team - draft.force-manual-pick.ts: same fix for the commissioner force-pick path; also unconditionally emit the timer-update socket event so clients always see the updated time - models/draft-timer.ts: add seasonId parameter to getTeamTimer and updateTeamTimer so they cannot match the wrong season's timer when a team participates in multiple seasons https://claude.ai/code/session_016VpJKZZFNQQqzfmLu8pHoc --------- Co-authored-by: Claude --- app/models/draft-timer.ts | 12 ++++----- app/routes/api/draft.adjust-time-bank.ts | 25 ++++++++++++------ app/routes/api/draft.force-manual-pick.ts | 32 ++++++++++++++--------- app/routes/api/draft.make-pick.ts | 32 +++++++++++++---------- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/app/models/draft-timer.ts b/app/models/draft-timer.ts index a267474..f41260f 100644 --- a/app/models/draft-timer.ts +++ b/app/models/draft-timer.ts @@ -1,6 +1,6 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; -import { eq } from "drizzle-orm"; +import { eq, and } from "drizzle-orm"; export async function initializeDraftTimers(seasonId: string, teams: { id: string }[], initialTime: number) { const timerData = teams.map((team) => ({ @@ -8,17 +8,17 @@ export async function initializeDraftTimers(seasonId: string, teams: { id: strin teamId: team.id, timeRemaining: initialTime, })); - + const db = database(); await db.insert(schema.draftTimers).values(timerData); } -export async function getTeamTimer(teamId: string) { +export async function getTeamTimer(seasonId: string, teamId: string) { const db = database(); const [timer] = await db .select() .from(schema.draftTimers) - .where(eq(schema.draftTimers.teamId, teamId)); + .where(and(eq(schema.draftTimers.seasonId, seasonId), eq(schema.draftTimers.teamId, teamId))); return timer; } @@ -30,12 +30,12 @@ export async function getSeasonTimers(seasonId: string) { .where(eq(schema.draftTimers.seasonId, seasonId)); } -export async function updateTeamTimer(teamId: string, timeRemaining: number) { +export async function updateTeamTimer(seasonId: string, teamId: string, timeRemaining: number) { const db = database(); const [timer] = await db .update(schema.draftTimers) .set({ timeRemaining, updatedAt: new Date() }) - .where(eq(schema.draftTimers.teamId, teamId)) + .where(and(eq(schema.draftTimers.seasonId, seasonId), eq(schema.draftTimers.teamId, teamId))) .returning(); return timer; } diff --git a/app/routes/api/draft.adjust-time-bank.ts b/app/routes/api/draft.adjust-time-bank.ts index 9109fbd..78246e9 100644 --- a/app/routes/api/draft.adjust-time-bank.ts +++ b/app/routes/api/draft.adjust-time-bank.ts @@ -61,17 +61,26 @@ export async function action(args: any) { ) ); + let newTime: number; + if (!currentTimer) { - return Response.json({ error: "Timer not found for this team" }, { status: 404 }); + if (adjustment <= 0) { + return Response.json({ error: "Timer not found for this team" }, { status: 404 }); + } + newTime = adjustment; + await db.insert(schema.draftTimers).values({ + seasonId, + teamId, + timeRemaining: newTime, + }); + } else { + newTime = Math.max(0, currentTimer.timeRemaining + adjustment); + await db + .update(schema.draftTimers) + .set({ timeRemaining: newTime, updatedAt: new Date() }) + .where(eq(schema.draftTimers.id, currentTimer.id)); } - const newTime = Math.max(0, currentTimer.timeRemaining + adjustment); - - await db - .update(schema.draftTimers) - .set({ timeRemaining: newTime, updatedAt: new Date() }) - .where(eq(schema.draftTimers.id, currentTimer.id)); - try { getSocketIO() .to(`draft-${seasonId}`) diff --git a/app/routes/api/draft.force-manual-pick.ts b/app/routes/api/draft.force-manual-pick.ts index 7566923..03176be 100644 --- a/app/routes/api/draft.force-manual-pick.ts +++ b/app/routes/api/draft.force-manual-pick.ts @@ -148,9 +148,9 @@ export async function action(args: any) { ), }); + const incrementTime = season.draftIncrementTime || 30; + const newTimeRemaining = (currentTimer?.timeRemaining ?? 0) + incrementTime; if (currentTimer) { - const incrementTime = season.draftIncrementTime || 30; - const newTimeRemaining = currentTimer.timeRemaining + incrementTime; await db .update(schema.draftTimers) .set({ @@ -158,18 +158,24 @@ export async function action(args: any) { updatedAt: new Date(), }) .where(eq(schema.draftTimers.id, currentTimer.id)); + } else { + await db.insert(schema.draftTimers).values({ + seasonId, + teamId, + timeRemaining: newTimeRemaining, + }); + } - // Emit timer update to all clients - try { - getSocketIO().to(`draft-${seasonId}`).emit("timer-update", { - seasonId, - teamId, - timeRemaining: newTimeRemaining, - currentPickNumber: pickNumber, - }); - } catch (error) { - console.error("Socket.IO timer-update error:", error); - } + // Emit timer update to all clients + try { + getSocketIO().to(`draft-${seasonId}`).emit("timer-update", { + seasonId, + teamId, + timeRemaining: newTimeRemaining, + currentPickNumber: pickNumber, + }); + } catch (error) { + console.error("Socket.IO timer-update error:", error); } // Initialize timer for the next team BEFORE updating pick number (prevents race condition) diff --git a/app/routes/api/draft.make-pick.ts b/app/routes/api/draft.make-pick.ts index fbe1cdd..9d2bdf8 100644 --- a/app/routes/api/draft.make-pick.ts +++ b/app/routes/api/draft.make-pick.ts @@ -186,25 +186,29 @@ export async function action(args: any) { ), }); - if (!currentTimer) { - console.warn(`[Pick] No timer found for team ${currentDraftSlot.teamId} in season ${seasonId}`); - } else { - const newTimeRemaining = currentTimer.timeRemaining + incrementTime; + const newTimeRemaining = (currentTimer?.timeRemaining ?? 0) + incrementTime; + if (currentTimer) { await db .update(schema.draftTimers) .set({ timeRemaining: newTimeRemaining, updatedAt: new Date() }) .where(eq(schema.draftTimers.id, currentTimer.id)); + } else { + await db.insert(schema.draftTimers).values({ + seasonId, + teamId: currentDraftSlot.teamId, + timeRemaining: newTimeRemaining, + }); + } - try { - getSocketIO().to(`draft-${seasonId}`).emit("timer-update", { - seasonId, - teamId: currentDraftSlot.teamId, - timeRemaining: newTimeRemaining, - currentPickNumber, - }); - } catch (error) { - console.error("Socket.IO timer-update error:", error); - } + try { + getSocketIO().to(`draft-${seasonId}`).emit("timer-update", { + seasonId, + teamId: currentDraftSlot.teamId, + timeRemaining: newTimeRemaining, + currentPickNumber, + }); + } catch (error) { + console.error("Socket.IO timer-update error:", error); } // Next team's timer is unchanged — their bank carries forward as-is