217 lines
6.2 KiB
TypeScript
217 lines
6.2 KiB
TypeScript
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));
|
|
|
|
// Remove from queue if it was picked from queue
|
|
if (teamQueue.some((item) => item.participantId === participantToPick.id)) {
|
|
await db
|
|
.delete(schema.draftQueue)
|
|
.where(
|
|
and(
|
|
eq(schema.draftQueue.teamId, teamId),
|
|
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,
|
|
});
|
|
}
|