mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-23 19:46:28 -05:00
Put backend logic in models
This commit is contained in:
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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),
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -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 } },
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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 },
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user