Put backend logic in models

This commit is contained in:
Kalle (Sendou)
2022-01-21 09:13:31 +02:00
parent f1c2175153
commit fcce761c12
12 changed files with 211 additions and 289 deletions

View File

@@ -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,
},
});
}

View File

@@ -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,
},
});
}

View File

@@ -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<FindMatchModalInfoByNumber> {
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<string>()
);
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),
},
};
}),
};
}

View File

@@ -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 } },
},
});
}

View File

@@ -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 },
});
}

View File

@@ -5,7 +5,11 @@ export type FindById = Prisma.PromiseReturnType<typeof findById>;
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<typeof updateSeeds>;
export function updateSeeds({
tournamentId,
seeds,
}: {
tournamentId: string;
seeds: string[];
}) {
return db.tournament.update({
where: {
id: tournamentId,
},
data: {
seeds,
},
});
}

View File

@@ -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<typeof findById>;
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<FindInfoForModal> {
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<string>()
);
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),
},
};
}),
};
}

View File

@@ -1,13 +1,13 @@
import type { Prisma } from ".prisma/client";
import { db } from "~/utils/db.server";
export type Create = Prisma.PromiseReturnType<typeof create>;
export function create({
userId,
export type JoinTeam = Prisma.PromiseReturnType<typeof joinTeam>;
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,
},
});
}

View File

@@ -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) {

View File

@@ -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,
});

View File

@@ -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,

View File

@@ -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<FindTournamentById>
) {
export function tournamentHasNotStarted(tournament: {
brackets: { rounds: unknown[] }[];
}) {
return (tournament.brackets[0]?.rounds.length ?? 0) === 0;
}