diff --git a/app/features/tournament-bracket/TournamentMatchRepository.server.ts b/app/features/tournament-bracket/TournamentMatchRepository.server.ts index dbf3de67e..050208d4f 100644 --- a/app/features/tournament-bracket/TournamentMatchRepository.server.ts +++ b/app/features/tournament-bracket/TournamentMatchRepository.server.ts @@ -1,8 +1,18 @@ import { sql } from "kysely"; import { jsonArrayFrom } from "kysely/helpers/sqlite"; import { db } from "~/db/sql"; +import { TournamentMatchStatus } from "~/db/tables"; import type { Unwrapped } from "~/utils/types"; +const opponentOneId = sql`"TournamentMatch"."opponentOne" ->> '$.id'`; +const opponentTwoId = sql`"TournamentMatch"."opponentTwo" ->> '$.id'`; +const opponentOneScore = sql< + number | null +>`"TournamentMatch"."opponentOne" ->> '$.score'`; +const opponentTwoScore = sql< + number | null +>`"TournamentMatch"."opponentTwo" ->> '$.score'`; + export type FindMatchById = NonNullable>; export async function findMatchById(id: number) { const row = await db @@ -52,12 +62,12 @@ export async function findMatchById(id: number) { innerEb( "TournamentTeamMember.tournamentTeamId", "=", - sql`"TournamentMatch"."opponentOne" ->> '$.id'`, + opponentOneId, ), innerEb( "TournamentTeamMember.tournamentTeamId", "=", - sql`"TournamentMatch"."opponentTwo" ->> '$.id'`, + opponentTwoId, ), ]), ), @@ -112,3 +122,109 @@ export async function userParticipationByTournamentId(tournamentId: number) { .groupBy("playerMatches.userId") .execute(); } + +export type FindByTournamentTeamIdItem = Unwrapped< + typeof findByTournamentTeamId +>; +export function findByTournamentTeamId(tournamentTeamId: number) { + return db + .selectFrom("TournamentMatch") + .innerJoin( + "TournamentRound", + "TournamentRound.id", + "TournamentMatch.roundId", + ) + .innerJoin( + "TournamentGroup", + "TournamentGroup.id", + "TournamentMatch.groupId", + ) + .innerJoin("TournamentTeam as otherTeam", (join) => + join.on((eb) => + eb.or([ + eb.and([ + eb(opponentOneId, "!=", tournamentTeamId), + eb(opponentOneId, "=", eb.ref("otherTeam.id")), + ]), + eb.and([ + eb(opponentTwoId, "!=", tournamentTeamId), + eb(opponentTwoId, "=", eb.ref("otherTeam.id")), + ]), + ]), + ), + ) + .select(({ eb }) => [ + "TournamentMatch.id as tournamentMatchId", + opponentOneScore.as("opponentOneScore"), + opponentTwoScore.as("opponentTwoScore"), + "otherTeam.name as otherTeamName", + "otherTeam.id as otherTeamId", + "TournamentRound.number as roundNumber", + "TournamentRound.stageId", + "TournamentGroup.number as groupNumber", + jsonArrayFrom( + eb + .selectFrom("TournamentMatchGameResult") + .select([ + "TournamentMatchGameResult.mode", + "TournamentMatchGameResult.stageId", + "TournamentMatchGameResult.source", + sql`"TournamentMatchGameResult"."winnerTeamId" = ${tournamentTeamId}`.as( + "wasWinner", + ), + ]) + .whereRef( + "TournamentMatchGameResult.matchId", + "=", + "TournamentMatch.id", + ) + .orderBy("TournamentMatchGameResult.number", "asc"), + ).as("matches"), + jsonArrayFrom( + eb + .selectFrom("User") + .innerJoin( + "TournamentMatchGameResultParticipant", + "TournamentMatchGameResultParticipant.userId", + "User.id", + ) + .innerJoin( + "TournamentMatchGameResult", + "TournamentMatchGameResult.id", + "TournamentMatchGameResultParticipant.matchGameResultId", + ) + .innerJoin("TournamentTeamMember", (join) => + join + .onRef("TournamentTeamMember.userId", "=", "User.id") + .onRef( + "TournamentTeamMember.tournamentTeamId", + "=", + "otherTeam.id", + ), + ) + .select([ + "User.id", + "User.username", + "User.discordAvatar", + "User.discordId", + "User.customUrl", + ]) + .whereRef( + "TournamentMatchGameResult.matchId", + "=", + "TournamentMatch.id", + ) + .distinct(), + ).as("players"), + ]) + .where((eb) => + eb.or([ + eb(opponentOneId, "=", tournamentTeamId), + eb(opponentTwoId, "=", tournamentTeamId), + ]), + ) + .where("TournamentMatch.status", ">=", TournamentMatchStatus.Completed) + .orderBy("TournamentGroup.number", "asc") + .orderBy("TournamentRound.number", "asc") + .execute(); +} diff --git a/app/features/tournament/core/sets.server.ts b/app/features/tournament/core/sets.server.ts index 503febe70..ebe6d658a 100644 --- a/app/features/tournament/core/sets.server.ts +++ b/app/features/tournament/core/sets.server.ts @@ -1,14 +1,11 @@ import type { Tables } from "~/db/tables"; +import type { FindByTournamentTeamIdItem } from "~/features/tournament-bracket/TournamentMatchRepository.server"; import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; import { sourceTypes } from "~/modules/tournament-map-list-generator/constants"; import type { TournamentMaplistSource } from "~/modules/tournament-map-list-generator/types"; import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; -import { findRoundsByTournamentId } from "../queries/findRoundsByTournamentId.server"; -import { - type SetHistoryByTeamIdItem, - setHistoryByTeamId, -} from "../queries/setHistoryByTeamId.server"; +import type { findRoundsByTournamentId } from "../queries/findRoundsByTournamentId.server"; export interface PlayedSet { tournamentMatchId: number; @@ -78,15 +75,12 @@ export function winCounts(sets: PlayedSet[]) { } export function tournamentTeamSets({ - tournamentTeamId, - tournamentId, + sets, + allRounds, }: { - tournamentTeamId: number; - tournamentId: number; + sets: FindByTournamentTeamIdItem[]; + allRounds: ReturnType; }): PlayedSet[] { - const sets = setHistoryByTeamId(tournamentTeamId); - const allRounds = findRoundsByTournamentId(tournamentId); - return sets.map((set) => { const round = allRounds.find((round) => round.stageId === set.stageId) ?? allRounds[0]; @@ -157,7 +151,7 @@ function parseTournamentMaplistSource(source: string): TournamentMaplistSource { return parsed; } -function flipScoreIfNeeded(set: SetHistoryByTeamIdItem): [number, number] { +function flipScoreIfNeeded(set: FindByTournamentTeamIdItem): [number, number] { const score: [number, number] = [ set.opponentOneScore ?? 0, set.opponentTwoScore ?? 0, diff --git a/app/features/tournament/loaders/to.$id.teams.$tid.server.ts b/app/features/tournament/loaders/to.$id.teams.$tid.server.ts index 1ab1f5fa5..6535b4a80 100644 --- a/app/features/tournament/loaders/to.$id.teams.$tid.server.ts +++ b/app/features/tournament/loaders/to.$id.teams.$tid.server.ts @@ -1,8 +1,10 @@ import type { LoaderFunctionArgs } from "react-router"; import { tournamentDataCached } from "~/features/tournament-bracket/core/Tournament.server"; +import * as TournamentMatchRepository from "~/features/tournament-bracket/TournamentMatchRepository.server"; import { tournamentTeamPageParamsSchema } from "~/features/tournament-bracket/tournament-bracket-schemas.server"; import { parseParams } from "~/utils/remix.server"; import { tournamentTeamSets, winCounts } from "../core/sets.server"; +import { findRoundsByTournamentId } from "../queries/findRoundsByTournamentId.server"; export const loader = async ({ params }: LoaderFunctionArgs) => { const { id: tournamentId, tid: tournamentTeamId } = parseParams({ @@ -15,7 +17,11 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { throw new Response(null, { status: 404 }); } - const sets = tournamentTeamSets({ tournamentTeamId, tournamentId }); + const setHistory = + await TournamentMatchRepository.findByTournamentTeamId(tournamentTeamId); + const allRounds = findRoundsByTournamentId(tournamentId); + + const sets = tournamentTeamSets({ sets: setHistory, allRounds }); return { tournamentTeamId, diff --git a/app/features/tournament/queries/setHistoryByTeamId.server.ts b/app/features/tournament/queries/setHistoryByTeamId.server.ts deleted file mode 100644 index cfe88b11f..000000000 --- a/app/features/tournament/queries/setHistoryByTeamId.server.ts +++ /dev/null @@ -1,117 +0,0 @@ -import * as R from "remeda"; -import { sql } from "~/db/sql"; -import type { Tables } from "~/db/tables"; -import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; -import { parseDBArray } from "~/utils/sql"; - -const stm = sql.prepare(/* sql */ ` - with "q1" as ( - select - "m"."id" as "tournamentMatchId", - "m"."opponentOne" ->> '$.score' as "opponentOneScore", - "m"."opponentTwo" ->> '$.score' as "opponentTwoScore", - "otherTeam"."name" as "otherTeamName", - "otherTeam"."id" as "otherTeamId", - "round"."number" as "roundNumber", - "round"."stageId" as "stageId", - "group"."number" as "groupNumber", - json_group_array( - json_object( - 'mode', - "r"."mode", - 'stageId', - "r"."stageId", - 'wasWinner', - "r"."winnerTeamId" == @tournamentTeamId, - 'source', - "r"."source" - ) - ) as "matches" - from "TournamentMatch" as "m" - left join "TournamentMatchGameResult" as "r" on "m"."id" = "r"."matchId" - left join "TournamentRound" as "round" on "m"."roundId" = "round"."id" - left join "TournamentGroup" as "group" on "m"."groupId" = "group"."id" - left join "TournamentTeam" as "otherTeam" on - ( - "m"."opponentOne" ->> '$.id' != @tournamentTeamId - and - "m"."opponentOne" ->> '$.id' = "otherTeam"."id" - ) or - ( - "m"."opponentTwo" ->> '$.id' != @tournamentTeamId - and - "m"."opponentTwo" ->> '$.id' = "otherTeam"."id" - ) - where - ( - "m"."opponentOne" ->> '$.id' = @tournamentTeamId - or - "m"."opponentTwo" ->> '$.id' = @tournamentTeamId - ) - and "m"."status" >= 4 - group by "m"."id" - order by "groupNumber" asc, "roundNumber" asc, "r"."number" asc - ) - select - "q1".*, - json_group_array( - json_object( - 'id', - "u"."id", - 'username', - "u"."username", - 'discordAvatar', - "u"."discordAvatar", - 'discordId', - "u"."discordId", - 'customUrl', - "u"."customUrl" - ) - ) as "players" - from "q1" - left join "TournamentMatchGameResult" as "r" on "q1"."tournamentMatchId" = "r"."matchId" - left join "TournamentMatchGameResultParticipant" as "p" on "r"."id" = "p"."matchGameResultId" - left join "User" as "u" on "p"."userId" = "u"."id" - -- filters out own team results - inner join "TournamentTeamMember" as "m" on "p"."userId" = "m"."userId" - and "m"."tournamentTeamId" == "q1"."otherTeamId" - group by "q1"."tournamentMatchId" -`); - -export interface SetHistoryByTeamIdItem { - tournamentMatchId: number; - opponentOneScore: number | null; - opponentTwoScore: number | null; - otherTeamName: string; - otherTeamId: number; - roundNumber: number; - stageId: number; - groupNumber: number; - matches: { - stageId: StageId; - source: Tables["TournamentMatchGameResult"]["source"]; - mode: ModeShort; - wasWinner: number; - }[]; - players: Array< - Pick< - Tables["User"], - "id" | "username" | "discordAvatar" | "discordId" | "customUrl" - > - >; -} - -export function setHistoryByTeamId( - tournamentTeamId: number, -): Array { - const rows = stm.all({ tournamentTeamId }) as any[]; - - return rows.map((row) => { - return { - ...row, - matches: parseDBArray(row.matches), - // TODO: there is probably a way to do this in SQL - players: R.uniqueBy(parseDBArray(row.players), (u) => u.id), - }; - }); -}