Refactor tournaments to use parseParams to avoid internal server error + remove barrel file

This commit is contained in:
Kalle 2025-04-16 14:19:19 +03:00
parent 087a188133
commit dd9fa3ec02
28 changed files with 132 additions and 74 deletions

View File

@ -8,8 +8,8 @@ import { Input } from "~/components/Input";
import { Label } from "~/components/Label";
import { SendouSwitch } from "~/components/elements/Switch";
import { PlusIcon } from "~/components/icons/Plus";
import { TOURNAMENT } from "~/features/tournament";
import * as Progression from "~/features/tournament-bracket/core/Progression";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import { defaultBracketSettings } from "../../tournament/tournament-utils";
const defaultBracket = (): Progression.InputBracket => ({

View File

@ -10,15 +10,19 @@ import {
import { currentSeason } from "~/features/mmr/season";
import { refreshUserSkills } from "~/features/mmr/tiered.server";
import { notify } from "~/features/notifications/core/notify.server";
import { tournamentIdFromParams } from "~/features/tournament";
import * as Progression from "~/features/tournament-bracket/core/Progression";
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import { createSwissBracketInTransaction } from "~/features/tournament/queries/createSwissBracketInTransaction.server";
import { updateRoundMaps } from "~/features/tournament/queries/updateRoundMaps.server";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import {
errorToastIfFalsy,
parseParams,
parseRequestPayload,
} from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { idObject } from "~/utils/zod";
import type { PreparedMaps } from "../../../db/tables";
import { updateTeamSeeds } from "../../tournament/queries/updateTeamSeeds.server";
import * as Swiss from "../core/Swiss";
@ -37,7 +41,10 @@ import { fillWithNullTillPowerOfTwo } from "../tournament-bracket-utils";
export const action: ActionFunction = async ({ params, request }) => {
const user = await requireUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
const data = await parseRequestPayload({ request, schema: bracketSchema });
const manager = getServerTournamentManager();

View File

@ -2,7 +2,6 @@ import type { ActionFunction } from "@remix-run/node";
import { nanoid } from "nanoid";
import { sql } from "~/db/sql";
import { requireUser } from "~/features/auth/core/user.server";
import { tournamentIdFromParams } from "~/features/tournament";
import * as TournamentMatchRepository from "~/features/tournament-bracket/TournamentMatchRepository.server";
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
@ -47,17 +46,16 @@ import {
export const action: ActionFunction = async ({ params, request }) => {
const user = await requireUser(request);
const matchId = parseParams({
const { mid: matchId, id: tournamentId } = parseParams({
params,
schema: matchPageParamsSchema,
}).mid;
});
const match = notFoundIfFalsy(findMatchById(matchId));
const data = await parseRequestPayload({
request,
schema: matchSchema,
});
const tournamentId = tournamentIdFromParams(params);
const tournament = await tournamentFromDB({ tournamentId, user });
const validateCanReportScore = () => {

View File

@ -1,7 +1,7 @@
import { sub } from "date-fns";
import * as R from "remeda";
import type { Tables, TournamentStageSettings } from "~/db/tables";
import { TOURNAMENT } from "~/features/tournament";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
import type { Round } from "~/modules/brackets-model";
import invariant from "~/utils/invariant";

View File

@ -4,10 +4,12 @@ import type {
TournamentStage,
TournamentStageSettings,
} from "~/db/tables";
import { TOURNAMENT } from "~/features/tournament";
import type * as Progression from "~/features/tournament-bracket/core/Progression";
import * as Standings from "~/features/tournament/core/Standings";
import { LEAGUES } from "~/features/tournament/tournament-constants";
import {
LEAGUES,
TOURNAMENT,
} from "~/features/tournament/tournament-constants";
import { tournamentIsRanked } from "~/features/tournament/tournament-utils";
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
import type { Match, Stage } from "~/modules/brackets-model";

View File

@ -1,6 +1,6 @@
import type { Tables, TournamentRoundMaps } from "~/db/tables";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import { modesIncluded } from "~/features/tournament";
import { modesIncluded } from "~/features/tournament/tournament-utils";
import type { Round } from "~/modules/brackets-model";
import type { ModeShort, StageId } from "~/modules/in-game-lists";
import type { TournamentMapListMap } from "~/modules/tournament-map-list-generator";

View File

@ -1,14 +1,17 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { getUser } from "~/features/auth/core/user.server";
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import { tournamentIdFromParams } from "~/features/tournament/tournament-utils";
import { notFoundIfFalsy } from "~/utils/remix.server";
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import type { Unwrapped } from "../../../utils/types";
import { tournamentFromDB } from "../core/Tournament.server";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await getUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const divisions = notFoundIfFalsy(await divisionsCached(tournamentId));

View File

@ -1,5 +1,4 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { tournamentIdFromParams } from "~/features/tournament";
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
import { resolveMapList } from "../core/mapList.server";
@ -10,11 +9,10 @@ import { matchPageParamsSchema } from "../tournament-bracket-schemas.server";
export type TournamentMatchLoaderData = typeof loader;
export const loader = async ({ params }: LoaderFunctionArgs) => {
const tournamentId = tournamentIdFromParams(params);
const matchId = parseParams({
const { mid: matchId, id: tournamentId } = parseParams({
params,
schema: matchPageParamsSchema,
}).mid;
});
const match = notFoundIfFalsy(findMatchById(matchId));

View File

@ -1,12 +1,15 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { eventStream } from "remix-utils/sse/server";
import { tournamentIdFromParams } from "~/features/tournament";
import { parseParams } from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import { emitter } from "../core/emitters.server";
import { bracketSubscriptionKey } from "../tournament-bracket-utils";
export const loader = ({ request, params }: LoaderFunctionArgs) => {
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
return eventStream(request.signal, (send) => {
const handler = (args: {

View File

@ -18,7 +18,7 @@ import { EyeIcon } from "~/components/icons/Eye";
import { EyeSlashIcon } from "~/components/icons/EyeSlash";
import { MapIcon } from "~/components/icons/Map";
import { useUser } from "~/features/auth/core/user";
import { TOURNAMENT } from "~/features/tournament";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import { useIsMounted } from "~/hooks/useIsMounted";
import { useSearchParamState } from "~/hooks/useSearchParamState";
import { useVisibilityChange } from "~/hooks/useVisibilityChange";

View File

@ -6,8 +6,8 @@ import { LinkButton } from "~/components/Button";
import { containerClassName } from "~/components/Main";
import { ArrowLongLeftIcon } from "~/components/icons/ArrowLongLeft";
import { useUser } from "~/features/auth/core/user";
import { TOURNAMENT } from "~/features/tournament";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import { useSearchParamState } from "~/hooks/useSearchParamState";
import { useVisibilityChange } from "~/hooks/useVisibilityChange";
import invariant from "~/utils/invariant";

View File

@ -159,8 +159,9 @@ export const bracketSchema = z.union([
}),
]);
export const matchPageParamsSchema = z.object({ mid: id });
export const matchPageParamsSchema = z.object({ id, mid: id });
export const tournamentTeamPageParamsSchema = z.object({
id,
tid: id,
});

View File

@ -1,12 +1,16 @@
import { type ActionFunction, redirect } from "@remix-run/node";
import { requireUser } from "~/features/auth/core/user.server";
import { tournamentIdFromParams } from "~/features/tournament";
import {
clearTournamentDataCache,
tournamentFromDB,
} from "~/features/tournament-bracket/core/Tournament.server";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import {
errorToastIfFalsy,
parseParams,
parseRequestPayload,
} from "~/utils/remix.server";
import { tournamentSubsPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
import { upsertSub } from "../queries/upsertSub.server";
import { subSchema } from "../tournament-subs-schemas.server";
@ -16,7 +20,10 @@ export const action: ActionFunction = async ({ params, request }) => {
request,
schema: subSchema,
});
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
errorToastIfFalsy(!tournament.everyBracketOver, "Tournament is over");

View File

@ -1,17 +1,24 @@
import type { ActionFunction } from "@remix-run/node";
import { requireUser } from "~/features/auth/core/user.server";
import { tournamentIdFromParams } from "~/features/tournament";
import {
clearTournamentDataCache,
tournamentFromDB,
} from "~/features/tournament-bracket/core/Tournament.server";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import {
errorToastIfFalsy,
parseParams,
parseRequestPayload,
} from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import { deleteSub } from "../queries/deleteSub.server";
import { deleteSubSchema } from "../tournament-subs-schemas.server";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
const data = await parseRequestPayload({
request,

View File

@ -1,13 +1,17 @@
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { requireUser } from "~/features/auth/core/user.server";
import { tournamentIdFromParams } from "~/features/tournament";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
import { parseParams } from "~/utils/remix.server";
import { tournamentSubsPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
import { findSubsByTournamentId } from "../queries/findSubsByTournamentId.server";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
if (!tournament.canAddNewSubPost) {

View File

@ -1,14 +1,18 @@
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { getUser } from "~/features/auth/core/user.server";
import { tournamentIdFromParams } from "~/features/tournament";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
import { parseParams } from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { tournamentRegisterPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
import { findSubsByTournamentId } from "../queries/findSubsByTournamentId.server";
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const user = await getUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
if (!tournament.subsFeatureEnabled) {

View File

@ -15,12 +15,13 @@ import { logger } from "~/utils/logger";
import {
badRequestIfFalsy,
errorToastIfFalsy,
parseParams,
parseRequestPayload,
successToast,
} from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { USER } from "../../../constants";
import { _action, id } from "../../../utils/zod";
import { _action, id, idObject } from "../../../utils/zod";
import { bracketProgressionSchema } from "../../calendar/actions/calendar.new.server";
import { bracketIdx } from "../../tournament-bracket/tournament-bracket-schemas.server";
import * as TournamentRepository from "../TournamentRepository.server";
@ -28,7 +29,6 @@ import { changeTeamOwner } from "../queries/changeTeamOwner.server";
import { deleteTeam } from "../queries/deleteTeam.server";
import { joinTeam, leaveTeam } from "../queries/joinLeaveTeam.server";
import { teamName } from "../tournament-schemas.server";
import { tournamentIdFromParams } from "../tournament-utils";
import { inGameNameIfNeeded } from "../tournament-utils.server";
export const action: ActionFunction = async ({ request, params }) => {
@ -38,7 +38,10 @@ export const action: ActionFunction = async ({ request, params }) => {
schema: adminActionSchema,
});
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
const validateIsTournamentAdmin = () =>

View File

@ -11,21 +11,23 @@ import invariant from "~/utils/invariant";
import {
errorToastIfFalsy,
notFoundIfFalsy,
parseParams,
parseRequestPayload,
} from "~/utils/remix.server";
import { tournamentPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
import { findByInviteCode } from "../queries/findTeamByInviteCode.server";
import { giveTrust } from "../queries/giveTrust.server";
import { joinTeam } from "../queries/joinLeaveTeam.server";
import { joinSchema } from "../tournament-schemas.server";
import {
tournamentIdFromParams,
validateCanJoinTeam,
} from "../tournament-utils";
import { validateCanJoinTeam } from "../tournament-utils";
import { inGameNameIfNeeded } from "../tournament-utils.server";
export const action: ActionFunction = async ({ request, params }) => {
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const user = await requireUserId(request);
const url = new URL(request.url);
const inviteCode = url.searchParams.get("code");

View File

@ -16,10 +16,12 @@ import {
errorToastIfFalsy,
notFoundIfFalsy,
parseFormData,
parseParams,
uploadImageIfSubmitted,
} from "~/utils/remix.server";
import { booleanToInt } from "~/utils/sql";
import { assertUnreachable } from "~/utils/types";
import { idObject } from "~/utils/zod";
import { checkIn } from "../queries/checkIn.server";
import { deleteTeam } from "../queries/deleteTeam.server";
import deleteTeamMember from "../queries/deleteTeamMember.server";
@ -30,7 +32,6 @@ import { upsertCounterpickMaps } from "../queries/upsertCounterpickMaps.server";
import { registerSchema } from "../tournament-schemas.server";
import {
isOneModeTournamentOf,
tournamentIdFromParams,
validateCounterPickMapPool,
} from "../tournament-utils";
import { inGameNameIfNeeded } from "../tournament-utils.server";
@ -46,7 +47,10 @@ export const action: ActionFunction = async ({ request, params }) => {
schema: registerSchema,
});
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
const event = notFoundIfFalsy(findByIdentifier(tournamentId));

View File

@ -4,11 +4,15 @@ import {
clearTournamentDataCache,
tournamentFromDB,
} from "~/features/tournament-bracket/core/Tournament.server";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import {
errorToastIfFalsy,
parseParams,
parseRequestPayload,
} from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import * as TournamentTeamRepository from "../TournamentTeamRepository.server";
import { updateTeamSeeds } from "../queries/updateTeamSeeds.server";
import { seedsActionSchema } from "../tournament-schemas.server";
import { tournamentIdFromParams } from "../tournament-utils";
export const action: ActionFunction = async ({ request, params }) => {
const data = await parseRequestPayload({
@ -16,7 +20,10 @@ export const action: ActionFunction = async ({ request, params }) => {
schema: seedsActionSchema,
});
const user = await requireUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
errorToastIfFalsy(tournament.isOrganizer(user), "Not an organizer");

View File

@ -1,2 +0,0 @@
export { TOURNAMENT } from "./tournament-constants";
export { tournamentIdFromParams, modesIncluded } from "./tournament-utils";

View File

@ -3,15 +3,21 @@ import { getUser } from "~/features/auth/core/user.server";
import * as QRepository from "~/features/sendouq/QRepository.server";
import * as TeamRepository from "~/features/team/TeamRepository.server";
import { findMapPoolByTeamId } from "~/features/tournament-bracket/queries/findMapPoolByTeamId.server";
import { parseParams } from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import { findOwnTournamentTeam } from "../queries/findOwnTournamentTeam.server";
import { tournamentIdFromParams } from "../tournament-utils";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await getUser(request);
if (!user) return null;
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const ownTournamentTeam = findOwnTournamentTeam({
tournamentId: tournamentIdFromParams(params),
tournamentId,
userId: user.id,
});
if (!ownTournamentTeam)

View File

@ -2,12 +2,16 @@ import type { LoaderFunctionArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { requireUser } from "~/features/auth/core/user.server";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
import { parseParams } from "~/utils/remix.server";
import { tournamentBracketsPage } from "~/utils/urls";
import { tournamentIdFromParams } from "../tournament-utils";
import { idObject } from "~/utils/zod";
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentFromDB({ tournamentId, user });
if (!tournament.isOrganizer(user) || tournament.hasStarted) {

View File

@ -4,14 +4,18 @@ import { tournamentDataCached } from "~/features/tournament-bracket/core/Tournam
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import { isAdmin } from "~/permissions";
import { databaseTimestampToDate } from "~/utils/dates";
import { parseParams } from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import { streamsByTournamentId } from "../core/streams.server";
import { tournamentIdFromParams } from "../tournament-utils";
export type TournamentLoaderData = SerializeFrom<typeof loader>;
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const user = await getUser(request);
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = await tournamentDataCached({ tournamentId, user });

View File

@ -1,13 +1,16 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { tournamentData } from "~/features/tournament-bracket/core/Tournament.server";
import { notFoundIfFalsy } from "~/utils/remix.server";
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
import { idObject } from "~/utils/zod";
import { streamsByTournamentId } from "../core/streams.server";
import { tournamentIdFromParams } from "../tournament-utils";
export type TournamentStreamsLoader = typeof loader;
export const loader = async ({ params }: LoaderFunctionArgs) => {
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
const tournament = notFoundIfFalsy(await tournamentData({ tournamentId }));
return {

View File

@ -3,14 +3,12 @@ import { tournamentDataCached } from "~/features/tournament-bracket/core/Tournam
import { tournamentTeamPageParamsSchema } from "~/features/tournament-bracket/tournament-bracket-schemas.server";
import { parseParams } from "~/utils/remix.server";
import { tournamentTeamSets, winCounts } from "../core/sets.server";
import { tournamentIdFromParams } from "../tournament-utils";
export const loader = async ({ params }: LoaderFunctionArgs) => {
const tournamentId = tournamentIdFromParams(params);
const tournamentTeamId = parseParams({
const { id: tournamentId, tid: tournamentTeamId } = parseParams({
params,
schema: tournamentTeamPageParamsSchema,
}).tid;
});
const tournament = await tournamentDataCached({ tournamentId });
if (

View File

@ -1,15 +1,19 @@
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { parseParams } from "~/utils/remix.server";
import {
tournamentBracketsPage,
tournamentRegisterPage,
tournamentResultsPage,
} from "~/utils/urls";
import { idObject } from "~/utils/zod";
import hasTournamentFinalized from "../queries/hasTournamentFinalized.server";
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
import { tournamentIdFromParams } from "../tournament-utils";
export const loader = ({ params }: LoaderFunctionArgs) => {
const tournamentId = tournamentIdFromParams(params);
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
if (!hasTournamentStarted(tournamentId)) {
return redirect(tournamentRegisterPage(tournamentId));

View File

@ -1,9 +1,7 @@
import type { Params } from "@remix-run/react";
import { INVITE_CODE_LENGTH } from "~/constants";
import type { ModeShort, StageId } from "~/modules/in-game-lists";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
import { weekNumberToDate } from "~/utils/dates";
import invariant from "~/utils/invariant";
import { tournamentLogoUrl } from "~/utils/urls";
import type { Tables, TournamentStageSettings } from "../../db/tables";
import { assertUnreachable } from "../../utils/types";
@ -15,13 +13,6 @@ import type { TournamentData } from "../tournament-bracket/core/Tournament.serve
import type { PlayedSet } from "./core/sets.server";
import { LEAGUES, TOURNAMENT } from "./tournament-constants";
export function tournamentIdFromParams(params: Params<string>) {
const result = Number(params.id);
invariant(!Number.isNaN(result), "id is not a number");
return result;
}
export function modesIncluded(
tournament: Pick<Tables["Tournament"], "mapPickingStyle">,
): ModeShort[] {