diff --git a/app/lib/draft-timer.ts b/app/lib/draft-timer.ts index 5793e46..832675f 100644 --- a/app/lib/draft-timer.ts +++ b/app/lib/draft-timer.ts @@ -41,6 +41,34 @@ export function calculateTimeAfterPick( return bankTime + incrementTime; } +/** + * Converts a draftSpeed form value + timerMode into DB time fields. + * + * Standard mode: speed is raw seconds (the per-pick time); both fields equal it. + * Chess clock mode: speed is a named preset that maps to (initial bank, increment). + */ +export function parseDraftSpeed( + draftSpeed: string | null, + draftTimerMode: "chess_clock" | "standard" +): { draftInitialTime: number; draftIncrementTime: number } { + if (draftTimerMode === "standard") { + const seconds = parseInt(draftSpeed ?? "", 10); + const time = isNaN(seconds) ? 90 : seconds; + return { draftInitialTime: time, draftIncrementTime: time }; + } + + switch (draftSpeed) { + case "fast": + return { draftInitialTime: 60, draftIncrementTime: 10 }; + case "slow": + return { draftInitialTime: 28800, draftIncrementTime: 3600 }; + case "very-slow": + return { draftInitialTime: 43200, draftIncrementTime: 3600 }; + default: + return { draftInitialTime: 120, draftIncrementTime: 15 }; + } +} + /** * Returns the Tailwind colour class(es) for a timer value. * diff --git a/app/models/draft-utils.ts b/app/models/draft-utils.ts index 22cde54..7f7eedb 100644 --- a/app/models/draft-utils.ts +++ b/app/models/draft-utils.ts @@ -644,36 +644,43 @@ export async function executeAutoPick(params: { const totalPicks = totalTeams * season.draftRounds; const isDraftComplete = nextPickNumber > totalPicks; - // Add increment to the team that just picked (post-pick reward). - // Atomic update so the timer loop cannot overwrite this via a concurrent decrement. - const [updatedTimer] = await db - .update(schema.draftTimers) - .set({ - timeRemaining: sql`${schema.draftTimers.timeRemaining} + ${incrementTime}`, - updatedAt: new Date(), - }) - .where( - and( - eq(schema.draftTimers.seasonId, seasonId), - eq(schema.draftTimers.teamId, teamId) - ) - ) - .returning(); + // Auto picks do not earn bank time. + // Standard mode: reset to the per-pick time (atomic, prevents race with timer loop). + // Chess clock mode: timer already reached 0; leave the bank frozen at 0. + let emitTimeRemaining: number; - if (updatedTimer) { + if (season.draftTimerMode === "standard") { + const [updatedTimer] = await db + .update(schema.draftTimers) + .set({ timeRemaining: sql`${incrementTime}`, updatedAt: new Date() }) + .where( + and( + eq(schema.draftTimers.seasonId, seasonId), + eq(schema.draftTimers.teamId, teamId) + ) + ) + .returning(); + emitTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime; console.log( - `[AutoPick] Added ${incrementTime}s increment to team ${teamId} after pick → ${updatedTimer.timeRemaining}s` + `[AutoPick] Reset timer for team ${teamId} to ${emitTimeRemaining}s (standard mode)` ); - try { - getSocketIO().to(`draft-${seasonId}`).emit("timer-update", { - seasonId, - teamId, - timeRemaining: updatedTimer.timeRemaining, - currentPickNumber: nextPickNumber, - }); - } catch (error) { - console.error("[AutoPick] Socket.IO timer-update error:", error); - } + } else { + // Chess clock: no DB update — auto picks don't earn increment. + emitTimeRemaining = currentTimer?.timeRemaining ?? 0; + console.log( + `[AutoPick] Chess clock pick for team ${teamId}, bank frozen at ${emitTimeRemaining}s (no increment)` + ); + } + + try { + getSocketIO().to(`draft-${seasonId}`).emit("timer-update", { + seasonId, + teamId, + timeRemaining: emitTimeRemaining, + currentPickNumber: nextPickNumber, + }); + } catch (error) { + console.error("[AutoPick] Socket.IO timer-update error:", error); } // Next team's timer is unchanged — their bank carries forward as-is diff --git a/app/routes/api/__tests__/draft.force-manual-pick.test.ts b/app/routes/api/__tests__/draft.force-manual-pick.test.ts index 4164fa5..e26aada 100644 --- a/app/routes/api/__tests__/draft.force-manual-pick.test.ts +++ b/app/routes/api/__tests__/draft.force-manual-pick.test.ts @@ -48,6 +48,7 @@ const mockSeason = { draftRounds: 3, draftInitialTime: 120, draftIncrementTime: 30, + draftTimerMode: "chess_clock", currentPickNumber: 1, draftPaused: false, }; @@ -347,86 +348,82 @@ describe("draft.force-manual-pick action", () => { // ── Timer behavior ───────────────────────────────────────────────────────── // - // All three pick paths (user pick, force auto, force manual) must treat timers - // identically: - // 1. The team that was picked FOR gets +draftIncrementTime added to their bank. - // 2. The next team's bank is left completely untouched. + // Force manual picks are commissioner actions — they do NOT earn the team + // any extra time bank, regardless of timer mode. + // + // chess_clock: bank is frozen (no DB update); current value read for socket emit. + // standard: bank resets to draftIncrementTime (the per-pick time) for the next turn. + // Next team's bank is always left completely untouched. describe("timer behavior", () => { - it("adds draftIncrementTime to the picking team's time bank", async () => { - // Timer has 75s; increment is 30s → DB atomically returns new balance: 105s - mockDb.returning - .mockResolvedValueOnce([mockDraftPick]) - .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 105 }]); + describe("chess_clock mode", () => { + it("does NOT add increment to the picking team's bank", async () => { + // Timer has 75s (from beforeEach mock); forced pick → bank stays at 75s + await action({ request: defaultPickRequest(), params: {}, context: ctx }); - await action({ request: defaultPickRequest(), params: {}, context: ctx }); + expect(mockSocketIO.emit).toHaveBeenCalledWith( + "timer-update", + expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 75 }) + ); + }); - expect(mockSocketIO.emit).toHaveBeenCalledWith( - "timer-update", - expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 105 }) - ); - }); + it("emits timer-update with the frozen (unchanged) bank value", async () => { + await action({ request: defaultPickRequest(), params: {}, context: ctx }); - it("emits timer-update for the picking team with their incremented balance", async () => { - mockDb.returning - .mockResolvedValueOnce([mockDraftPick]) - .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 105 }]); + expect(mockSocketIO.emit).toHaveBeenCalledWith("timer-update", { + seasonId: SEASON_ID, + teamId: TEAM_ID, + timeRemaining: 75, + currentPickNumber: 2, + }); + }); - await action({ request: defaultPickRequest(), params: {}, context: ctx }); + it("reads the picking team's timer but does not write it", async () => { + await action({ request: defaultPickRequest(), params: {}, context: ctx }); - expect(mockSocketIO.emit).toHaveBeenCalledWith("timer-update", { - seasonId: SEASON_ID, - teamId: TEAM_ID, - timeRemaining: 105, - currentPickNumber: 2, + // findFirst called once (picking team's current value for the emit) + expect(mockDb.query.draftTimers.findFirst).toHaveBeenCalledTimes(1); + // update called once only for season.currentPickNumber — NOT for the timer + expect(mockDb.update).toHaveBeenCalledTimes(1); }); }); - it("creates a new timer for the picking team if they have no existing timer", async () => { - // update() returns [] when no timer row exists → triggers the insert fallback - mockDb.returning - .mockResolvedValueOnce([mockDraftPick]) - .mockResolvedValueOnce([]); + describe("standard mode", () => { + beforeEach(() => { + mockDb.query.seasons.findFirst.mockResolvedValue({ + ...mockSeason, + draftTimerMode: "standard", + }); + }); - await action({ request: defaultPickRequest(), params: {}, context: ctx }); + it("resets the picking team's timer to draftIncrementTime", async () => { + mockDb.returning + .mockResolvedValueOnce([mockDraftPick]) + .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 30 }]); - // insert should be called for: (1) draft pick, (2) new timer (increment only: 30s) - expect(mockDb.insert).toHaveBeenCalledTimes(2); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); + + expect(mockSocketIO.emit).toHaveBeenCalledWith( + "timer-update", + expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 }) + ); + }); + + it("writes the DB timer (reset) and updates the season pick number", async () => { + mockDb.returning + .mockResolvedValueOnce([mockDraftPick]) + .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 30 }]); + + await action({ request: defaultPickRequest(), params: {}, context: ctx }); + + // Two updates: timer reset + season.currentPickNumber + expect(mockDb.update).toHaveBeenCalledTimes(2); + }); }); - it("increment is additive, not a flat reset — fast pickers accumulate time", async () => { - // Team had 100s remaining; increment is 30s → DB atomically returns 130s - mockDb.returning - .mockResolvedValueOnce([mockDraftPick]) - .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 130 }]); - - await action({ request: defaultPickRequest(), params: {}, context: ctx }); - - // Verify 130s (100 + 30), not 120s (which would be a flat reset to initialTime) - expect(mockSocketIO.emit).toHaveBeenCalledWith( - "timer-update", - expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 130 }) - ); - }); - - // ── REGRESSION TESTS ──────────────────────────────────────────────────── - // - // Bug: force-manual-pick was resetting the next team's timer to - // draftInitialTime (120s) instead of carrying their bank forward. - // The correct behaviour (matching make-pick and force-autopick) is to - // leave the next team's timer completely untouched. - - it("REGRESSION: does not look up the next team's timer", async () => { - // The atomic SQL increment (timeRemaining + N) requires no prior read — - // draftTimers.findFirst is never called. - await action({ request: defaultPickRequest(), params: {}, context: ctx }); - - expect(mockDb.query.draftTimers.findFirst).toHaveBeenCalledTimes(0); - }); + // ── REGRESSION: Next team's timer must never be touched ────────────────── it("REGRESSION: does not emit a timer-update for the next team", async () => { - // Before the fix, a timer-update was emitted for the next team with - // timeRemaining: draftInitialTime (120s), overwriting their actual bank. await action({ request: defaultPickRequest(), params: {}, context: ctx }); const nextTeamTimerEmits = mockSocketIO.emit.mock.calls.filter( @@ -435,14 +432,5 @@ describe("draft.force-manual-pick action", () => { ); expect(nextTeamTimerEmits).toHaveLength(0); }); - - it("REGRESSION: does not overwrite the next team's existing time bank", async () => { - // Verify that db.update is called exactly twice (timer increment + season - // pick number), not three times (which would include resetting the next - // team's timer). - await action({ request: defaultPickRequest(), params: {}, context: ctx }); - - expect(mockDb.update).toHaveBeenCalledTimes(2); - }); }); }); diff --git a/app/routes/api/__tests__/draft.make-pick.timer-mode.test.ts b/app/routes/api/__tests__/draft.make-pick.timer-mode.test.ts new file mode 100644 index 0000000..c8c0844 --- /dev/null +++ b/app/routes/api/__tests__/draft.make-pick.timer-mode.test.ts @@ -0,0 +1,324 @@ +/** + * Tests for draft.make-pick timer behavior across chess_clock and standard modes. + * + * chess_clock: after a pick, the team's remaining bank += increment (carry-over) + * standard: after a pick, the team's bank resets to exactly the increment + */ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { RouterContextProvider } from "react-router"; +import { action } from "~/routes/api/draft.make-pick"; + +const ctx = {} as unknown as RouterContextProvider; + +vi.mock("~/database/context"); +vi.mock("~/server/socket", () => ({ + getSocketIO: vi.fn(), +})); +vi.mock("@clerk/react-router/server", () => ({ + getAuth: vi.fn(), +})); +vi.mock("~/models/draft-pick", () => ({ + getDraftPicksWithSports: vi.fn(), + getTeamDraftPicksWithSports: vi.fn(), +})); +vi.mock("~/models/participant", () => ({ + getParticipantsForSeasonWithSports: vi.fn(), +})); +vi.mock("~/models/season-sport", () => ({ + getSeasonSportsSimple: vi.fn(), +})); +vi.mock("~/lib/draft-eligibility", () => ({ + calculateDraftEligibility: vi.fn(), +})); +vi.mock("~/models/draft-utils", () => ({ + checkAndTriggerNextAutodraft: vi.fn(), + calculatePickInfo: vi.fn().mockReturnValue({ round: 1, pickInRound: 1, teamIndex: 0 }), + pruneIneligibleQueueItems: vi.fn().mockResolvedValue([]), +})); +vi.mock("~/models/user", () => ({ + isUserAdminByClerkId: vi.fn(), +})); + +// ── Fixtures ───────────────────────────────────────────────────────────────── + +const SEASON_ID = "season-1"; +const TEAM_ID = "team-1"; +const NEXT_TEAM_ID = "team-2"; +const PARTICIPANT_ID = "participant-1"; +const OWNER_ID = "owner-user-1"; +const SPORT_ID = "sport-nfl"; + +const mockParticipant = { + id: PARTICIPANT_ID, + name: "Patrick Mahomes", + sportsSeason: { + id: "sports-season-1", + sport: { id: SPORT_ID, name: "NFL" }, + }, +}; + +const mockDraftPick = { + id: "pick-1", + seasonId: SEASON_ID, + teamId: TEAM_ID, + participantId: PARTICIPANT_ID, + pickNumber: 1, + round: 1, + pickInRound: 1, + pickedByUserId: OWNER_ID, + pickedByType: "owner", +}; + +const mockDraftSlots = [ + { + id: "slot-1", + seasonId: SEASON_ID, + teamId: TEAM_ID, + draftOrder: 1, + team: { id: TEAM_ID, name: "Team 1", seasonId: SEASON_ID, ownerId: OWNER_ID }, + }, + { + id: "slot-2", + seasonId: SEASON_ID, + teamId: NEXT_TEAM_ID, + draftOrder: 2, + team: { id: NEXT_TEAM_ID, name: "Team 2", seasonId: SEASON_ID, ownerId: "owner-2" }, + }, +]; + +function makeSeason(overrides: Record = {}) { + return { + id: SEASON_ID, + leagueId: "league-1", + status: "draft", + draftRounds: 3, + draftInitialTime: 120, + draftIncrementTime: 30, + draftTimerMode: "chess_clock", + currentPickNumber: 1, + draftPaused: false, + ...overrides, + }; +} + +function makeRequest() { + const formData = new FormData(); + formData.append("seasonId", SEASON_ID); + formData.append("participantId", PARTICIPANT_ID); + return new Request("http://localhost/api/draft/make-pick", { + method: "POST", + body: formData, + }); +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +describe("draft.make-pick action — timer mode behavior", () => { + let mockDb: any; + let mockSocketIO: any; + + beforeEach(async () => { + vi.clearAllMocks(); + + const { getAuth } = await import("@clerk/react-router/server"); + vi.mocked(getAuth).mockResolvedValue({ userId: OWNER_ID } as any); + + const { isUserAdminByClerkId } = await import("~/models/user"); + vi.mocked(isUserAdminByClerkId).mockResolvedValue(false); + + mockSocketIO = { to: vi.fn().mockReturnThis(), emit: vi.fn() }; + const socketModule = await import("~/server/socket"); + vi.mocked(socketModule.getSocketIO).mockReturnValue(mockSocketIO); + + const { getDraftPicksWithSports, getTeamDraftPicksWithSports } = await import("~/models/draft-pick"); + vi.mocked(getDraftPicksWithSports).mockResolvedValue([]); + vi.mocked(getTeamDraftPicksWithSports).mockResolvedValue([]); + + const { getParticipantsForSeasonWithSports } = await import("~/models/participant"); + vi.mocked(getParticipantsForSeasonWithSports).mockResolvedValue([]); + + const { getSeasonSportsSimple } = await import("~/models/season-sport"); + vi.mocked(getSeasonSportsSimple).mockResolvedValue([]); + + const { calculateDraftEligibility } = await import("~/lib/draft-eligibility"); + vi.mocked(calculateDraftEligibility).mockReturnValue({ + eligibleSportIds: new Set([SPORT_ID]), + ineligibleReasons: {}, + } as any); + + const { checkAndTriggerNextAutodraft } = await import("~/models/draft-utils"); + vi.mocked(checkAndTriggerNextAutodraft).mockResolvedValue(undefined); + + mockDb = { + query: { + seasons: { findFirst: vi.fn() }, + commissioners: { findFirst: vi.fn().mockResolvedValue(null) }, + draftPicks: { findFirst: vi.fn().mockResolvedValue(null) }, + participants: { findFirst: vi.fn().mockResolvedValue(mockParticipant) }, + draftSlots: { findMany: vi.fn().mockResolvedValue(mockDraftSlots) }, + draftTimers: { findFirst: vi.fn().mockResolvedValue({ timeRemaining: 75 }) }, + draftQueue: { findMany: vi.fn().mockResolvedValue([]) }, + }, + insert: vi.fn().mockReturnThis(), + values: vi.fn().mockReturnThis(), + returning: vi.fn().mockResolvedValue([mockDraftPick]), + update: vi.fn().mockReturnThis(), + set: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + delete: vi.fn().mockReturnThis(), + }; + + const { database } = await import("~/database/context"); + vi.mocked(database).mockReturnValue(mockDb); + }); + + describe("chess_clock mode", () => { + describe("owner pick — earns the increment", () => { + it("emits timer-update with the incremented balance (bank + increment)", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "chess_clock", draftInitialTime: 120, draftIncrementTime: 30 }) + ); + // Simulate DB returning previous 75s + 30s increment = 105s + mockDb.returning + .mockResolvedValueOnce([mockDraftPick]) // insert draft pick + .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 105 }]); // update timer + + await action({ request: makeRequest(), params: {}, context: ctx }); + + expect(mockSocketIO.emit).toHaveBeenCalledWith( + "timer-update", + expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 105 }) + ); + }); + + it("picks with more time remaining accumulate a larger bank", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "chess_clock", draftInitialTime: 120, draftIncrementTime: 30 }) + ); + // Team had 100s; 100 + 30 = 130s + mockDb.returning + .mockResolvedValueOnce([mockDraftPick]) + .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 130 }]); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + expect(mockSocketIO.emit).toHaveBeenCalledWith( + "timer-update", + expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 130 }) + ); + }); + }); + + describe("commissioner pick — no increment earned", () => { + const COMMISSIONER_ID = "commissioner-user-1"; + + beforeEach(async () => { + // Auth as commissioner (not the team owner) + const { getAuth } = await import("@clerk/react-router/server"); + vi.mocked(getAuth).mockResolvedValue({ userId: COMMISSIONER_ID } as any); + // Commissioner record exists + mockDb.query.commissioners.findFirst.mockResolvedValue({ + id: "c-1", + userId: COMMISSIONER_ID, + }); + // Team is owned by someone else + mockDb.query.draftSlots.findMany.mockResolvedValue([ + { + id: "slot-1", + seasonId: SEASON_ID, + teamId: TEAM_ID, + draftOrder: 1, + team: { id: TEAM_ID, name: "Team 1", seasonId: SEASON_ID, ownerId: "different-owner" }, + }, + { + id: "slot-2", + seasonId: SEASON_ID, + teamId: NEXT_TEAM_ID, + draftOrder: 2, + team: { id: NEXT_TEAM_ID, name: "Team 2", seasonId: SEASON_ID, ownerId: "owner-2" }, + }, + ]); + }); + + it("emits timer-update with the frozen (unchanged) bank — no increment added", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "chess_clock", draftIncrementTime: 30 }) + ); + // timerSnapshot has 75s remaining; commissioner picks → should still be 75s + mockDb.query.draftTimers.findFirst.mockResolvedValue({ timeRemaining: 75 }); + mockDb.returning.mockResolvedValueOnce([mockDraftPick]); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + expect(mockSocketIO.emit).toHaveBeenCalledWith( + "timer-update", + expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 75 }) + ); + }); + + it("does not write the timer to the DB (no increment update)", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "chess_clock", draftIncrementTime: 30 }) + ); + mockDb.returning.mockResolvedValueOnce([mockDraftPick]); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + // Only one update: season.currentPickNumber — the timer row is NOT written + expect(mockDb.update).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe("standard mode", () => { + it("emits timer-update with exactly draftIncrementTime regardless of remaining bank", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "standard", draftInitialTime: 120, draftIncrementTime: 30 }) + ); + // Simulate DB resetting to exactly 30s (the increment), ignoring prior bank + mockDb.returning + .mockResolvedValueOnce([mockDraftPick]) + .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 30 }]); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + expect(mockSocketIO.emit).toHaveBeenCalledWith( + "timer-update", + expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 }) + ); + }); + + it("fast pickers do NOT accumulate time — always resets to increment", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "standard", draftInitialTime: 120, draftIncrementTime: 30 }) + ); + // Team picked quickly with 90s remaining — in standard mode they still reset to 30s + mockDb.returning + .mockResolvedValueOnce([mockDraftPick]) + .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 30 }]); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + // Must be 30 (increment reset), not 120 (initialTime) or 120 (90+30) + expect(mockSocketIO.emit).toHaveBeenCalledWith( + "timer-update", + expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 }) + ); + }); + + it("still returns 200 on a successful pick", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "standard", draftIncrementTime: 45 }) + ); + mockDb.returning + .mockResolvedValueOnce([mockDraftPick]) + .mockResolvedValueOnce([{ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 45 }]); + + const response = await action({ request: makeRequest(), params: {}, context: ctx }); + + expect(response.status).toBe(200); + const data = await response.json(); + expect(data.success).toBe(true); + }); + }); +}); diff --git a/app/routes/api/__tests__/draft.start.test.ts b/app/routes/api/__tests__/draft.start.test.ts new file mode 100644 index 0000000..da9a17b --- /dev/null +++ b/app/routes/api/__tests__/draft.start.test.ts @@ -0,0 +1,152 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { RouterContextProvider } from "react-router"; +import { action } from "~/routes/api/draft.start"; + +const ctx = {} as unknown as RouterContextProvider; + +vi.mock("~/database/context"); +vi.mock("~/server/socket", () => ({ + getSocketIO: vi.fn(), +})); +vi.mock("@clerk/react-router/server", () => ({ + getAuth: vi.fn(), +})); +vi.mock("~/models/commissioner", () => ({ + isCommissioner: vi.fn(), +})); +vi.mock("~/models/draft-timer", () => ({ + deleteSeasonTimers: vi.fn(), + initializeDraftTimers: vi.fn(), +})); + +// ── Fixtures ───────────────────────────────────────────────────────────────── + +const SEASON_ID = "season-1"; +const COMMISSIONER_ID = "commissioner-user-1"; + +const mockDraftSlots = [ + { id: "slot-1", seasonId: SEASON_ID, teamId: "team-1", draftOrder: 1 }, + { id: "slot-2", seasonId: SEASON_ID, teamId: "team-2", draftOrder: 2 }, +]; + +function makeSeason(overrides: Record = {}) { + return { + id: SEASON_ID, + leagueId: "league-1", + status: "pre_draft", + draftInitialTime: 120, + draftIncrementTime: 30, + draftTimerMode: "chess_clock", + ...overrides, + }; +} + +function makeRequest() { + const formData = new FormData(); + formData.append("seasonId", SEASON_ID); + return new Request("http://localhost/api/draft/start", { + method: "POST", + body: formData, + }); +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +describe("draft.start action — timer initialization", () => { + let mockDb: any; + let mockSocketIO: any; + + beforeEach(async () => { + vi.clearAllMocks(); + + const { getAuth } = await import("@clerk/react-router/server"); + vi.mocked(getAuth).mockResolvedValue({ userId: COMMISSIONER_ID } as any); + + const { isCommissioner } = await import("~/models/commissioner"); + vi.mocked(isCommissioner).mockResolvedValue(true); + + mockSocketIO = { to: vi.fn().mockReturnThis(), emit: vi.fn() }; + const socketModule = await import("~/server/socket"); + vi.mocked(socketModule.getSocketIO).mockReturnValue(mockSocketIO); + + mockDb = { + query: { + seasons: { findFirst: vi.fn() }, + draftSlots: { findMany: vi.fn() }, + }, + update: vi.fn().mockReturnThis(), + set: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + }; + + mockDb.query.draftSlots.findMany.mockResolvedValue(mockDraftSlots); + + const { database } = await import("~/database/context"); + vi.mocked(database).mockReturnValue(mockDb); + }); + + describe("chess_clock mode", () => { + it("initializes each team's timer to draftInitialTime", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "chess_clock", draftInitialTime: 120, draftIncrementTime: 30 }) + ); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + const { initializeDraftTimers } = await import("~/models/draft-timer"); + expect(vi.mocked(initializeDraftTimers)).toHaveBeenCalledWith( + SEASON_ID, + expect.any(Array), + 120 // draftInitialTime, not draftIncrementTime + ); + }); + + it("uses the configured draftInitialTime (not the increment)", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "chess_clock", draftInitialTime: 28800, draftIncrementTime: 3600 }) + ); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + const { initializeDraftTimers } = await import("~/models/draft-timer"); + expect(vi.mocked(initializeDraftTimers)).toHaveBeenCalledWith( + SEASON_ID, + expect.any(Array), + 28800 // 8 hours initial bank + ); + }); + }); + + describe("standard mode", () => { + it("initializes each team's timer to draftIncrementTime (the per-pick time)", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "standard", draftInitialTime: 120, draftIncrementTime: 30 }) + ); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + const { initializeDraftTimers } = await import("~/models/draft-timer"); + expect(vi.mocked(initializeDraftTimers)).toHaveBeenCalledWith( + SEASON_ID, + expect.any(Array), + 30 // draftIncrementTime, not draftInitialTime + ); + }); + + it("ignores draftInitialTime — uses only increment for the starting clock", async () => { + // Even though initialTime=600, standard mode should use increment=45 + mockDb.query.seasons.findFirst.mockResolvedValue( + makeSeason({ draftTimerMode: "standard", draftInitialTime: 600, draftIncrementTime: 45 }) + ); + + await action({ request: makeRequest(), params: {}, context: ctx }); + + const { initializeDraftTimers } = await import("~/models/draft-timer"); + expect(vi.mocked(initializeDraftTimers)).toHaveBeenCalledWith( + SEASON_ID, + expect.any(Array), + 45 // increment only — initial time is irrelevant in standard mode + ); + }); + }); +}); diff --git a/app/routes/api/draft.force-manual-pick.ts b/app/routes/api/draft.force-manual-pick.ts index 1a84683..f920509 100644 --- a/app/routes/api/draft.force-manual-pick.ts +++ b/app/routes/api/draft.force-manual-pick.ts @@ -145,6 +145,14 @@ export async function action(args: ActionFunctionArgs) { return Response.json({ error: reason }, { status: 400 }); } + // Snapshot the team's time bank before any writes (used by chess clock branch below). + const timerSnapshot = await db.query.draftTimers.findFirst({ + where: and( + eq(schema.draftTimers.seasonId, seasonId), + eq(schema.draftTimers.teamId, teamId) + ), + }); + // Create the draft pick const [draftPick] = await db .insert(schema.draftPicks) @@ -165,25 +173,30 @@ export async function action(args: ActionFunctionArgs) { const totalPicks = totalTeams * season.draftRounds; const isDraftComplete = nextPickNumber > totalPicks; - // Add increment to the team that just picked (atomic to avoid race with timer loop) + // Commissioner force picks do not earn bank time. + // Standard mode: reset to the per-pick time (so the next turn starts fresh). + // Chess clock mode: leave the bank frozen — no DB update, use the pre-pick snapshot. const incrementTime = season.draftIncrementTime || 30; - const [updatedTimer] = await db - .update(schema.draftTimers) - .set({ - timeRemaining: sql`${schema.draftTimers.timeRemaining} + ${incrementTime}`, - updatedAt: new Date(), - }) - .where( - and( - eq(schema.draftTimers.seasonId, seasonId), - eq(schema.draftTimers.teamId, teamId) - ) - ) - .returning(); + let newTimeRemaining: number; - const newTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime; - if (!updatedTimer) { - await db.insert(schema.draftTimers).values({ seasonId, teamId, timeRemaining: newTimeRemaining }); + if (season.draftTimerMode === "standard") { + const [updatedTimer] = await db + .update(schema.draftTimers) + .set({ timeRemaining: sql`${incrementTime}`, updatedAt: new Date() }) + .where( + and( + eq(schema.draftTimers.seasonId, seasonId), + eq(schema.draftTimers.teamId, teamId) + ) + ) + .returning(); + newTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime; + if (!updatedTimer) { + await db.insert(schema.draftTimers).values({ seasonId, teamId, timeRemaining: newTimeRemaining }); + } + } else { + // Chess clock: bank is frozen — reuse the pre-pick snapshot, no DB write needed. + newTimeRemaining = timerSnapshot?.timeRemaining ?? 0; } // Emit timer update to all clients diff --git a/app/routes/api/draft.make-pick.ts b/app/routes/api/draft.make-pick.ts index ff6cd5a..23aa717 100644 --- a/app/routes/api/draft.make-pick.ts +++ b/app/routes/api/draft.make-pick.ts @@ -202,27 +202,51 @@ export async function action(args: ActionFunctionArgs) { const totalPicks = totalTeams * season.draftRounds; const isDraftComplete = nextPickNumber > totalPicks; - // Add increment to the team that just picked (post-pick reward). - // Use an atomic SQL update so the timer loop cannot overwrite this increment - // via a concurrent read-modify-write decrement. + // Update the picking team's timer after their pick. + // Standard mode: always reset to the per-pick time, regardless of who picked. + // Chess clock, owner pick: add the increment (reward for picking promptly). + // Chess clock, commissioner/admin pick: no change — forced picks don't earn bank time. const incrementTime = season.draftIncrementTime || 30; - const [updatedTimer] = await db - .update(schema.draftTimers) - .set({ - timeRemaining: sql`${schema.draftTimers.timeRemaining} + ${incrementTime}`, - updatedAt: new Date(), - }) - .where( - and( - eq(schema.draftTimers.seasonId, seasonId), - eq(schema.draftTimers.teamId, currentDraftSlot.teamId) - ) - ) - .returning(); + let newTimeRemaining: number; - // Safety fallback: timer row should always exist after draft start - const newTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime; - if (!updatedTimer) { + let updatedTimer: { timeRemaining: number } | undefined; + + if (season.draftTimerMode === "standard") { + // Atomic reset so the timer loop cannot race with this write. + [updatedTimer] = await db + .update(schema.draftTimers) + .set({ timeRemaining: sql`${incrementTime}`, updatedAt: new Date() }) + .where( + and( + eq(schema.draftTimers.seasonId, seasonId), + eq(schema.draftTimers.teamId, currentDraftSlot.teamId) + ) + ) + .returning(); + newTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime; + } else if (isTeamOwner) { + // Chess clock, owner pick: earn the increment (atomic add). + [updatedTimer] = await db + .update(schema.draftTimers) + .set({ + timeRemaining: sql`${schema.draftTimers.timeRemaining} + ${incrementTime}`, + updatedAt: new Date(), + }) + .where( + and( + eq(schema.draftTimers.seasonId, seasonId), + eq(schema.draftTimers.teamId, currentDraftSlot.teamId) + ) + ) + .returning(); + newTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime; + } else { + // Chess clock, commissioner/admin pick: bank is frozen — no DB update needed. + newTimeRemaining = timerSnapshot?.timeRemaining ?? 0; + } + + // If the timer row didn't exist yet, seed it (standard and chess clock owner branches only). + if (!updatedTimer && (season.draftTimerMode === "standard" || isTeamOwner)) { await db.insert(schema.draftTimers).values({ seasonId, teamId: currentDraftSlot.teamId, diff --git a/app/routes/api/draft.start.ts b/app/routes/api/draft.start.ts index 754dbbf..1fb7c07 100644 --- a/app/routes/api/draft.start.ts +++ b/app/routes/api/draft.start.ts @@ -61,7 +61,12 @@ export async function action(args: ActionFunctionArgs) { }) .where(eq(schema.seasons.id, seasonId)); - const initialTime = season.draftInitialTime || 120; + // Standard mode: each pick starts with exactly the increment (no carry-over bank). + // Chess clock mode: each team starts with the full initial time bank. + const initialTime = + season.draftTimerMode === "standard" + ? season.draftIncrementTime || 30 + : season.draftInitialTime || 120; // Reset timers for all teams await deleteSeasonTimers(seasonId); diff --git a/app/routes/leagues/$leagueId.settings.tsx b/app/routes/leagues/$leagueId.settings.tsx index 451c596..0d89fc9 100644 --- a/app/routes/leagues/$leagueId.settings.tsx +++ b/app/routes/leagues/$leagueId.settings.tsx @@ -24,6 +24,7 @@ import * as schema from "~/database/schema"; import { deleteAllDraftPicks } from "~/models/draft-pick"; import { clearAllQueuesForSeason } from "~/models/draft-queue"; import { deleteSeasonTimers } from "~/models/draft-timer"; +import { parseDraftSpeed } from "~/lib/draft-timer"; import type { Route } from "./+types/$leagueId.settings"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; @@ -452,32 +453,13 @@ export async function action(args: Route.ActionArgs) { const draftDateTime = formData.get("draftDateTime"); const draftRounds = formData.get("draftRounds"); const draftSpeed = formData.get("draftSpeed"); + const draftTimerMode = formData.get("draftTimerMode") as "chess_clock" | "standard" | null; // Map draft speed to time values - let draftInitialTime: number; - let draftIncrementTime: number; - - switch (draftSpeed) { - case "fast": - draftInitialTime = 60; - draftIncrementTime = 10; - break; - case "standard": - draftInitialTime = 120; - draftIncrementTime = 15; - break; - case "slow": - draftInitialTime = 28800; // 8 hours - draftIncrementTime = 3600; // 1 hour - break; - case "very-slow": - draftInitialTime = 43200; // 12 hours - draftIncrementTime = 3600; // 1 hour - break; - default: - draftInitialTime = 120; - draftIncrementTime = 15; - } + const { draftInitialTime, draftIncrementTime } = parseDraftSpeed( + draftSpeed as string | null, + draftTimerMode ?? "chess_clock" + ); // League-level fields (name, isPublicDraftBoard) are already saved above. // Now handle season-specific updates. @@ -512,6 +494,11 @@ export async function action(args: Route.ActionArgs) { seasonUpdates.draftIncrementTime = draftIncrementTime; } + // Timer mode (only if submitted; disabled during active draft) + if (draftTimerMode !== null) { + seasonUpdates.draftTimerMode = draftTimerMode; + } + // Handle scoring rules (only if in pre_draft status) if (season.status === "pre_draft") { const scoringFields = [ @@ -663,6 +650,7 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone const { league, season, teams, teamCount, teamsWithOwners, allSportsSeasons, draftSlots, isAdmin, allUsers, leagueMembers, ownerMap, commissioners, currentUserId } = loaderData; const navigation = useNavigation(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [timerMode, setTimerMode] = useState<"chess_clock" | "standard">(season?.draftTimerMode ?? "chess_clock"); const [selectedSports, setSelectedSports] = useState>( new Set(season?.seasonSports?.map((s: any) => s.sportsSeason.id) || []) ); @@ -1000,6 +988,25 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone

