Seeds use new backend logic style

This commit is contained in:
Kalle (Sendou)
2022-01-16 22:59:41 +02:00
parent d530e9383c
commit 93c4ed9edd
9 changed files with 83 additions and 60 deletions

View File

@@ -1,13 +1,3 @@
export function isTournamentAdmin({
userId,
organization,
}: {
userId?: string;
organization: { ownerId: string };
}) {
return organization.ownerId === userId;
}
export function canReportMatchScore({
userId,
members,

View File

@@ -0,0 +1,18 @@
import { db } from "~/utils/db.server";
export async function updateSeeds({
tournamentId,
seeds,
}: {
tournamentId: string;
seeds: string[];
}) {
return db.tournament.update({
where: {
id: tournamentId,
},
data: {
seeds,
},
});
}

View File

@@ -0,0 +1,17 @@
import { Prisma } from "@prisma/client";
import { db } from "~/utils/db.server";
export type FindTournamentById = Prisma.PromiseReturnType<
typeof findTournamentById
>;
export function findTournamentById(id: string) {
return db.tournament.findUnique({
where: { id },
include: {
organizer: true,
brackets: { include: { rounds: true } },
teams: { include: { members: true } },
},
});
}

View File

@@ -137,21 +137,3 @@ export function findByNameForUrlWithInviteCodes(tournamentNameForUrl: string) {
},
});
}
export type UpdateSeeds = Prisma.PromiseReturnType<typeof updateSeeds>;
export function updateSeeds({
tournamentId,
seeds,
}: {
tournamentId: string;
seeds: string[];
}) {
return db.tournament.update({
where: {
id: tournamentId,
},
data: {
seeds,
},
});
}

View File

@@ -14,7 +14,6 @@ import invariant from "tiny-invariant";
import { AdminIcon } from "~/components/icons/Admin";
import { CheckinActions } from "~/components/tournament/CheckinActions";
import { InfoBanner } from "~/components/tournament/InfoBanner";
import { isTournamentAdmin } from "~/core/tournament/permissions";
import { tournamentHasStarted } from "~/core/tournament/utils";
import {
checkIn,
@@ -26,6 +25,7 @@ import type { MyCSSProperties } from "~/utils";
import { useUser } from "~/hooks/common";
import tournamentStylesUrl from "../../styles/tournament.css";
import * as React from "react";
import { isTournamentAdmin } from "~/validators/tournament";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: tournamentStylesUrl }];

View File

@@ -28,18 +28,22 @@ import { Alert } from "~/components/Alert";
import { Button } from "~/components/Button";
import { Catcher } from "~/components/Catcher";
import { Draggable } from "~/components/Draggable";
import {
FindTournamentByNameForUrlI,
updateSeeds,
} from "~/services/tournament";
import { FindTournamentByNameForUrlI } from "~/services/tournament";
import seedsStylesUrl from "~/styles/tournament-seeds.css";
import {
parseRequestFormData,
requireUser,
safeJSONParse,
Unpacked,
validate,
} from "~/utils";
import { useTimeoutState } from "~/hooks/common";
import { updateSeeds } from "~/db/tournament/mutations/updateSeeds";
import { findTournamentById } from "~/db/tournament/queries/findTournamentById";
import {
isTournamentAdmin,
tournamentHasNotStarted,
} from "~/validators/tournament";
const seedsActionSchema = z.object({
tournamentId: z.string().uuid(),
@@ -53,10 +57,20 @@ export const action: ActionFunction = async ({ context, request }) => {
});
const user = requireUser(context);
const tournament = await findTournamentById(data.tournamentId);
validate(tournament, "Invalid tournament id");
validate(
isTournamentAdmin({ userId: user.id, organization: tournament.organizer }),
"Not tournament admin"
);
validate(
tournamentHasNotStarted(tournament),
"Can't change seeds after tournament has started"
);
await updateSeeds({
tournamentId: data.tournamentId,
userId: user.id,
newSeeds: data.seeds,
seeds: data.seeds,
});
return null;

View File

@@ -4,7 +4,6 @@ import {
TOURNAMENT_TEAM_ROSTER_MAX_SIZE,
} from "~/constants";
import { MapListIds, tournamentRoundsForDB } from "~/core/tournament/bracket";
import { isTournamentAdmin } from "~/core/tournament/permissions";
import { captainOfTeam, sortTeamsBySeed } from "~/core/tournament/utils";
import * as Tournament from "~/models/Tournament";
import * as TournamentTeam from "~/models/TournamentTeam";
@@ -12,6 +11,7 @@ import * as TournamentTeamMember from "~/models/TournamentTeamMember";
import * as TrustRelationship from "~/models/TrustRelationship";
import { Serialized, Unpacked } from "~/utils";
import { db } from "~/utils/db.server";
import { isTournamentAdmin } from "~/validators/tournament";
export type FindTournamentByNameForUrlI = Serialized<
Prisma.PromiseReturnType<typeof findTournamentByNameForUrl>
@@ -409,27 +409,3 @@ export async function checkOut({
return TournamentTeam.checkOut(teamId);
}
export async function updateSeeds({
tournamentId,
userId,
newSeeds,
}: {
tournamentId: string;
userId: string;
newSeeds: string[];
}) {
const tournament = await Tournament.findById(tournamentId);
if (!tournament) throw new Response("Invalid tournament id", { status: 400 });
if (
!isTournamentAdmin({
organization: tournament.organizer,
userId,
})
) {
throw new Response("Not tournament admin", { status: 401 });
}
// TODO: fail if tournament has started
return Tournament.updateSeeds({ tournamentId, seeds: newSeeds });
}

View File

@@ -40,6 +40,13 @@ export function requireEvents(ctx: unknown) {
}
}
/** Asserts condition is truthy. Throws a new `Response` with status code 400 and given message if falsy. */
export function validate(condition: any, message: string): asserts condition {
if (condition) return;
throw new Response(message, { status: 400 });
}
/** Get link to log in with query param set as current page */
export function getLogInUrl(location: ReturnType<typeof useLocation>) {
return `/auth/discord?origin=${encodeURIComponent(

View File

@@ -0,0 +1,19 @@
import { FindTournamentById } from "~/db/tournament/queries/findTournamentById";
/** Checks that a user is considered an admin of the tournament. An admin can perform all sorts of actions that normal users can't. */
export function isTournamentAdmin({
userId,
organization,
}: {
userId?: string;
organization: { ownerId: string };
}) {
return organization.ownerId === userId;
}
/** Checks if tournament has not started meaning there is no bracket with rounds generated. */
export function tournamentHasNotStarted(
tournament: NonNullable<FindTournamentById>
) {
return (tournament.brackets[0]?.rounds.length ?? 0) === 0;
}