Migrate setHistoryByTeamId to Kysely

This commit is contained in:
Kalle 2026-04-27 08:43:30 +03:00
parent 53d7ca3813
commit b26f4ab254
4 changed files with 132 additions and 133 deletions

View File

@ -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<number>`"TournamentMatch"."opponentOne" ->> '$.id'`;
const opponentTwoId = sql<number>`"TournamentMatch"."opponentTwo" ->> '$.id'`;
const opponentOneScore = sql<
number | null
>`"TournamentMatch"."opponentOne" ->> '$.score'`;
const opponentTwoScore = sql<
number | null
>`"TournamentMatch"."opponentTwo" ->> '$.score'`;
export type FindMatchById = NonNullable<Unwrapped<typeof findMatchById>>;
export async function findMatchById(id: number) {
const row = await db
@ -52,12 +62,12 @@ export async function findMatchById(id: number) {
innerEb(
"TournamentTeamMember.tournamentTeamId",
"=",
sql<number>`"TournamentMatch"."opponentOne" ->> '$.id'`,
opponentOneId,
),
innerEb(
"TournamentTeamMember.tournamentTeamId",
"=",
sql<number>`"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<number>`"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();
}

View File

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

View File

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

View File

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