feat: Implement autodraft chain logic and timer initialization for next team picks
This commit is contained in:
parent
1825c0e271
commit
918e9ff04a
3 changed files with 314 additions and 27 deletions
|
|
@ -7,6 +7,64 @@ import { getParticipantsForSeasonWithSports } from "./participant";
|
|||
import { getSeasonSportsSimple } from "./season-sport";
|
||||
import { calculateDraftEligibility } from "~/lib/draft-eligibility";
|
||||
|
||||
/**
|
||||
* Check if the next team has autodraft enabled and immediately execute their pick
|
||||
* This is called after a pick is made to chain autodraft picks
|
||||
*/
|
||||
export async function checkAndTriggerNextAutodraft(params: {
|
||||
seasonId: string;
|
||||
nextPickNumber: number;
|
||||
totalTeams: number;
|
||||
draftSlots: any[];
|
||||
db?: ReturnType<typeof database>;
|
||||
}): Promise<void> {
|
||||
const { seasonId, nextPickNumber, totalTeams, draftSlots, db: providedDb } = params;
|
||||
const db = providedDb || database();
|
||||
|
||||
// Calculate which team is next using snake draft logic
|
||||
const nextRound = Math.ceil(nextPickNumber / totalTeams);
|
||||
const isNextRoundEven = nextRound % 2 === 0;
|
||||
let nextPickInRound = ((nextPickNumber - 1) % totalTeams) + 1;
|
||||
|
||||
// Apply snake draft reversal for even rounds
|
||||
if (isNextRoundEven) {
|
||||
nextPickInRound = totalTeams - nextPickInRound + 1;
|
||||
}
|
||||
|
||||
const nextDraftSlot = draftSlots.find((slot) => slot.draftOrder === nextPickInRound);
|
||||
if (!nextDraftSlot) return;
|
||||
|
||||
const nextTeamId = nextDraftSlot.teamId;
|
||||
|
||||
// Check if next team has autodraft enabled
|
||||
const autodraftSettings = await db.query.autodraftSettings.findFirst({
|
||||
where: and(
|
||||
eq(schema.autodraftSettings.seasonId, seasonId),
|
||||
eq(schema.autodraftSettings.teamId, nextTeamId)
|
||||
),
|
||||
});
|
||||
|
||||
if (autodraftSettings?.isEnabled) {
|
||||
console.log(
|
||||
`[AutodraftChain] Team ${nextTeamId} has autodraft enabled, triggering immediate pick for pick ${nextPickNumber}`
|
||||
);
|
||||
|
||||
// Immediately execute autopick for this team
|
||||
await executeAutoPick({
|
||||
seasonId,
|
||||
teamId: nextTeamId,
|
||||
pickNumber: nextPickNumber,
|
||||
triggeredBy: "timer", // Use "timer" to indicate automatic (not commissioner-forced)
|
||||
autodraftSettings,
|
||||
db,
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
`[AutodraftChain] Team ${nextTeamId} does not have autodraft enabled, waiting for manual pick`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-pick for a team when their timer runs out
|
||||
* 1. Check queue - pick first eligible item if available (cleans up ineligible items)
|
||||
|
|
@ -449,19 +507,11 @@ export async function executeAutoPick(params: {
|
|||
`[AutoPick] Pick created - ${triggeredBy} triggered - Pick ${pickNumber} - Participant ${participantId}`
|
||||
);
|
||||
|
||||
// Update season's current pick number and check if draft complete
|
||||
// Calculate next pick info (before updating season)
|
||||
const nextPickNumber = pickNumber + 1;
|
||||
const totalPicks = totalTeams * season.draftRounds;
|
||||
const isDraftComplete = nextPickNumber > totalPicks;
|
||||
|
||||
await db
|
||||
.update(schema.seasons)
|
||||
.set({
|
||||
currentPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
|
||||
status: isDraftComplete ? "active" : season.status,
|
||||
})
|
||||
.where(eq(schema.seasons.id, seasonId));
|
||||
|
||||
// Add increment to the team that just picked
|
||||
const currentTimer = await db.query.draftTimers.findFirst({
|
||||
where: and(
|
||||
|
|
@ -497,6 +547,80 @@ export async function executeAutoPick(params: {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize timer for the next team BEFORE updating pick number (prevents race condition)
|
||||
if (!isDraftComplete) {
|
||||
// Calculate which team is next using snake draft logic
|
||||
const nextRound = Math.ceil(nextPickNumber / totalTeams);
|
||||
const isNextRoundEven = nextRound % 2 === 0;
|
||||
let nextPickInRound = ((nextPickNumber - 1) % totalTeams) + 1;
|
||||
|
||||
// Apply snake draft reversal for even rounds
|
||||
if (isNextRoundEven) {
|
||||
nextPickInRound = totalTeams - nextPickInRound + 1;
|
||||
}
|
||||
|
||||
const nextDraftSlot = draftSlots.find((slot) => slot.draftOrder === nextPickInRound);
|
||||
|
||||
if (nextDraftSlot) {
|
||||
const nextTeamId = nextDraftSlot.teamId;
|
||||
const initialTime = season.draftInitialTime || 120;
|
||||
|
||||
console.log(
|
||||
`[AutoPick] Initializing timer for next team ${nextTeamId} with ${initialTime}s (pick ${nextPickNumber})`
|
||||
);
|
||||
|
||||
// Check if timer already exists for next team
|
||||
const nextTimer = await db.query.draftTimers.findFirst({
|
||||
where: and(
|
||||
eq(schema.draftTimers.seasonId, seasonId),
|
||||
eq(schema.draftTimers.teamId, nextTeamId)
|
||||
),
|
||||
});
|
||||
|
||||
if (nextTimer) {
|
||||
// Update existing timer
|
||||
await db
|
||||
.update(schema.draftTimers)
|
||||
.set({
|
||||
timeRemaining: initialTime,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.draftTimers.id, nextTimer.id));
|
||||
} else {
|
||||
// Create new timer if it doesn't exist
|
||||
await db.insert(schema.draftTimers).values({
|
||||
seasonId,
|
||||
teamId: nextTeamId,
|
||||
timeRemaining: initialTime,
|
||||
});
|
||||
}
|
||||
|
||||
// Emit timer update for next team
|
||||
try {
|
||||
const io = (global as any).__socketIO;
|
||||
if (io) {
|
||||
io.to(`draft-${seasonId}`).emit("timer-update", {
|
||||
seasonId,
|
||||
teamId: nextTeamId,
|
||||
timeRemaining: initialTime,
|
||||
currentPickNumber: nextPickNumber,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[AutoPick] Socket.IO next timer-update error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update season's current pick number (AFTER initializing next timer to prevent race condition)
|
||||
await db
|
||||
.update(schema.seasons)
|
||||
.set({
|
||||
currentPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
|
||||
status: isDraftComplete ? "active" : season.status,
|
||||
})
|
||||
.where(eq(schema.seasons.id, seasonId));
|
||||
|
||||
// Remove from ALL team queues in this season (participant is now drafted)
|
||||
await db
|
||||
.delete(schema.draftQueue)
|
||||
|
|
@ -568,6 +692,17 @@ export async function executeAutoPick(params: {
|
|||
console.error("[AutoPick] Socket.IO events error:", error);
|
||||
}
|
||||
|
||||
// Check if next team has autodraft enabled and trigger immediately
|
||||
if (!isDraftComplete) {
|
||||
await checkAndTriggerNextAutodraft({
|
||||
seasonId,
|
||||
nextPickNumber,
|
||||
totalTeams,
|
||||
draftSlots,
|
||||
db,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
pick: draftPick,
|
||||
|
|
|
|||
|
|
@ -130,19 +130,11 @@ export async function action(args: any) {
|
|||
})
|
||||
.returning();
|
||||
|
||||
// Update season's current pick number
|
||||
// Calculate next pick info (before updating season)
|
||||
const nextPickNumber = pickNumber + 1;
|
||||
const totalPicks = totalTeams * season.draftRounds;
|
||||
const isDraftComplete = nextPickNumber > totalPicks;
|
||||
|
||||
await db
|
||||
.update(schema.seasons)
|
||||
.set({
|
||||
currentPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
|
||||
status: isDraftComplete ? "active" : season.status,
|
||||
})
|
||||
.where(eq(schema.seasons.id, seasonId));
|
||||
|
||||
// Add increment to the team that just picked
|
||||
const currentTimer = await db.query.draftTimers.findFirst({
|
||||
where: and(
|
||||
|
|
@ -176,6 +168,78 @@ export async function action(args: any) {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize timer for the next team BEFORE updating pick number (prevents race condition)
|
||||
if (!isDraftComplete) {
|
||||
// Calculate which team is next using snake draft logic
|
||||
const nextRound = Math.ceil(nextPickNumber / totalTeams);
|
||||
const isNextRoundEven = nextRound % 2 === 0;
|
||||
let nextPickInRound = ((nextPickNumber - 1) % totalTeams) + 1;
|
||||
|
||||
// Apply snake draft reversal for even rounds
|
||||
if (isNextRoundEven) {
|
||||
nextPickInRound = totalTeams - nextPickInRound + 1;
|
||||
}
|
||||
|
||||
const nextDraftSlot = draftSlots.find((slot) => slot.draftOrder === nextPickInRound);
|
||||
|
||||
if (nextDraftSlot) {
|
||||
const nextTeamId = nextDraftSlot.teamId;
|
||||
const initialTime = season.draftInitialTime || 120;
|
||||
|
||||
console.log(
|
||||
`[ForceManualPick] Initializing timer for next team ${nextTeamId} with ${initialTime}s (pick ${nextPickNumber})`
|
||||
);
|
||||
|
||||
// Check if timer already exists for next team
|
||||
const nextTimer = await db.query.draftTimers.findFirst({
|
||||
where: and(
|
||||
eq(schema.draftTimers.seasonId, seasonId),
|
||||
eq(schema.draftTimers.teamId, nextTeamId)
|
||||
),
|
||||
});
|
||||
|
||||
if (nextTimer) {
|
||||
// Update existing timer
|
||||
await db
|
||||
.update(schema.draftTimers)
|
||||
.set({
|
||||
timeRemaining: initialTime,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.draftTimers.id, nextTimer.id));
|
||||
} else {
|
||||
// Create new timer if it doesn't exist
|
||||
await db.insert(schema.draftTimers).values({
|
||||
seasonId,
|
||||
teamId: nextTeamId,
|
||||
timeRemaining: initialTime,
|
||||
});
|
||||
}
|
||||
|
||||
// Emit timer update for next team
|
||||
try {
|
||||
const io = (global as any).__socketIO;
|
||||
io.to(`draft-${seasonId}`).emit("timer-update", {
|
||||
seasonId,
|
||||
teamId: nextTeamId,
|
||||
timeRemaining: initialTime,
|
||||
currentPickNumber: nextPickNumber,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Socket.IO next timer-update error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update season's current pick number (AFTER initializing next timer to prevent race condition)
|
||||
await db
|
||||
.update(schema.seasons)
|
||||
.set({
|
||||
currentPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
|
||||
status: isDraftComplete ? "active" : season.status,
|
||||
})
|
||||
.where(eq(schema.seasons.id, seasonId));
|
||||
|
||||
// Remove from ALL team queues in this season (participant is now drafted)
|
||||
await db
|
||||
.delete(schema.draftQueue)
|
||||
|
|
@ -200,7 +264,7 @@ export async function action(args: any) {
|
|||
try {
|
||||
const io = (global as any).__socketIO;
|
||||
const team = draftSlots.find((slot) => slot.team.id === teamId)?.team;
|
||||
|
||||
|
||||
io.to(`draft-${seasonId}`).emit("pick-made", {
|
||||
pick: {
|
||||
...draftPick,
|
||||
|
|
@ -222,8 +286,20 @@ export async function action(args: any) {
|
|||
console.error("Socket.IO error:", error);
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
// Check if next team has autodraft enabled and trigger immediately
|
||||
if (!isDraftComplete) {
|
||||
const { checkAndTriggerNextAutodraft } = await import("~/models/draft-utils");
|
||||
await checkAndTriggerNextAutodraft({
|
||||
seasonId,
|
||||
nextPickNumber,
|
||||
totalTeams,
|
||||
draftSlots,
|
||||
db,
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
pick: draftPick,
|
||||
nextPickNumber: isDraftComplete ? pickNumber : nextPickNumber,
|
||||
isDraftComplete,
|
||||
|
|
|
|||
|
|
@ -161,6 +161,11 @@ export async function action(args: any) {
|
|||
console.error("Socket.IO participant-removed-from-queues error:", error);
|
||||
}
|
||||
|
||||
// Calculate next pick info (before updating season)
|
||||
const nextPickNumber = currentPickNumber + 1;
|
||||
const totalPicks = totalTeams * season.draftRounds;
|
||||
const isDraftComplete = nextPickNumber > totalPicks;
|
||||
|
||||
// Add increment to the team that just picked
|
||||
const currentTimer = await db.query.draftTimers.findFirst({
|
||||
where: and(
|
||||
|
|
@ -194,11 +199,70 @@ export async function action(args: any) {
|
|||
}
|
||||
}
|
||||
|
||||
// Update season's current pick number
|
||||
const nextPickNumber = currentPickNumber + 1;
|
||||
const totalPicks = totalTeams * season.draftRounds;
|
||||
const isDraftComplete = nextPickNumber > totalPicks;
|
||||
// Initialize timer for the next team BEFORE updating pick number (prevents race condition)
|
||||
if (!isDraftComplete) {
|
||||
// Calculate which team is next using snake draft logic
|
||||
const nextRound = Math.ceil(nextPickNumber / totalTeams);
|
||||
const isNextRoundEven = nextRound % 2 === 0;
|
||||
let nextPickInRound = ((nextPickNumber - 1) % totalTeams) + 1;
|
||||
|
||||
// Apply snake draft reversal for even rounds
|
||||
if (isSnakeDraft && isNextRoundEven) {
|
||||
nextPickInRound = totalTeams - nextPickInRound + 1;
|
||||
}
|
||||
|
||||
const nextDraftSlot = draftSlots.find((slot) => slot.draftOrder === nextPickInRound);
|
||||
|
||||
if (nextDraftSlot) {
|
||||
const nextTeamId = nextDraftSlot.teamId;
|
||||
const initialTime = season.draftInitialTime || 120;
|
||||
|
||||
console.log(
|
||||
`[MakePick] Initializing timer for next team ${nextTeamId} with ${initialTime}s (pick ${nextPickNumber})`
|
||||
);
|
||||
|
||||
// Check if timer already exists for next team
|
||||
const nextTimer = await db.query.draftTimers.findFirst({
|
||||
where: and(
|
||||
eq(schema.draftTimers.seasonId, seasonId),
|
||||
eq(schema.draftTimers.teamId, nextTeamId)
|
||||
),
|
||||
});
|
||||
|
||||
if (nextTimer) {
|
||||
// Update existing timer
|
||||
await db
|
||||
.update(schema.draftTimers)
|
||||
.set({
|
||||
timeRemaining: initialTime,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.draftTimers.id, nextTimer.id));
|
||||
} else {
|
||||
// Create new timer if it doesn't exist
|
||||
await db.insert(schema.draftTimers).values({
|
||||
seasonId,
|
||||
teamId: nextTeamId,
|
||||
timeRemaining: initialTime,
|
||||
});
|
||||
}
|
||||
|
||||
// Emit timer update for next team
|
||||
try {
|
||||
const io = (global as any).__socketIO;
|
||||
io.to(`draft-${seasonId}`).emit("timer-update", {
|
||||
seasonId,
|
||||
teamId: nextTeamId,
|
||||
timeRemaining: initialTime,
|
||||
currentPickNumber: nextPickNumber,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Socket.IO next timer-update error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update season's current pick number (AFTER initializing next timer to prevent race condition)
|
||||
await db
|
||||
.update(schema.seasons)
|
||||
.set({
|
||||
|
|
@ -231,8 +295,20 @@ export async function action(args: any) {
|
|||
console.error("Socket.IO error:", error);
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
// Check if next team has autodraft enabled and trigger immediately
|
||||
if (!isDraftComplete) {
|
||||
const { checkAndTriggerNextAutodraft } = await import("~/models/draft-utils");
|
||||
await checkAndTriggerNextAutodraft({
|
||||
seasonId,
|
||||
nextPickNumber,
|
||||
totalTeams,
|
||||
draftSlots,
|
||||
db,
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
pick: draftPick,
|
||||
nextPickNumber: isDraftComplete ? currentPickNumber : nextPickNumber,
|
||||
isDraftComplete,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue