brackt/app/models/draft-slot.ts

161 lines
3.9 KiB
TypeScript
Raw Permalink Normal View History

import { eq, count } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import type { RawFlagConfig } from "~/lib/flag-types";
export type DraftSlot = typeof schema.draftSlots.$inferSelect;
export type NewDraftSlot = typeof schema.draftSlots.$inferInsert;
export interface DraftSlotWithTeam extends DraftSlot {
team: {
id: string;
name: string;
ownerId: string | null;
logoUrl?: string | null;
flagConfig?: RawFlagConfig | null;
avatarType?: string | null;
};
}
/**
* Create a single draft slot
*/
export async function createDraftSlot(data: NewDraftSlot): Promise<DraftSlot> {
const db = database();
const [draftSlot] = await db
.insert(schema.draftSlots)
.values(data)
.returning();
return draftSlot;
}
/**
* Create multiple draft slots at once
*/
export async function createManyDraftSlots(
slots: NewDraftSlot[]
): Promise<DraftSlot[]> {
const db = database();
return await db.insert(schema.draftSlots).values(slots).returning();
}
/**
* Find all draft slots for a season, ordered by draft order
*/
export async function findDraftSlotsBySeasonId(
seasonId: string
): Promise<DraftSlotWithTeam[]> {
const db = database();
return await db.query.draftSlots.findMany({
where: eq(schema.draftSlots.seasonId, seasonId),
orderBy: (draftSlots, { asc }) => [asc(draftSlots.draftOrder)],
with: {
team: {
columns: {
id: true,
name: true,
ownerId: true,
logoUrl: true,
flagConfig: true,
avatarType: true,
},
},
},
}) as DraftSlotWithTeam[];
}
/**
* Find a draft slot by team ID
*/
export async function findDraftSlotByTeamId(
teamId: string
): Promise<DraftSlot | undefined> {
const db = database();
return await db.query.draftSlots.findFirst({
where: eq(schema.draftSlots.teamId, teamId),
});
}
/**
* Update a draft slot's order
*/
export async function updateDraftSlotOrder(
id: string,
draftOrder: number
): Promise<DraftSlot> {
const db = database();
const [draftSlot] = await db
.update(schema.draftSlots)
.set({ draftOrder, updatedAt: new Date() })
.where(eq(schema.draftSlots.id, id))
.returning();
return draftSlot;
}
/**
* Delete all draft slots for a season
*/
export async function deleteDraftSlotsBySeasonId(
seasonId: string
): Promise<void> {
const db = database();
await db
.delete(schema.draftSlots)
.where(eq(schema.draftSlots.seasonId, seasonId));
}
/**
* Delete a specific draft slot
*/
export async function deleteDraftSlot(id: string): Promise<void> {
const db = database();
await db.delete(schema.draftSlots).where(eq(schema.draftSlots.id, id));
}
/**
* Set the draft order for all teams in a season
* This will delete existing slots and create new ones
*/
export async function setDraftOrder(
seasonId: string,
teamIds: string[]
): Promise<DraftSlot[]> {
// Delete existing draft slots for this season
await deleteDraftSlotsBySeasonId(seasonId);
// Create new draft slots with the specified order
const slots = teamIds.map((teamId, index) => ({
seasonId,
teamId,
draftOrder: index + 1,
}));
return await createManyDraftSlots(slots);
}
Fix draft order initialization for teams added mid-season (#449) * Fix draft order showing only new team when league size increased before order was set When a commissioner increased the team count on a league that had teams but no draft order set yet, the server created draft slots only for the newly-added teams. This left the DB with N+K teams but only K slots, causing the drag-and-drop list to display only the K new teams. Two fixes: 1. Server: only append new draft slots when an order was already set (existingSlots.length > 0). If no order exists yet, skip slot creation so the page correctly treats the order as unset for all teams. 2. Frontend: buildDraftOrderTeams() appends any unslotted teams after the slotted ones, so a partially-corrupt DB state still shows all teams. https://claude.ai/code/session_01M3H55gnMxRztJK9KMXqqZo * Address code review feedback on draft order bug fix - Move buildDraftOrderTeams to app/lib/draft-order.ts so it is importable and testable; remove the local copy from the settings component - Add unit tests for buildDraftOrderTeams covering the empty, full, and partial-slot cases - Tighten the draft slot guard from existingSlots.length > 0 to existingSlots.length === currentTeamCount so partial legacy states are treated the same as "order not set" - Condense the 3-line server comment to a single line per project style - Rename getNumTeamsInSeason → getNumDraftSlotsBySeasonId to reflect what the function actually counts, and update its one call site - Add tests for the server-side slot-creation guard logic https://claude.ai/code/session_01M3H55gnMxRztJK9KMXqqZo * Fix lint: move shouldAppendDraftSlots to outer scope oxlint (consistent-function-scoping) requires functions that don't close over any variables to be defined at the outer scope rather than inside a describe block. https://claude.ai/code/session_01M3H55gnMxRztJK9KMXqqZo --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 18:22:47 -07:00
export async function getNumDraftSlotsBySeasonId(seasonId: string): Promise<number> {
const db = database();
const [result] = await db
.select({ count: count() })
.from(schema.draftSlots)
.where(eq(schema.draftSlots.seasonId, seasonId));
return result?.count ?? 0;
}
/**
* Randomize the draft order for a season
*/
export async function randomizeDraftOrder(
seasonId: string,
teamIds: string[]
): Promise<DraftSlot[]> {
// Shuffle the team IDs using Fisher-Yates algorithm
const shuffled = [...teamIds];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return await setDraftOrder(seasonId, shuffled);
}