mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 12:16:12 -05:00
Seeds use new backend logic style
This commit is contained in:
@@ -1,13 +1,3 @@
|
||||
export function isTournamentAdmin({
|
||||
userId,
|
||||
organization,
|
||||
}: {
|
||||
userId?: string;
|
||||
organization: { ownerId: string };
|
||||
}) {
|
||||
return organization.ownerId === userId;
|
||||
}
|
||||
|
||||
export function canReportMatchScore({
|
||||
userId,
|
||||
members,
|
||||
|
||||
18
app/db/tournament/mutations/updateSeeds.ts
Normal file
18
app/db/tournament/mutations/updateSeeds.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
}
|
||||
17
app/db/tournament/queries/findTournamentById.ts
Normal file
17
app/db/tournament/queries/findTournamentById.ts
Normal 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 } },
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 }];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
19
app/validators/tournament.ts
Normal file
19
app/validators/tournament.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user