import { getAuth } from "@clerk/react-router/server"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq, and, notInArray, desc, asc, inArray } from "drizzle-orm"; export async function action(args: any) { const { request } = args; const auth = await getAuth(args); const userId = (auth as any).userId as string | null; if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const formData = await request.formData(); const seasonId = formData.get("seasonId") as string; const teamId = formData.get("teamId") as string; const pickNumber = parseInt(formData.get("pickNumber") as string); if (!seasonId || !teamId || !pickNumber) { return Response.json({ error: "Missing required fields" }, { status: 400 }); } const db = database(); // Get season details const season = await db.query.seasons.findFirst({ where: eq(schema.seasons.id, seasonId), with: { league: true, }, }); if (!season) { return Response.json({ error: "Season not found" }, { status: 404 }); } // Check if user is commissioner if (season.league.createdBy !== userId) { return Response.json({ error: "Only commissioner can force autopick" }, { status: 403 }); } // Get team's queue const teamQueue = await db.query.draftQueue.findMany({ where: eq(schema.draftQueue.teamId, teamId), orderBy: schema.draftQueue.queuePosition, with: { participant: { with: { sportsSeason: { with: { sport: true, }, }, }, }, }, }); // Get already drafted participant IDs const draftPicks = await db.query.draftPicks.findMany({ where: eq(schema.draftPicks.seasonId, seasonId), }); const draftedParticipantIds = draftPicks.map((p) => p.participantId); let participantToPick = null; // 1. Try to pick from queue (first non-drafted participant) for (const queueItem of teamQueue) { if (!draftedParticipantIds.includes(queueItem.participantId)) { participantToPick = queueItem.participant; break; } } // 2. If no valid queue item, pick highest EV available participant if (!participantToPick) { // Get sports seasons for this season const seasonSports = await db.query.seasonSports.findMany({ where: eq(schema.seasonSports.seasonId, seasonId), }); const sportsSeasonIds = seasonSports.map((ss) => ss.sportsSeasonId); if (sportsSeasonIds.length > 0) { const availableParticipants = await db .select({ id: schema.participants.id, name: schema.participants.name, expectedValue: schema.participants.expectedValue, sportsSeasonId: schema.participants.sportsSeasonId, }) .from(schema.participants) .where( and( sportsSeasonIds.length === 1 ? eq(schema.participants.sportsSeasonId, sportsSeasonIds[0]) : inArray(schema.participants.sportsSeasonId, sportsSeasonIds), draftedParticipantIds.length > 0 ? notInArray(schema.participants.id, draftedParticipantIds) : undefined ) ) .orderBy(desc(schema.participants.expectedValue), asc(schema.participants.name)) .limit(1); if (availableParticipants.length > 0) { // Get full participant with sport info participantToPick = await db.query.participants.findFirst({ where: eq(schema.participants.id, availableParticipants[0].id), with: { sportsSeason: { with: { sport: true, }, }, }, }); } } } if (!participantToPick) { return Response.json({ error: "No available participants to pick" }, { status: 400 }); } // Calculate round and pickInRound const draftSlots = await db.query.draftSlots.findMany({ where: eq(schema.draftSlots.seasonId, seasonId), orderBy: schema.draftSlots.draftOrder, with: { team: true, }, }); const totalTeams = draftSlots.length; const currentRound = Math.ceil(pickNumber / totalTeams); const isEvenRound = currentRound % 2 === 0; let pickInRound = ((pickNumber - 1) % totalTeams) + 1; if (isEvenRound) { pickInRound = totalTeams - pickInRound + 1; } // Create the draft pick const [draftPick] = await db .insert(schema.draftPicks) .values({ seasonId, teamId, participantId: participantToPick.id, pickNumber, round: currentRound, pickInRound, pickedByUserId: userId, pickedByType: "auto", }) .returning(); // Update season's current pick number 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( eq(schema.draftTimers.seasonId, seasonId), eq(schema.draftTimers.teamId, teamId) ), }); if (currentTimer) { const incrementTime = season.draftIncrementTime || 30; const newTimeRemaining = currentTimer.timeRemaining + incrementTime; await db .update(schema.draftTimers) .set({ timeRemaining: newTimeRemaining, updatedAt: new Date(), }) .where(eq(schema.draftTimers.id, currentTimer.id)); // Emit timer update to all clients try { const io = (global as any).__socketIO; io.to(`draft-${seasonId}`).emit("timer-update", { seasonId, teamId, timeRemaining: newTimeRemaining, currentPickNumber: pickNumber, }); } catch (error) { console.error("Socket.IO timer-update error:", error); } } // Remove from ALL team queues in this season (participant is now drafted) await db .delete(schema.draftQueue) .where( and( eq(schema.draftQueue.seasonId, seasonId), eq(schema.draftQueue.participantId, participantToPick.id) ) ); // Emit socket event 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, team, participant: { ...participantToPick, sport: participantToPick.sportsSeason.sport, }, sport: participantToPick.sportsSeason.sport, }, nextPickNumber: isDraftComplete ? pickNumber : nextPickNumber, isDraftComplete, }); if (isDraftComplete) { io.to(`draft-${seasonId}`).emit("draft-completed"); } } catch (error) { console.error("Socket.IO error:", error); } return Response.json({ success: true, pick: draftPick, participant: participantToPick, nextPickNumber: isDraftComplete ? pickNumber : nextPickNumber, isDraftComplete, }); }