From fcce761c12faa4a930da6c0bdb174833ebf98340 Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Fri, 21 Jan 2022 09:13:31 +0200 Subject: [PATCH] Put backend logic in models --- .../tournament/mutations/putPlayerToTeam.ts | 19 -- app/db/tournament/mutations/updateSeeds.ts | 18 -- .../queries/findMatchModalInfoByNumber.ts | 172 ------------------ .../tournament/queries/findTournamentById.ts | 17 -- .../tournament/queries/tournamentTeamById.ts | 8 - app/models/Tournament.ts | 24 ++- app/models/TournamentMatch.ts | 170 +++++++++++++++++ app/models/TournamentTeamMember.ts | 10 +- .../$organization.$tournament/manage-team.tsx | 10 +- .../to/$organization.$tournament/seeds.tsx | 7 +- app/services/tournament.ts | 38 +--- app/validators/tournament.ts | 7 +- 12 files changed, 211 insertions(+), 289 deletions(-) delete mode 100644 app/db/tournament/mutations/putPlayerToTeam.ts delete mode 100644 app/db/tournament/mutations/updateSeeds.ts delete mode 100644 app/db/tournament/queries/findMatchModalInfoByNumber.ts delete mode 100644 app/db/tournament/queries/findTournamentById.ts delete mode 100644 app/db/tournament/queries/tournamentTeamById.ts diff --git a/app/db/tournament/mutations/putPlayerToTeam.ts b/app/db/tournament/mutations/putPlayerToTeam.ts deleted file mode 100644 index 5357c7cf1..000000000 --- a/app/db/tournament/mutations/putPlayerToTeam.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { db } from "~/utils/db.server"; - -export async function putPlayerToTeam({ - tournamentId, - teamId, - newPlayerId, -}: { - tournamentId: string; - teamId: string; - newPlayerId: string; -}) { - return db.tournamentTeamMember.create({ - data: { - tournamentId, - teamId, - memberId: newPlayerId, - }, - }); -} diff --git a/app/db/tournament/mutations/updateSeeds.ts b/app/db/tournament/mutations/updateSeeds.ts deleted file mode 100644 index 52c606090..000000000 --- a/app/db/tournament/mutations/updateSeeds.ts +++ /dev/null @@ -1,18 +0,0 @@ -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, - }, - }); -} diff --git a/app/db/tournament/queries/findMatchModalInfoByNumber.ts b/app/db/tournament/queries/findMatchModalInfoByNumber.ts deleted file mode 100644 index eb5cfd5b6..000000000 --- a/app/db/tournament/queries/findMatchModalInfoByNumber.ts +++ /dev/null @@ -1,172 +0,0 @@ -import type { - Mode, - TournamentMatchGameResult, - TournamentTeamMember, - User, -} from "@prisma/client"; -import invariant from "tiny-invariant"; -import { TeamRosterInputTeam } from "~/components/tournament/TeamRosterInputs"; -import { getRoundNameByPositions } from "~/core/tournament/bracket"; -import { db } from "~/utils/db.server"; -import { v4 as uuidv4 } from "uuid"; - -export type FindMatchModalInfoByNumber = - | { - title: string; - scoreTitle: string; - roundName: string; - matchInfos: { - idForFrontend: string; - teamUpper: TeamRosterInputTeam; - teamLower: TeamRosterInputTeam; - winnerId?: string; - stage: { name: string; mode: Mode }; - }[]; - } - | undefined; - -export async function findMatchModalInfoByNumber({ - bracketId, - matchNumber, -}: { - bracketId: string; - matchNumber: number; -}): Promise { - const tournamentRounds = await db.tournamentRound.findMany({ - where: { bracketId }, - include: { - matches: { - include: { - results: { include: { players: true } }, - participants: { - include: { - team: { include: { members: { include: { member: true } } } }, - }, - }, - }, - }, - stages: { include: { stage: true } }, - }, - }); - - const tournamentRound = tournamentRounds.find((round) => - round.matches.find((match) => match.position === matchNumber) - ); - const match = tournamentRound?.matches.find( - (match) => match.position === matchNumber - ); - - if (!tournamentRound || !match) return; - - const teamsOrdered = match.participants.sort((a, b) => - b.order.localeCompare(a.order) - ); - - const upperTeam = match.participants.find((p) => p.order === "UPPER"); - const lowerTeam = match.participants.find((p) => p.order === "LOWER"); - invariant(upperTeam && lowerTeam, "upper or lower team is undefined"); - - const matchInfos = tournamentRound.stages - .sort((a, b) => a.position - b.position) - .map((tournamentRoundStage) => { - /** Result of this one stage, if undefined means the stage was not played yet */ - const stageResult = match.results.find( - (r) => r.roundStageId === tournamentRoundStage.id - ); - - const membersWithPlayedInfo = playersOfMatch({ - stageResult, - upperTeamMembers: upperTeam.team.members, - lowerTeamMembers: lowerTeam.team.members, - }); - - return { - idForFrontend: uuidv4(), - teamUpper: { - name: upperTeam.team.name, - id: upperTeam.teamId, - members: membersWithPlayedInfo.upperTeamMembers, - }, - teamLower: { - name: lowerTeam.team.name, - id: lowerTeam.teamId, - members: membersWithPlayedInfo.lowerTeamMembers, - }, - winnerId: stageResult - ? stageResult.winner === "UPPER" - ? upperTeam.teamId - : lowerTeam.teamId - : undefined, - stage: { - name: tournamentRoundStage.stage.name, - mode: tournamentRoundStage.stage.mode, - }, - }; - }); - - const scoreTitle = match.results - .reduce( - (scores, result) => { - if (result.winner === "UPPER") scores[0]++; - else scores[1]++; - return scores; - }, - [0, 0] - ) - .join("-"); - - return { - title: `${teamsOrdered[0].team.name} vs. ${teamsOrdered[1].team.name}`, - scoreTitle, - roundName: getRoundNameByPositions( - tournamentRound.position, - tournamentRounds.map((round) => round.position) - ), - matchInfos, - }; -} - -/** Returns players grouped by team with info whether they played this stage or not */ -function playersOfMatch({ - stageResult, - upperTeamMembers, - lowerTeamMembers, -}: { - stageResult?: TournamentMatchGameResult & { - players: User[]; - }; - upperTeamMembers: (TournamentTeamMember & { - member: User; - })[]; - lowerTeamMembers: (TournamentTeamMember & { - member: User; - })[]; -}) { - if (!stageResult) return { upperTeamMembers: [], lowerTeamMembers: [] }; - - const stageResultPlayerIds = stageResult.players.reduce( - (acc, cur) => acc.add(cur.id), - new Set() - ); - - return { - upperTeamMembers: upperTeamMembers.map(({ member }) => { - return { - member: { - id: member.id, - discordName: member.discordName, - played: stageResultPlayerIds.has(member.id), - }, - }; - }), - lowerTeamMembers: lowerTeamMembers.map(({ member }) => { - return { - member: { - id: member.id, - discordName: member.discordName, - played: stageResultPlayerIds.has(member.id), - }, - }; - }), - }; -} diff --git a/app/db/tournament/queries/findTournamentById.ts b/app/db/tournament/queries/findTournamentById.ts deleted file mode 100644 index 3b9621b40..000000000 --- a/app/db/tournament/queries/findTournamentById.ts +++ /dev/null @@ -1,17 +0,0 @@ -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 } }, - }, - }); -} diff --git a/app/db/tournament/queries/tournamentTeamById.ts b/app/db/tournament/queries/tournamentTeamById.ts deleted file mode 100644 index 0662c9c46..000000000 --- a/app/db/tournament/queries/tournamentTeamById.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { db } from "~/utils/db.server"; - -export function tournamentTeamById(id: string) { - return db.tournamentTeam.findUnique({ - where: { id }, - include: { tournament: true, members: true }, - }); -} diff --git a/app/models/Tournament.ts b/app/models/Tournament.ts index 531340d6f..ef87eaafd 100644 --- a/app/models/Tournament.ts +++ b/app/models/Tournament.ts @@ -5,7 +5,11 @@ export type FindById = Prisma.PromiseReturnType; export function findById(id: string) { return db.tournament.findUnique({ where: { id }, - include: { organizer: true, teams: { include: { members: true } } }, + include: { + organizer: true, + brackets: { include: { rounds: true } }, + teams: { include: { members: true } }, + }, }); } @@ -137,3 +141,21 @@ export function findByNameForUrlWithInviteCodes(tournamentNameForUrl: string) { }, }); } + +export type UpdateSeeds = Prisma.PromiseReturnType; +export function updateSeeds({ + tournamentId, + seeds, +}: { + tournamentId: string; + seeds: string[]; +}) { + return db.tournament.update({ + where: { + id: tournamentId, + }, + data: { + seeds, + }, + }); +} diff --git a/app/models/TournamentMatch.ts b/app/models/TournamentMatch.ts index 7907c9aa9..68fbc8de7 100644 --- a/app/models/TournamentMatch.ts +++ b/app/models/TournamentMatch.ts @@ -1,5 +1,15 @@ import { Prisma, TeamOrder } from "@prisma/client"; import { db } from "~/utils/db.server"; +import type { + Mode, + TournamentMatchGameResult, + TournamentTeamMember, + User, +} from "@prisma/client"; +import invariant from "tiny-invariant"; +import { TeamRosterInputTeam } from "~/components/tournament/TeamRosterInputs"; +import { getRoundNameByPositions } from "~/core/tournament/bracket"; +import { v4 as uuidv4 } from "uuid"; export type FindById = Prisma.PromiseReturnType; export function findById(id: string) { @@ -73,3 +83,163 @@ export function createParticipants(data: CreateParticipantsData) { data, }); } + +export type FindInfoForModal = + | { + title: string; + scoreTitle: string; + roundName: string; + matchInfos: { + idForFrontend: string; + teamUpper: TeamRosterInputTeam; + teamLower: TeamRosterInputTeam; + winnerId?: string; + stage: { name: string; mode: Mode }; + }[]; + } + | undefined; +export async function findInfoForModal({ + bracketId, + matchNumber, +}: { + bracketId: string; + matchNumber: number; +}): Promise { + const tournamentRounds = await db.tournamentRound.findMany({ + where: { bracketId }, + include: { + matches: { + include: { + results: { include: { players: true } }, + participants: { + include: { + team: { include: { members: { include: { member: true } } } }, + }, + }, + }, + }, + stages: { include: { stage: true } }, + }, + }); + + const tournamentRound = tournamentRounds.find((round) => + round.matches.find((match) => match.position === matchNumber) + ); + const match = tournamentRound?.matches.find( + (match) => match.position === matchNumber + ); + + if (!tournamentRound || !match) return; + + const teamsOrdered = match.participants.sort((a, b) => + b.order.localeCompare(a.order) + ); + + const upperTeam = match.participants.find((p) => p.order === "UPPER"); + const lowerTeam = match.participants.find((p) => p.order === "LOWER"); + invariant(upperTeam && lowerTeam, "upper or lower team is undefined"); + + const matchInfos = tournamentRound.stages + .sort((a, b) => a.position - b.position) + .map((tournamentRoundStage) => { + /** Result of this one stage, if undefined means the stage was not played yet */ + const stageResult = match.results.find( + (r) => r.roundStageId === tournamentRoundStage.id + ); + + const membersWithPlayedInfo = playersOfMatch({ + stageResult, + upperTeamMembers: upperTeam.team.members, + lowerTeamMembers: lowerTeam.team.members, + }); + + return { + idForFrontend: uuidv4(), + teamUpper: { + name: upperTeam.team.name, + id: upperTeam.teamId, + members: membersWithPlayedInfo.upperTeamMembers, + }, + teamLower: { + name: lowerTeam.team.name, + id: lowerTeam.teamId, + members: membersWithPlayedInfo.lowerTeamMembers, + }, + winnerId: stageResult + ? stageResult.winner === "UPPER" + ? upperTeam.teamId + : lowerTeam.teamId + : undefined, + stage: { + name: tournamentRoundStage.stage.name, + mode: tournamentRoundStage.stage.mode, + }, + }; + }); + + const scoreTitle = match.results + .reduce( + (scores, result) => { + if (result.winner === "UPPER") scores[0]++; + else scores[1]++; + return scores; + }, + [0, 0] + ) + .join("-"); + + return { + title: `${teamsOrdered[0].team.name} vs. ${teamsOrdered[1].team.name}`, + scoreTitle, + roundName: getRoundNameByPositions( + tournamentRound.position, + tournamentRounds.map((round) => round.position) + ), + matchInfos, + }; +} + +/** Returns players grouped by team with info whether they played this stage or not */ +function playersOfMatch({ + stageResult, + upperTeamMembers, + lowerTeamMembers, +}: { + stageResult?: TournamentMatchGameResult & { + players: User[]; + }; + upperTeamMembers: (TournamentTeamMember & { + member: User; + })[]; + lowerTeamMembers: (TournamentTeamMember & { + member: User; + })[]; +}) { + if (!stageResult) return { upperTeamMembers: [], lowerTeamMembers: [] }; + + const stageResultPlayerIds = stageResult.players.reduce( + (acc, cur) => acc.add(cur.id), + new Set() + ); + + return { + upperTeamMembers: upperTeamMembers.map(({ member }) => { + return { + member: { + id: member.id, + discordName: member.discordName, + played: stageResultPlayerIds.has(member.id), + }, + }; + }), + lowerTeamMembers: lowerTeamMembers.map(({ member }) => { + return { + member: { + id: member.id, + discordName: member.discordName, + played: stageResultPlayerIds.has(member.id), + }, + }; + }), + }; +} diff --git a/app/models/TournamentTeamMember.ts b/app/models/TournamentTeamMember.ts index f4cad274e..de9d152fd 100644 --- a/app/models/TournamentTeamMember.ts +++ b/app/models/TournamentTeamMember.ts @@ -1,13 +1,13 @@ import type { Prisma } from ".prisma/client"; import { db } from "~/utils/db.server"; -export type Create = Prisma.PromiseReturnType; -export function create({ - userId, +export type JoinTeam = Prisma.PromiseReturnType; +export function joinTeam({ + memberId, teamId, tournamentId, }: { - userId: string; + memberId: string; teamId: string; tournamentId: string; }) { @@ -15,7 +15,7 @@ export function create({ data: { tournamentId, teamId, - memberId: userId, + memberId, }, }); } diff --git a/app/routes/to/$organization.$tournament/manage-team.tsx b/app/routes/to/$organization.$tournament/manage-team.tsx index 8f705fdb6..6087224a6 100644 --- a/app/routes/to/$organization.$tournament/manage-team.tsx +++ b/app/routes/to/$organization.$tournament/manage-team.tsx @@ -24,9 +24,9 @@ import { roompassRegExp, roompassRegExpString, } from "~/core/tournament/utils"; -import { putPlayerToTeam } from "~/db/tournament/mutations/putPlayerToTeam"; -import { tournamentTeamById } from "~/db/tournament/queries/tournamentTeamById"; import { useBaseURL, useTimeoutState } from "~/hooks/common"; +import * as TournamentTeam from "~/models/TournamentTeam"; +import * as TournamentTeamMember from "~/models/TournamentTeamMember"; import type { FindManyByTrustReceiverId } from "~/models/TrustRelationship"; import { editTeam, @@ -88,7 +88,7 @@ export const action: ActionFunction = async ({ switch (data._action) { case "ADD_PLAYER": { try { - const tournamentTeam = await tournamentTeamById(data.teamId); + const tournamentTeam = await TournamentTeam.findById(data.teamId); // TODO: Validate if tournament already started / concluded (depending on if tournament allows mid-event roster additions) validate(tournamentTeam, "Invalid tournament team id"); @@ -98,10 +98,10 @@ export const action: ActionFunction = async ({ "Not captain of the team" ); - await putPlayerToTeam({ + await TournamentTeamMember.joinTeam({ tournamentId: tournamentTeam.tournament.id, teamId: data.teamId, - newPlayerId: data.userId, + memberId: data.userId, }); } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError) { diff --git a/app/routes/to/$organization.$tournament/seeds.tsx b/app/routes/to/$organization.$tournament/seeds.tsx index e2c932235..9b30fd1e2 100644 --- a/app/routes/to/$organization.$tournament/seeds.tsx +++ b/app/routes/to/$organization.$tournament/seeds.tsx @@ -38,8 +38,7 @@ import { validate, } from "~/utils"; import { useTimeoutState } from "~/hooks/common"; -import { updateSeeds } from "~/db/tournament/mutations/updateSeeds"; -import { findTournamentById } from "~/db/tournament/queries/findTournamentById"; +import * as Tournament from "~/models/Tournament"; import { isTournamentAdmin, tournamentHasNotStarted, @@ -57,7 +56,7 @@ export const action: ActionFunction = async ({ context, request }) => { }); const user = requireUser(context); - const tournament = await findTournamentById(data.tournamentId); + const tournament = await Tournament.findById(data.tournamentId); validate(tournament, "Invalid tournament id"); validate( isTournamentAdmin({ userId: user.id, organization: tournament.organizer }), @@ -68,7 +67,7 @@ export const action: ActionFunction = async ({ context, request }) => { "Can't change seeds after tournament has started" ); - await updateSeeds({ + await Tournament.updateSeeds({ tournamentId: data.tournamentId, seeds: data.seeds, }); diff --git a/app/services/tournament.ts b/app/services/tournament.ts index e9495c98f..88b26b9f5 100644 --- a/app/services/tournament.ts +++ b/app/services/tournament.ts @@ -236,9 +236,9 @@ export async function joinTeamViaInviteCode({ const trustReceiverId = captainOfTeam(tournamentTeamToJoin).memberId; return Promise.all([ - TournamentTeamMember.create({ + TournamentTeamMember.joinTeam({ teamId: tournamentTeamToJoin.id, - userId, + memberId: userId, tournamentId, }), // TODO: this could also be put to queue and scheduled for later @@ -246,40 +246,6 @@ export async function joinTeamViaInviteCode({ ]); } -export async function putPlayerToTeam({ - teamId, - captainId, - newPlayerId, -}: { - teamId: string; - captainId: string; - newPlayerId: string; -}) { - const tournamentTeam = await TournamentTeam.findById(teamId); - - if (!tournamentTeam) throw new Response("Invalid team id", { status: 400 }); - - // TODO: 400 if tournament already started / concluded (depending on if tournament allows mid-event roster additions) - - if (tournamentTeam.members.length >= TOURNAMENT_TEAM_ROSTER_MAX_SIZE) { - throw new Response("Team is already full", { status: 400 }); - } - - if ( - !tournamentTeam.members.some( - ({ memberId, captain }) => captain && memberId === captainId - ) - ) { - throw new Response("Not captain of the team", { status: 401 }); - } - - return TournamentTeamMember.create({ - tournamentId: tournamentTeam.tournament.id, - teamId, - userId: newPlayerId, - }); -} - export async function editTeam({ teamId, userId, diff --git a/app/validators/tournament.ts b/app/validators/tournament.ts index f409f1f09..c2152b41e 100644 --- a/app/validators/tournament.ts +++ b/app/validators/tournament.ts @@ -1,5 +1,4 @@ import { TOURNAMENT_TEAM_ROSTER_MAX_SIZE } from "~/constants"; -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({ @@ -14,9 +13,9 @@ export function isTournamentAdmin({ } /** Checks if tournament has not started meaning there is no bracket with rounds generated. */ -export function tournamentHasNotStarted( - tournament: NonNullable -) { +export function tournamentHasNotStarted(tournament: { + brackets: { rounds: unknown[] }[]; +}) { return (tournament.brackets[0]?.rounds.length ?? 0) === 0; }