diff --git a/app/models/draft-utils.ts b/app/models/draft-utils.ts index 618adda..a8af2dd 100644 --- a/app/models/draft-utils.ts +++ b/app/models/draft-utils.ts @@ -1,6 +1,7 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq, and, notInArray, desc, inArray } from "drizzle-orm"; +import type { InferSelectModel } from "drizzle-orm"; import { getTeamQueue } from "./draft-queue"; import { isParticipantDrafted, getDraftPicksWithSports, getTeamDraftPicksWithSports } from "./draft-pick"; import { getParticipantsForSeasonWithSports } from "./participant"; @@ -12,11 +13,13 @@ import { getSocketIO } from "../../server/socket"; * Check if the next team has autodraft enabled and immediately execute their pick * This is called after a pick is made to chain autodraft picks */ +type DraftSlot = { teamId: string; draftOrder: number }; + export async function checkAndTriggerNextAutodraft(params: { seasonId: string; nextPickNumber: number; totalTeams: number; - draftSlots: any[]; + draftSlots: DraftSlot[]; db?: ReturnType; }): Promise { const { seasonId, totalTeams, draftSlots, db: providedDb } = params; @@ -367,20 +370,26 @@ export function getTeamForPick( * @param params.db - Database instance (optional, will use database() if not provided) * @returns Result object with success status and pick data */ +type AutodraftSettings = InferSelectModel; + export async function executeAutoPick(params: { seasonId: string; teamId: string; pickNumber: number; triggeredBy: "commissioner" | "timer"; commissionerUserId?: string; - autodraftSettings?: any; + autodraftSettings?: AutodraftSettings | null; db?: ReturnType; chainEnabled?: boolean; // Set to false when called from within the autodraft chain to prevent recursion }): Promise<{ success: boolean; error?: string; - pick?: any; - participant?: any; + pick?: InferSelectModel; + participant?: InferSelectModel & { + sportsSeason: InferSelectModel & { + sport: InferSelectModel; + }; + }; nextPickNumber?: number; isDraftComplete?: boolean; }> { 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 1c30d93..d1ea88f 100644 --- a/app/routes/api/__tests__/draft.force-manual-pick.test.ts +++ b/app/routes/api/__tests__/draft.force-manual-pick.test.ts @@ -1,6 +1,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { RouterContextProvider } from "react-router"; import { action } from "~/routes/api/draft.force-manual-pick"; +const ctx = {} as unknown as RouterContextProvider; + vi.mock("~/database/context"); vi.mock("~/server/socket", () => ({ getSocketIO: vi.fn(), @@ -183,7 +186,7 @@ describe("draft.force-manual-pick action", () => { const { getAuth } = await import("@clerk/react-router/server"); vi.mocked(getAuth).mockResolvedValue({ userId: null } as any); - const response = await action({ request: defaultPickRequest(), params: {}, context: {} }); + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(response.status).toBe(401); }); @@ -191,7 +194,7 @@ describe("draft.force-manual-pick action", () => { it("returns 403 when authenticated user is not a commissioner", async () => { mockDb.query.commissioners.findFirst.mockResolvedValue(null); - const response = await action({ request: defaultPickRequest(), params: {}, context: {} }); + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(response.status).toBe(403); const data = await response.json(); @@ -206,7 +209,7 @@ describe("draft.force-manual-pick action", () => { const response = await action({ request: makeRequest({ seasonId: SEASON_ID }), // missing teamId, participantId, pickNumber params: {}, - context: {}, + context: ctx, }); expect(response.status).toBe(400); @@ -215,18 +218,45 @@ describe("draft.force-manual-pick action", () => { it("returns 404 when the season does not exist", async () => { mockDb.query.seasons.findFirst.mockResolvedValue(null); - const response = await action({ request: defaultPickRequest(), params: {}, context: {} }); + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(response.status).toBe(404); }); - it("returns 400 when the participant is already drafted", async () => { - mockDb.query.draftPicks.findFirst.mockResolvedValue({ + it("returns 400 when the draft is not active", async () => { + mockDb.query.seasons.findFirst.mockResolvedValue({ ...mockSeason, status: "pre_draft" }); + + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); + + expect(response.status).toBe(400); + const data = await response.json(); + expect(data.error).toMatch(/not currently active/i); + }); + + it("returns 400 when a pick already exists at the requested slot", async () => { + mockDb.query.draftPicks.findFirst.mockResolvedValueOnce({ id: "existing-pick", - participantId: PARTICIPANT_ID, + participantId: "other-participant", }); - const response = await action({ request: defaultPickRequest(), params: {}, context: {} }); + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); + + expect(response.status).toBe(400); + const data = await response.json(); + expect(data.error).toMatch(/already exists at this slot/i); + }); + + it("returns 400 when the participant is already drafted", async () => { + // First call: slot check (no pick at this slot number) + // Second call: participant check (participant already drafted elsewhere) + mockDb.query.draftPicks.findFirst + .mockResolvedValueOnce(null) + .mockResolvedValueOnce({ + id: "existing-pick", + participantId: PARTICIPANT_ID, + }); + + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(response.status).toBe(400); const data = await response.json(); @@ -236,7 +266,7 @@ describe("draft.force-manual-pick action", () => { it("returns 404 when the participant does not exist", async () => { mockDb.query.participants.findFirst.mockResolvedValue(null); - const response = await action({ request: defaultPickRequest(), params: {}, context: {} }); + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(response.status).toBe(404); }); @@ -248,7 +278,7 @@ describe("draft.force-manual-pick action", () => { ineligibleReasons: { [SPORT_ID]: "Sport limit reached" }, } as any); - const response = await action({ request: defaultPickRequest(), params: {}, context: {} }); + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(response.status).toBe(400); const data = await response.json(); @@ -260,7 +290,7 @@ describe("draft.force-manual-pick action", () => { describe("successful pick", () => { it("returns 200 with pick data and next pick number", async () => { - const response = await action({ request: defaultPickRequest(), params: {}, context: {} }); + const response = await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(response.status).toBe(200); const data = await response.json(); @@ -280,7 +310,7 @@ describe("draft.force-manual-pick action", () => { pickNumber: "6", }), params: {}, - context: {}, + context: ctx, }); const data = await response.json(); @@ -288,7 +318,7 @@ describe("draft.force-manual-pick action", () => { }); it("emits pick-made to the correct draft room", async () => { - await action({ request: defaultPickRequest(), params: {}, context: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(mockSocketIO.to).toHaveBeenCalledWith(`draft-${SEASON_ID}`); expect(mockSocketIO.emit).toHaveBeenCalledWith( @@ -298,7 +328,7 @@ describe("draft.force-manual-pick action", () => { }); it("removes the participant from all team queues in the season", async () => { - await action({ request: defaultPickRequest(), params: {}, context: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(mockDb.delete).toHaveBeenCalled(); expect(mockSocketIO.emit).toHaveBeenCalledWith("participant-removed-from-queues", { @@ -324,7 +354,7 @@ describe("draft.force-manual-pick action", () => { timeRemaining: 75, }); - await action({ request: defaultPickRequest(), params: {}, context: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(mockDb.set).toHaveBeenCalledWith( expect.objectContaining({ timeRemaining: 105 }) @@ -339,7 +369,7 @@ describe("draft.force-manual-pick action", () => { timeRemaining: 75, }); - await action({ request: defaultPickRequest(), params: {}, context: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(mockSocketIO.emit).toHaveBeenCalledWith("timer-update", { seasonId: SEASON_ID, @@ -352,7 +382,7 @@ describe("draft.force-manual-pick action", () => { it("creates a new timer for the picking team if they have no existing timer", async () => { mockDb.query.draftTimers.findFirst.mockResolvedValue(null); - await action({ request: defaultPickRequest(), params: {}, context: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); // insert should be called for: (1) draft pick, (2) new timer (0 + 30 = 30s) expect(mockDb.insert).toHaveBeenCalledTimes(2); @@ -367,7 +397,7 @@ describe("draft.force-manual-pick action", () => { timeRemaining: 100, }); - await action({ request: defaultPickRequest(), params: {}, context: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); // 100 + 30 = 130, not 120 (which would be a reset to initialTime) expect(mockDb.set).toHaveBeenCalledWith( @@ -387,7 +417,7 @@ describe("draft.force-manual-pick action", () => { // once to get the picking team's current balance, and // once to find (and then reset) the next team's timer. // After the fix it should be called exactly once. - await action({ request: defaultPickRequest(), params: {}, context: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(mockDb.query.draftTimers.findFirst).toHaveBeenCalledTimes(1); }); @@ -395,7 +425,7 @@ describe("draft.force-manual-pick action", () => { 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: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); const nextTeamTimerEmits = mockSocketIO.emit.mock.calls.filter( ([event, payload]: [string, any]) => @@ -408,7 +438,7 @@ describe("draft.force-manual-pick action", () => { // 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: {} }); + await action({ request: defaultPickRequest(), params: {}, context: ctx }); expect(mockDb.update).toHaveBeenCalledTimes(2); }); diff --git a/app/routes/api/autodraft.update.ts b/app/routes/api/autodraft.update.ts index 6ffb14b..5da956e 100644 --- a/app/routes/api/autodraft.update.ts +++ b/app/routes/api/autodraft.update.ts @@ -4,10 +4,10 @@ import * as schema from "~/database/schema"; import { eq, and } from "drizzle-orm"; import { getSocketIO } from "~/server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/draft.adjust-time-bank.ts b/app/routes/api/draft.adjust-time-bank.ts index 78246e9..09cc59e 100644 --- a/app/routes/api/draft.adjust-time-bank.ts +++ b/app/routes/api/draft.adjust-time-bank.ts @@ -4,10 +4,10 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { getSocketIO } from "../../../server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/draft.force-autopick.ts b/app/routes/api/draft.force-autopick.ts index c5679d7..a2fb8d8 100644 --- a/app/routes/api/draft.force-autopick.ts +++ b/app/routes/api/draft.force-autopick.ts @@ -4,10 +4,10 @@ import * as schema from "~/database/schema"; import { eq, and } from "drizzle-orm"; import { executeAutoPick } from "~/models/draft-utils"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/draft.force-manual-pick.ts b/app/routes/api/draft.force-manual-pick.ts index 5fa8276..8df1d35 100644 --- a/app/routes/api/draft.force-manual-pick.ts +++ b/app/routes/api/draft.force-manual-pick.ts @@ -7,11 +7,11 @@ import { getDraftPicksWithSports, getTeamDraftPicksWithSports } from "~/models/d import { getParticipantsForSeasonWithSports } from "~/models/participant"; import { getSeasonSportsSimple } from "~/models/season-sport"; import { getSocketIO } from "../../../server/socket"; +import type { ActionFunctionArgs } from "react-router"; -export async function action(args: any) { +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); @@ -50,6 +50,22 @@ export async function action(args: any) { return Response.json({ error: "Only commissioners can force a manual pick" }, { status: 403 }); } + if (season.status !== "draft") { + return Response.json({ error: "Draft is not currently active" }, { status: 400 }); + } + + // Check if a pick already exists at this pick number slot (prevents duplicate picks at same slot) + const existingPickAtSlot = await db.query.draftPicks.findFirst({ + where: and( + eq(schema.draftPicks.seasonId, seasonId), + eq(schema.draftPicks.pickNumber, pickNumber) + ), + }); + + if (existingPickAtSlot) { + return Response.json({ error: "A pick already exists at this slot" }, { status: 400 }); + } + // Check if participant is already drafted const existingPick = await db.query.draftPicks.findFirst({ where: and( diff --git a/app/routes/api/draft.make-pick.ts b/app/routes/api/draft.make-pick.ts index 9d2bdf8..82c33c9 100644 --- a/app/routes/api/draft.make-pick.ts +++ b/app/routes/api/draft.make-pick.ts @@ -8,10 +8,10 @@ import { getParticipantsForSeasonWithSports } from "~/models/participant"; import { getSeasonSportsSimple } from "~/models/season-sport"; import { getSocketIO } from "../../../server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); @@ -56,12 +56,11 @@ export async function action(args: any) { const totalTeams = draftSlots.length; const currentRound = Math.ceil(currentPickNumber / totalTeams); - const isSnakeDraft = true; // Assuming snake draft const isEvenRound = currentRound % 2 === 0; - // Calculate which team should pick + // Calculate which team should pick (snake draft) let pickInRound = ((currentPickNumber - 1) % totalTeams) + 1; - if (isSnakeDraft && isEvenRound) { + if (isEvenRound) { pickInRound = totalTeams - pickInRound + 1; } diff --git a/app/routes/api/draft.pause.ts b/app/routes/api/draft.pause.ts index d11c9bc..36a3c3d 100644 --- a/app/routes/api/draft.pause.ts +++ b/app/routes/api/draft.pause.ts @@ -4,10 +4,10 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { getSocketIO } from "../../../server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/draft.replace-pick.ts b/app/routes/api/draft.replace-pick.ts index cf47755..2c6de37 100644 --- a/app/routes/api/draft.replace-pick.ts +++ b/app/routes/api/draft.replace-pick.ts @@ -8,10 +8,10 @@ import { getParticipantsForSeasonWithSports } from "~/models/participant"; import { getSeasonSportsSimple } from "~/models/season-sport"; import { getSocketIO } from "../../../server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/draft.resume.ts b/app/routes/api/draft.resume.ts index 9ecc499..20fd25e 100644 --- a/app/routes/api/draft.resume.ts +++ b/app/routes/api/draft.resume.ts @@ -4,10 +4,10 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { getSocketIO } from "../../../server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/draft.rollback.ts b/app/routes/api/draft.rollback.ts index 9c18dc2..839ff9e 100644 --- a/app/routes/api/draft.rollback.ts +++ b/app/routes/api/draft.rollback.ts @@ -4,10 +4,10 @@ import * as schema from "~/database/schema"; import { eq, and, gte } from "drizzle-orm"; import { getSocketIO } from "../../../server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/draft.start.ts b/app/routes/api/draft.start.ts index b53fdee..7f76321 100644 --- a/app/routes/api/draft.start.ts +++ b/app/routes/api/draft.start.ts @@ -4,10 +4,10 @@ import * as schema from "~/database/schema"; import { eq, and } from "drizzle-orm"; import { getSocketIO } from "../../../server/socket"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/queue.add.ts b/app/routes/api/queue.add.ts index 1a4738b..beb70b4 100644 --- a/app/routes/api/queue.add.ts +++ b/app/routes/api/queue.add.ts @@ -4,10 +4,10 @@ import * as schema from "~/database/schema"; import { eq } from "drizzle-orm"; import { addToQueue, getTeamQueue, isParticipantInQueue } from "~/models/draft-queue"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/queue.clear.ts b/app/routes/api/queue.clear.ts index cc3bf4b..b8bc6a5 100644 --- a/app/routes/api/queue.clear.ts +++ b/app/routes/api/queue.clear.ts @@ -3,11 +3,11 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq } from "drizzle-orm"; import { clearTeamQueue } from "~/models/draft-queue"; +import type { ActionFunctionArgs } from "react-router"; -export async function action(args: any) { +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/queue.remove.ts b/app/routes/api/queue.remove.ts index f5aceb3..bd23b2d 100644 --- a/app/routes/api/queue.remove.ts +++ b/app/routes/api/queue.remove.ts @@ -4,10 +4,10 @@ import * as schema from "~/database/schema"; import { eq } from "drizzle-orm"; import { removeFromQueue, getTeamQueue } from "~/models/draft-queue"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/api/queue.reorder.ts b/app/routes/api/queue.reorder.ts index a7accf3..bb09981 100644 --- a/app/routes/api/queue.reorder.ts +++ b/app/routes/api/queue.reorder.ts @@ -4,10 +4,10 @@ import * as schema from "~/database/schema"; import { eq } from "drizzle-orm"; import { reorderQueueWithSeason, getTeamQueue } from "~/models/draft-queue"; -export async function action(args: any) { +import type { ActionFunctionArgs } from "react-router"; +export async function action(args: ActionFunctionArgs) { const { request } = args; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { return Response.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx b/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx index 2fa61e2..d36ec48 100644 --- a/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx +++ b/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx @@ -7,8 +7,9 @@ import { DraftGrid } from "~/components/DraftGrid"; import { useDraftSocket } from "~/hooks/useDraftSocket"; import { useState, useEffect } from "react"; import { buildOwnerMap } from "~/lib/owner-map"; +import type { Route } from "./+types/$leagueId.draft-board.$seasonId"; -export async function loader(args: any) { +export async function loader(args: Route.LoaderArgs) { const { params } = args; const { leagueId, seasonId } = params; @@ -38,8 +39,7 @@ export async function loader(args: any) { // Check access: public boards are accessible to everyone without auth if (!season.league.isPublicDraftBoard) { // Not public - check if the user is a league member or commissioner - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!userId) { throw new Response("This draft board is not public", { status: 403 }); diff --git a/app/routes/leagues/$leagueId.draft.$seasonId.tsx b/app/routes/leagues/$leagueId.draft.$seasonId.tsx index db207e2..53e8a78 100644 --- a/app/routes/leagues/$leagueId.draft.$seasonId.tsx +++ b/app/routes/leagues/$leagueId.draft.$seasonId.tsx @@ -25,12 +25,12 @@ import { useDraftNotifications } from "~/hooks/useDraftNotifications"; import { NotificationSettings } from "~/components/NotificationSettings"; import { toast } from "sonner"; import { formatClockTime, getTimerColorClass } from "~/lib/draft-timer"; +import type { Route } from "./+types/$leagueId.draft.$seasonId"; -export async function loader(args: any) { +export async function loader(args: Route.LoaderArgs) { const { params } = args; const { seasonId, leagueId } = params; - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); if (!seasonId) { throw new Response("Season ID is required", { status: 400 }); diff --git a/app/routes/leagues/$leagueId.server.ts b/app/routes/leagues/$leagueId.server.ts index 8596ee5..ec662c4 100644 --- a/app/routes/leagues/$leagueId.server.ts +++ b/app/routes/leagues/$leagueId.server.ts @@ -10,8 +10,9 @@ import { findDraftSlotsBySeasonId, } from "~/models"; import { findCurrentSeasonWithSports } from "~/models/season"; +import type { Route } from "./+types/$leagueId"; -export async function loader(args: any) { +export async function loader(args: Route.LoaderArgs) { const { userId } = await getAuth(args); const { params } = args; const { leagueId } = params; diff --git a/app/routes/leagues/$leagueId.standings.$seasonId.tsx b/app/routes/leagues/$leagueId.standings.$seasonId.tsx index fa7893f..7f30705 100644 --- a/app/routes/leagues/$leagueId.standings.$seasonId.tsx +++ b/app/routes/leagues/$leagueId.standings.$seasonId.tsx @@ -9,11 +9,11 @@ import { StandingsTable } from "~/components/standings/StandingsTable"; import { getSevenDayStandingsChange, getSeasonPointProgression } from "~/models/standings"; import { isSeasonComplete, getSeasonCompletionPercentage } from "~/lib/season-helpers.server"; import { PointProgressionChart } from "~/components/standings/PointProgressionChart"; +import type { Route } from "./+types/$leagueId.standings.$seasonId"; -export async function loader(args: any) { +export async function loader(args: Route.LoaderArgs) { try { - const auth = await getAuth(args); - const userId = (auth as any).userId as string | null; + const { userId } = await getAuth(args); const { params } = args; const { leagueId, seasonId } = params; diff --git a/app/routes/leagues/__tests__/autodraft.test.ts b/app/routes/leagues/__tests__/autodraft.test.ts index fa4a886..8eb4896 100644 --- a/app/routes/leagues/__tests__/autodraft.test.ts +++ b/app/routes/leagues/__tests__/autodraft.test.ts @@ -1,6 +1,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { RouterContextProvider } from "react-router"; import { action } from "~/routes/api/autodraft.update"; +const ctx = {} as unknown as RouterContextProvider; + // Mock dependencies vi.mock("~/database/context"); vi.mock("~/server/socket", () => ({ @@ -94,7 +97,7 @@ describe("Autodraft Settings API", () => { const response = await action({ request, params: {}, - context: {}, + context: ctx, }); const data = await response.json(); @@ -163,7 +166,7 @@ describe("Autodraft Settings API", () => { const response = await action({ request, params: {}, - context: {}, + context: ctx, }); const data = await response.json(); @@ -206,7 +209,7 @@ describe("Autodraft Settings API", () => { const response = await action({ request, params: {}, - context: {}, + context: ctx, }); expect(response.status).toBe(403); @@ -227,7 +230,7 @@ describe("Autodraft Settings API", () => { const response = await action({ request, params: {}, - context: {}, + context: ctx, }); expect(response.status).toBe(400); @@ -274,7 +277,7 @@ describe("Autodraft Settings API", () => { const response1 = await action({ request: request1, params: {}, - context: {}, + context: ctx, }); const data1 = await response1.json(); @@ -306,7 +309,7 @@ describe("Autodraft Settings API", () => { const response2 = await action({ request: request2, params: {}, - context: {}, + context: ctx, }); const data2 = await response2.json(); @@ -355,7 +358,7 @@ describe("Autodraft Settings API", () => { await action({ request, params: {}, - context: {}, + context: ctx, }); expect(mockSocketIO.to).toHaveBeenCalledWith(`draft-${seasonId}`); diff --git a/server/socket.ts b/server/socket.ts index 837f179..7c8b66d 100644 --- a/server/socket.ts +++ b/server/socket.ts @@ -26,8 +26,8 @@ interface ServerToClientEvents { "team-connected": (data: { teamId: string }) => void; "team-disconnected": (data: { teamId: string }) => void; "connected-teams-list": (data: { teamIds: string[] }) => void; - "draft-paused": () => void; - "draft-resumed": () => void; + "draft-paused": (data: { seasonId: string; paused: boolean }) => void; + "draft-resumed": (data: { seasonId: string; paused: boolean }) => void; "participant-removed-from-queues": (data: { participantId: string }) => void; }