+
+ + +

+ Chess Clock: unused time carries over between picks. Standard: timer resets each pick. +

+
+

- Initial time + increment per pick + {timerMode === "standard" + ? "Time per pick — resets to this value after each selection" + : "Initial time bank + increment added after each pick"}

diff --git a/app/routes/leagues/__tests__/draft-reset.test.ts b/app/routes/leagues/__tests__/draft-reset.test.ts index 8f51d6a..f1f3da7 100644 --- a/app/routes/leagues/__tests__/draft-reset.test.ts +++ b/app/routes/leagues/__tests__/draft-reset.test.ts @@ -22,6 +22,7 @@ const createMockSeason = (overrides: { draftDateTime: null, draftInitialTime: 120, draftIncrementTime: 15, + draftTimerMode: 'chess_clock' as const, currentPickNumber: null, draftStartedAt: null, draftPaused: false, diff --git a/app/routes/leagues/__tests__/settings-update.test.ts b/app/routes/leagues/__tests__/settings-update.test.ts index a1cc858..413b600 100644 --- a/app/routes/leagues/__tests__/settings-update.test.ts +++ b/app/routes/leagues/__tests__/settings-update.test.ts @@ -54,6 +54,7 @@ const mockSeason = { draftDateTime: null, draftInitialTime: 120, draftIncrementTime: 15, + draftTimerMode: 'chess_clock' as const, currentPickNumber: null, draftStartedAt: null, draftPaused: false, diff --git a/app/routes/leagues/new.tsx b/app/routes/leagues/new.tsx index 8a33b20..ffa6b56 100644 --- a/app/routes/leagues/new.tsx +++ b/app/routes/leagues/new.tsx @@ -39,6 +39,7 @@ import { PopoverTrigger, } from "~/components/ui/popover"; import { cn } from "~/lib/utils"; +import { parseDraftSpeed } from "~/lib/draft-timer"; import { ScoringRulesEditor } from "~/components/scoring/ScoringRulesEditor"; export function meta(): Route.MetaDescriptors { @@ -83,32 +84,11 @@ export async function action(args: Route.ActionArgs) { const draftRounds = formData.get("draftRounds"); const draftDateTime = formData.get("draftDateTime"); const draftSpeed = formData.get("draftSpeed"); - + const draftTimerMode = (formData.get("draftTimerMode") as "chess_clock" | "standard") || "chess_clock"; + // Map draft speed to time values - let draftInitialTimeNum: number; - let draftIncrementTimeNum: number; - - switch (draftSpeed) { - case "fast": - draftInitialTimeNum = 60; - draftIncrementTimeNum = 10; - break; - case "standard": - draftInitialTimeNum = 120; - draftIncrementTimeNum = 15; - break; - case "slow": - draftInitialTimeNum = 28800; // 8 hours - draftIncrementTimeNum = 3600; // 1 hour - break; - case "very-slow": - draftInitialTimeNum = 43200; // 12 hours - draftIncrementTimeNum = 3600; // 1 hour - break; - default: - draftInitialTimeNum = 120; - draftIncrementTimeNum = 15; - } + const { draftInitialTime: draftInitialTimeNum, draftIncrementTime: draftIncrementTimeNum } = + parseDraftSpeed(draftSpeed as string | null, draftTimerMode); // Validation if (typeof name !== "string" || !name.trim()) { @@ -173,6 +153,7 @@ export async function action(args: Route.ActionArgs) { draftDateTime: typeof draftDateTime === "string" && draftDateTime ? new Date(draftDateTime) : null, draftInitialTime: draftInitialTimeNum, draftIncrementTime: draftIncrementTimeNum, + draftTimerMode, ...scoringRules, }); @@ -224,6 +205,7 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro const [draftRounds, setDraftRounds] = useState(20); const [draftDate, setDraftDate] = useState(); const [draftTime, setDraftTime] = useState(""); + const [timerMode, setTimerMode] = useState<"chess_clock" | "standard">("chess_clock"); const handleSportToggle = (sportId: string) => { const newSelected = new Set(selectedSports); @@ -383,22 +365,61 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro

+
+ + +

+ Chess Clock: unused time carries over between picks. Standard: timer resets each pick. +

+
+

- Initial time + increment per pick + {timerMode === "standard" + ? "Time per pick — resets to this value after each selection" + : "Initial time bank + increment added after each pick"}

diff --git a/database/schema.ts b/database/schema.ts index 231d20c..8a50d02 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -64,6 +64,11 @@ export const autodraftModeEnum = pgEnum("autodraft_mode", [ "while_on", ]); +export const draftTimerModeEnum = pgEnum("draft_timer_mode", [ + "chess_clock", + "standard", +]); + export const probabilitySourceEnum = pgEnum("probability_source", [ "manual", "futures_odds", @@ -130,6 +135,7 @@ export const seasons = pgTable("seasons", { draftDateTime: timestamp("draft_date_time"), draftInitialTime: integer("draft_initial_time").notNull().default(120), // seconds draftIncrementTime: integer("draft_increment_time").notNull().default(30), // seconds + draftTimerMode: draftTimerModeEnum("draft_timer_mode").notNull().default("chess_clock"), currentPickNumber: integer("current_pick_number").default(1), draftStartedAt: timestamp("draft_started_at"), draftPaused: boolean("draft_paused").notNull().default(false), diff --git a/drizzle/0053_smooth_kingpin.sql b/drizzle/0053_smooth_kingpin.sql new file mode 100644 index 0000000..185c1b8 --- /dev/null +++ b/drizzle/0053_smooth_kingpin.sql @@ -0,0 +1,2 @@ +CREATE TYPE "public"."draft_timer_mode" AS ENUM('chess_clock', 'standard');--> statement-breakpoint +ALTER TABLE "seasons" ADD COLUMN "draft_timer_mode" "draft_timer_mode" DEFAULT 'chess_clock' NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0053_snapshot.json b/drizzle/meta/0053_snapshot.json new file mode 100644 index 0000000..7a0889c --- /dev/null +++ b/drizzle/meta/0053_snapshot.json @@ -0,0 +1,3596 @@ +{ + "id": "a84e2784-1121-4715-aa97-79e3780ebf1f", + "prevId": "bf782222-1d5d-4fc3-b7dc-117780379a46", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.autodraft_settings": { + "name": "autodraft_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mode": { + "name": "mode", + "type": "autodraft_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'next_pick'" + }, + "queue_only": { + "name": "queue_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "autodraft_settings_season_id_seasons_id_fk": { + "name": "autodraft_settings_season_id_seasons_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "autodraft_settings_team_id_teams_id_fk": { + "name": "autodraft_settings_team_id_teams_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.commissioners": { + "name": "commissioners", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "commissioners_league_id_leagues_id_fk": { + "name": "commissioners_league_id_leagues_id_fk", + "tableFrom": "commissioners", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_picks": { + "name": "draft_picks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pick_number": { + "name": "pick_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pick_in_round": { + "name": "pick_in_round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "picked_by_user_id": { + "name": "picked_by_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "picked_by_type": { + "name": "picked_by_type", + "type": "picked_by_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "draft_picks_season_pick_unique": { + "name": "draft_picks_season_pick_unique", + "columns": [ + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pick_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "draft_picks_season_id_seasons_id_fk": { + "name": "draft_picks_season_id_seasons_id_fk", + "tableFrom": "draft_picks", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_team_id_teams_id_fk": { + "name": "draft_picks_team_id_teams_id_fk", + "tableFrom": "draft_picks", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_participant_id_participants_id_fk": { + "name": "draft_picks_participant_id_participants_id_fk", + "tableFrom": "draft_picks", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_queue": { + "name": "draft_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_position": { + "name": "queue_position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_queue_season_id_seasons_id_fk": { + "name": "draft_queue_season_id_seasons_id_fk", + "tableFrom": "draft_queue", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_team_id_teams_id_fk": { + "name": "draft_queue_team_id_teams_id_fk", + "tableFrom": "draft_queue", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_participant_id_participants_id_fk": { + "name": "draft_queue_participant_id_participants_id_fk", + "tableFrom": "draft_queue", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_slots": { + "name": "draft_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "draft_order": { + "name": "draft_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_slots_season_id_seasons_id_fk": { + "name": "draft_slots_season_id_seasons_id_fk", + "tableFrom": "draft_slots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_slots_team_id_teams_id_fk": { + "name": "draft_slots_team_id_teams_id_fk", + "tableFrom": "draft_slots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_timers": { + "name": "draft_timers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "time_remaining": { + "name": "time_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_timers_season_id_seasons_id_fk": { + "name": "draft_timers_season_id_seasons_id_fk", + "tableFrom": "draft_timers", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_timers_team_id_teams_id_fk": { + "name": "draft_timers_team_id_teams_id_fk", + "tableFrom": "draft_timers", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.event_results": { + "name": "event_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "qualifying_points_awarded": { + "name": "qualifying_points_awarded", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "raw_score": { + "name": "raw_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "event_results_scoring_event_id_scoring_events_id_fk": { + "name": "event_results_scoring_event_id_scoring_events_id_fk", + "tableFrom": "event_results", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "event_results_participant_id_participants_id_fk": { + "name": "event_results_participant_id_participants_id_fk", + "tableFrom": "event_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.leagues": { + "name": "leagues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "current_season_id": { + "name": "current_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_public_draft_board": { + "name": "is_public_draft_board", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discord_webhook_url": { + "name": "discord_webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_ev_snapshots": { + "name": "participant_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "calculated_ev": { + "name": "calculated_ev", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "participant_ev_snapshots_unique": { + "name": "participant_ev_snapshots_unique", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "participant_ev_snapshots_participant_id_participants_id_fk": { + "name": "participant_ev_snapshots_participant_id_participants_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk": { + "name": "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_expected_values": { + "name": "participant_expected_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "probability_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'manual'" + }, + "source_odds": { + "name": "source_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_expected_values_participant_id_participants_id_fk": { + "name": "participant_expected_values_participant_id_participants_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_expected_values_sports_season_id_sports_seasons_id_fk": { + "name": "participant_expected_values_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_qualifying_totals": { + "name": "participant_qualifying_totals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_qualifying_points": { + "name": "total_qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "events_scored": { + "name": "events_scored", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "final_ranking": { + "name": "final_ranking", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_qualifying_totals_participant_id_participants_id_fk": { + "name": "participant_qualifying_totals_participant_id_participants_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk": { + "name": "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_results": { + "name": "participant_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "final_position": { + "name": "final_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_partial_score": { + "name": "is_partial_score", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualifying_points": { + "name": "qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_results_participant_id_participants_id_fk": { + "name": "participant_results_participant_id_participants_id_fk", + "tableFrom": "participant_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_season_results": { + "name": "participant_season_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "current_points": { + "name": "current_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_position": { + "name": "current_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_season_results_participant_id_participants_id_fk": { + "name": "participant_season_results_participant_id_participants_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_season_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_season_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participants": { + "name": "participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "short_name": { + "name": "short_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participants_sports_season_id_sports_seasons_id_fk": { + "name": "participants_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participants", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_games": { + "name": "playoff_match_games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_number": { + "name": "game_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "playoff_match_game_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_games_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_games_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_games_winner_id_participants_id_fk": { + "name": "playoff_match_games_winner_id_participants_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_odds": { + "name": "playoff_match_odds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "moneyline_odds": { + "name": "moneyline_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "implied_probability": { + "name": "implied_probability", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "odds_source": { + "name": "odds_source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_odds_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_odds_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_odds_participant_id_participants_id_fk": { + "name": "playoff_match_odds_participant_id_participants_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_matches": { + "name": "playoff_matches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "match_number": { + "name": "match_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "participant1_id": { + "name": "participant1_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "participant2_id": { + "name": "participant2_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "loser_id": { + "name": "loser_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "is_scoring": { + "name": "is_scoring", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "template_round": { + "name": "template_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "seed_info": { + "name": "seed_info", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_matches_scoring_event_id_scoring_events_id_fk": { + "name": "playoff_matches_scoring_event_id_scoring_events_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_matches_participant1_id_participants_id_fk": { + "name": "playoff_matches_participant1_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_participant2_id_participants_id_fk": { + "name": "playoff_matches_participant2_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_winner_id_participants_id_fk": { + "name": "playoff_matches_winner_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_loser_id_participants_id_fk": { + "name": "playoff_matches_loser_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "loser_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.qualifying_point_config": { + "name": "qualifying_point_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "points": { + "name": "points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "qualifying_point_config_sports_season_id_sports_seasons_id_fk": { + "name": "qualifying_point_config_sports_season_id_sports_seasons_id_fk", + "tableFrom": "qualifying_point_config", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scoring_events": { + "name": "scoring_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "event_starts_at": { + "name": "event_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "event_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "playoff_round": { + "name": "playoff_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "is_qualifying_event": { + "name": "is_qualifying_event", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bracket_template_id": { + "name": "bracket_template_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "scoring_starts_at_round": { + "name": "scoring_starts_at_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "bracket_region_config": { + "name": "bracket_region_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "scoring_events_sports_season_id_sports_seasons_id_fk": { + "name": "scoring_events_sports_season_id_sports_seasons_id_fk", + "tableFrom": "scoring_events", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_sports": { + "name": "season_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_sports_season_id_seasons_id_fk": { + "name": "season_sports_season_id_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_template_sports": { + "name": "season_template_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_template_sports_template_id_season_templates_id_fk": { + "name": "season_template_sports_template_id_season_templates_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_template_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_template_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_templates": { + "name": "season_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seasons": { + "name": "seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pre_draft'" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "draft_rounds": { + "name": "draft_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20 + }, + "flex_spots": { + "name": "flex_spots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "draft_date_time": { + "name": "draft_date_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_initial_time": { + "name": "draft_initial_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 120 + }, + "draft_increment_time": { + "name": "draft_increment_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "draft_timer_mode": { + "name": "draft_timer_mode", + "type": "draft_timer_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'chess_clock'" + }, + "current_pick_number": { + "name": "current_pick_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "draft_started_at": { + "name": "draft_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_paused": { + "name": "draft_paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "invite_code": { + "name": "invite_code", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "points_for_1st": { + "name": "points_for_1st", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "points_for_2nd": { + "name": "points_for_2nd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 70 + }, + "points_for_3rd": { + "name": "points_for_3rd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "points_for_4th": { + "name": "points_for_4th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 40 + }, + "points_for_5th": { + "name": "points_for_5th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_6th": { + "name": "points_for_6th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_7th": { + "name": "points_for_7th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "points_for_8th": { + "name": "points_for_8th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "seasons_league_id_leagues_id_fk": { + "name": "seasons_league_id_leagues_id_fk", + "tableFrom": "seasons", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "seasons_template_id_season_templates_id_fk": { + "name": "seasons_template_id_season_templates_id_fk", + "tableFrom": "seasons", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "seasons_invite_code_unique": { + "name": "seasons_invite_code_unique", + "nullsNotDistinct": false, + "columns": [ + "invite_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports": { + "name": "sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "sport_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_url": { + "name": "icon_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "simulator_type": { + "name": "simulator_type", + "type": "simulator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sports_slug_unique": { + "name": "sports_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports_seasons": { + "name": "sports_seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sport_id": { + "name": "sport_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_date": { + "name": "end_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "sports_season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'upcoming'" + }, + "scoring_type": { + "name": "scoring_type", + "type": "scoring_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scoring_pattern": { + "name": "scoring_pattern", + "type": "scoring_pattern", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "total_majors": { + "name": "total_majors", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "majors_completed": { + "name": "majors_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qualifying_points_finalized": { + "name": "qualifying_points_finalized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "elo_calibration_exponent": { + "name": "elo_calibration_exponent", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "elo_min_rating": { + "name": "elo_min_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1250 + }, + "elo_max_rating": { + "name": "elo_max_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1750 + }, + "simulation_status": { + "name": "simulation_status", + "type": "simulation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "is_draftable": { + "name": "is_draftable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sports_seasons_sport_id_sports_id_fk": { + "name": "sports_seasons_sport_id_sports_id_fk", + "tableFrom": "sports_seasons", + "tableTo": "sports", + "columnsFrom": [ + "sport_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_ev_snapshots": { + "name": "team_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_ev_snapshots_unique": { + "name": "team_ev_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_ev_snapshots_team_id_teams_id_fk": { + "name": "team_ev_snapshots_team_id_teams_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_ev_snapshots_season_id_seasons_id_fk": { + "name": "team_ev_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_sport_scores": { + "name": "team_sport_scores", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "participants_completed": { + "name": "participants_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_total": { + "name": "participants_total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_sport_scores_team_id_teams_id_fk": { + "name": "team_sport_scores_team_id_teams_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_sport_scores_sports_season_id_sports_seasons_id_fk": { + "name": "team_sport_scores_sports_season_id_sports_seasons_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings": { + "name": "team_standings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_rank": { + "name": "current_rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_rank": { + "name": "previous_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_standings_team_id_teams_id_fk": { + "name": "team_standings_team_id_teams_id_fk", + "tableFrom": "team_standings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_season_id_seasons_id_fk": { + "name": "team_standings_season_id_seasons_id_fk", + "tableFrom": "team_standings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings_snapshots": { + "name": "team_standings_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "rank": { + "name": "rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_standings_snapshots_unique": { + "name": "team_standings_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_standings_snapshots_team_id_teams_id_fk": { + "name": "team_standings_snapshots_team_id_teams_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_snapshots_season_id_seasons_id_fk": { + "name": "team_standings_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_season_id_seasons_id_fk": { + "name": "teams_season_id_seasons_id_fk", + "tableFrom": "teams", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_group_members": { + "name": "tournament_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tournament_group_id": { + "name": "tournament_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_group_members_tournament_group_id_tournament_groups_id_fk": { + "name": "tournament_group_members_tournament_group_id_tournament_groups_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "tournament_groups", + "columnsFrom": [ + "tournament_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tournament_group_members_participant_id_participants_id_fk": { + "name": "tournament_group_members_participant_id_participants_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_groups": { + "name": "tournament_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_groups_scoring_event_id_scoring_events_id_fk": { + "name": "tournament_groups_scoring_event_id_scoring_events_id_fk", + "tableFrom": "tournament_groups", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "clerk_id": { + "name": "clerk_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "nullsNotDistinct": false, + "columns": [ + "clerk_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.autodraft_mode": { + "name": "autodraft_mode", + "schema": "public", + "values": [ + "next_pick", + "while_on" + ] + }, + "public.draft_timer_mode": { + "name": "draft_timer_mode", + "schema": "public", + "values": [ + "chess_clock", + "standard" + ] + }, + "public.event_type": { + "name": "event_type", + "schema": "public", + "values": [ + "playoff_game", + "major_tournament", + "final_standings", + "schedule_event" + ] + }, + "public.picked_by_type": { + "name": "picked_by_type", + "schema": "public", + "values": [ + "owner", + "commissioner", + "admin", + "auto" + ] + }, + "public.playoff_match_game_status": { + "name": "playoff_match_game_status", + "schema": "public", + "values": [ + "scheduled", + "complete", + "postponed" + ] + }, + "public.probability_source": { + "name": "probability_source", + "schema": "public", + "values": [ + "manual", + "futures_odds", + "elo_simulation", + "performance_model" + ] + }, + "public.scoring_pattern": { + "name": "scoring_pattern", + "schema": "public", + "values": [ + "playoff_bracket", + "season_standings", + "qualifying_points" + ] + }, + "public.scoring_type": { + "name": "scoring_type", + "schema": "public", + "values": [ + "playoffs", + "regular_season", + "majors" + ] + }, + "public.season_status": { + "name": "season_status", + "schema": "public", + "values": [ + "pre_draft", + "draft", + "active", + "completed" + ] + }, + "public.simulation_status": { + "name": "simulation_status", + "schema": "public", + "values": [ + "idle", + "running", + "failed" + ] + }, + "public.simulator_type": { + "name": "simulator_type", + "schema": "public", + "values": [ + "f1_standings", + "indycar_standings", + "golf_qualifying_points", + "playoff_bracket", + "ucl_bracket", + "ncaam_bracket", + "ncaaw_bracket", + "nba_bracket", + "nhl_bracket" + ] + }, + "public.sport_type": { + "name": "sport_type", + "schema": "public", + "values": [ + "team", + "individual" + ] + }, + "public.sports_season_status": { + "name": "sports_season_status", + "schema": "public", + "values": [ + "upcoming", + "active", + "completed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 940187f..fd4b097 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -372,6 +372,13 @@ "when": 1773897678653, "tag": "0052_new_warbird", "breakpoints": true + }, + { + "idx": 53, + "version": "7", + "when": 1773903201189, + "tag": "0053_smooth_kingpin", + "breakpoints": true } ] } \ No newline at end of file