brackt/app/routes/leagues/$leagueId.server.ts
Claude c036cf6e28
Code review fixes: type safety, security hardening, and dead code removal
- Fix Socket.IO event types: draft-paused and draft-resumed were typed as
  () => void but are emitted with { seasonId, paused } data payloads

- Fix draft.force-manual-pick: add missing season.status === "draft" guard
  so commissioners cannot force picks outside an active draft; add duplicate
  pick-number check so a slot cannot be assigned two picks (the previous
  code only checked participant uniqueness, not slot uniqueness)

- Replace args: any with ActionFunctionArgs / Route.LoaderArgs across all
  API routes and league loaders; replace (auth as any).userId casts with
  proper const { userId } = await getAuth(args) destructuring

- Remove unused isSnakeDraft = true dead variable from draft.make-pick

- Replace autodraftSettings: any and draftSlots: any[] in draft-utils with
  properly typed InferSelectModel / DraftSlot types

- Update force-manual-pick tests: sequence draftPicks.findFirst mock for
  the two-call flow; add new tests for status-check and slot-uniqueness

https://claude.ai/code/session_01FKq2gPFYpgdfxr8cw4Z2AZ
2026-02-23 02:41:43 +00:00

137 lines
3.9 KiB
TypeScript

import { getAuth } from "@clerk/react-router/server";
import {
findLeagueById,
findCurrentSeason,
findTeamsBySeasonId,
findCommissionersByLeagueId,
isUserLeagueMember,
isCommissioner,
findUserByClerkId,
findDraftSlotsBySeasonId,
} from "~/models";
import { findCurrentSeasonWithSports } from "~/models/season";
import type { Route } from "./+types/$leagueId";
export async function loader(args: Route.LoaderArgs) {
const { userId } = await getAuth(args);
const { params } = args;
const { leagueId } = params;
// Fetch league
const league = await findLeagueById(leagueId);
if (!league) {
throw new Response("League not found", { status: 404 });
}
// Fetch current season with sports
const seasonWithSports = await findCurrentSeasonWithSports(leagueId);
const season = seasonWithSports || null;
// Fetch commissioners
const commissioners = await findCommissionersByLeagueId(leagueId);
// Check if current user is a commissioner
const isUserCommissioner = userId
? await isCommissioner(leagueId, userId)
: false;
// Check if user is a member (has a team in current season)
const isUserMember = userId
? await isUserLeagueMember(leagueId, userId)
: false;
// Check access: user must be a commissioner, a member, or an admin
// If not logged in or not authorized, throw 403
if (!userId) {
throw new Response("You must be logged in to view this league", {
status: 401,
});
}
if (!isUserCommissioner && !isUserMember) {
throw new Response("You do not have access to this league", {
status: 403,
});
}
// Fetch teams for current season
const teams = season ? await findTeamsBySeasonId(season.id) : [];
// Fetch draft slots if season is in pre_draft or draft status
const draftSlots =
season && (season.status === "pre_draft" || season.status === "draft")
? await findDraftSlotsBySeasonId(season.id)
: [];
// Fetch user data for team owners
const ownerIds = teams
.map((t) => t.ownerId)
.filter((id): id is string => id !== null);
const uniqueOwnerIds = [...new Set(ownerIds)];
const owners = await Promise.all(
uniqueOwnerIds.map(async (ownerId) => {
const user = await findUserByClerkId(ownerId);
return user
? { clerkId: ownerId, name: user.username || user.displayName }
: null;
})
);
const ownerMap = new Map(
owners
.filter((o): o is NonNullable<typeof o> => o !== null)
.map((o) => [o.clerkId, o.name])
);
// Fetch user data for commissioners
const commissionerIds = commissioners.map((c) => c.userId);
const commissionerUsers = await Promise.all(
commissionerIds.map(async (commissionerId) => {
const user = await findUserByClerkId(commissionerId);
return user
? { clerkId: commissionerId, name: user.username || user.displayName }
: null;
})
);
const commissionerMap = new Map(
commissionerUsers
.filter((c): c is NonNullable<typeof c> => c !== null)
.map((c) => [c.clerkId, c.name])
);
// Count available teams
const availableTeamCount = teams.filter((t) => !t.ownerId).length;
// Count teams with owners
const teamsWithOwners = teams.filter((t) => t.ownerId !== null).length;
// Get sports seasons data
const sportsSeasons = seasonWithSports?.seasonSports?.map((ss) => ({
id: ss.sportsSeason.id,
name: ss.sportsSeason.name,
status: ss.sportsSeason.status,
scoringPattern: ss.sportsSeason.scoringPattern,
sport: ss.sportsSeason.sport,
})) || [];
const sportsCount = sportsSeasons.length;
// Check if draft order is set
const isDraftOrderSet = draftSlots.length > 0;
return {
league,
season,
teams,
commissioners,
currentUserId: userId,
isUserCommissioner,
ownerMap: Object.fromEntries(ownerMap),
commissionerMap: Object.fromEntries(commissionerMap),
availableTeamCount,
draftSlots,
sportsCount,
teamsWithOwners,
isDraftOrderSet,
sportsSeasons,
};
}