From 2ab24f64fa75e92de60fdb25c3f78a39dcee87b7 Mon Sep 17 00:00:00 2001 From: cesaregarza <40225001+cesaregarza@users.noreply.github.com> Date: Thu, 28 Aug 2025 10:53:33 -0500 Subject: [PATCH] Feature/add tournament active players endpoint (#2497) Co-authored-by: Kalle <38327916+Sendouc@users.noreply.github.com> --- .../routes/tournament.$id.players.ts | 30 ++++++++++++++++ app/features/api-public/schema.ts | 7 ++++ .../TournamentMatchRepository.server.ts | 27 ++++++++++++++ .../playersThatPlayedByTeamId.server.ts | 35 ------------------- app/routes.ts | 4 +++ 5 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 app/features/api-public/routes/tournament.$id.players.ts delete mode 100644 app/features/tournament-bracket/queries/playersThatPlayedByTeamId.server.ts diff --git a/app/features/api-public/routes/tournament.$id.players.ts b/app/features/api-public/routes/tournament.$id.players.ts new file mode 100644 index 000000000..c7d668715 --- /dev/null +++ b/app/features/api-public/routes/tournament.$id.players.ts @@ -0,0 +1,30 @@ +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import { cors } from "remix-utils/cors"; +import { z } from "zod/v4"; +import * as TournamentMatchRepository from "~/features/tournament-bracket/TournamentMatchRepository.server"; +import { parseParams } from "~/utils/remix.server"; +import { id } from "~/utils/zod"; +import { + handleOptionsRequest, + requireBearerAuth, +} from "../api-public-utils.server"; +import type { GetTournamentPlayersResponse } from "../schema"; + +const paramsSchema = z.object({ + id, +}); + +export const loader = async ({ params, request }: LoaderFunctionArgs) => { + await handleOptionsRequest(request); + requireBearerAuth(request); + + const { id } = parseParams({ + params, + schema: paramsSchema, + }); + + const participants: GetTournamentPlayersResponse = + await TournamentMatchRepository.userParticipationByTournamentId(id); + + return cors(request, json(participants)); +}; diff --git a/app/features/api-public/schema.ts b/app/features/api-public/schema.ts index 8139fd137..62e5a3bc7 100644 --- a/app/features/api-public/schema.ts +++ b/app/features/api-public/schema.ts @@ -200,6 +200,13 @@ export type GetTournamentTeamsResponse = Array<{ }>; }>; +/** GET /api/tournament/{tournamentId}/players */ + +export type GetTournamentPlayersResponse = Array<{ + userId: number; + matchIds: number[]; +}>; + /** GET /api/tournament/{tournamentId}/casted */ export interface GetCastedTournamentMatchesResponse { diff --git a/app/features/tournament-bracket/TournamentMatchRepository.server.ts b/app/features/tournament-bracket/TournamentMatchRepository.server.ts index fd6d7f66a..bf5f46a83 100644 --- a/app/features/tournament-bracket/TournamentMatchRepository.server.ts +++ b/app/features/tournament-bracket/TournamentMatchRepository.server.ts @@ -11,3 +11,30 @@ export function findResultById(id: number) { .where("TournamentMatchGameResult.id", "=", id) .executeTakeFirst(); } + +export async function userParticipationByTournamentId(tournamentId: number) { + return db + .with("playerMatches", (db) => + db + .selectFrom("TournamentMatchGameResultParticipant as Participant") + .innerJoin( + "TournamentMatchGameResult as GameResult", + "GameResult.id", + "Participant.matchGameResultId", + ) + .innerJoin("TournamentMatch as Match", "Match.id", "GameResult.matchId") + .innerJoin("TournamentStage as Stage", "Stage.id", "Match.stageId") + .select(["Participant.userId", "GameResult.matchId"]) + .where("Stage.tournamentId", "=", tournamentId) + .distinct(), + ) + .selectFrom("playerMatches") + .select(({ fn, ref }) => [ + "playerMatches.userId", + fn + .agg("json_group_array", [ref("playerMatches.matchId")]) + .as("matchIds"), + ]) + .groupBy("playerMatches.userId") + .execute(); +} diff --git a/app/features/tournament-bracket/queries/playersThatPlayedByTeamId.server.ts b/app/features/tournament-bracket/queries/playersThatPlayedByTeamId.server.ts deleted file mode 100644 index 5341b3179..000000000 --- a/app/features/tournament-bracket/queries/playersThatPlayedByTeamId.server.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { sql } from "~/db/sql"; -import type { Tables } from "~/db/tables"; - -const stm = sql.prepare(/* sql */ ` - select - "User"."id", - "User"."username", - "User"."discordAvatar", - "User"."discordId", - "User"."customUrl", - "User"."country", - "TournamentTeam"."id" as "tournamentTeamId" - from "TournamentTeam" - left join "TournamentTeamMember" on "TournamentTeamMember"."tournamentTeamId" = "TournamentTeam"."id" - left join "User" on "User"."id" = "TournamentTeamMember"."userId" - left join "TournamentStage" on "TournamentStage"."tournamentId" = "TournamentTeam"."tournamentId" - left join "TournamentMatch" on "TournamentMatch"."stageId" = "TournamentStage"."id" - left join "TournamentMatchGameResult" on "TournamentMatchGameResult"."matchId" = "TournamentMatch"."id" - right join "TournamentMatchGameResultParticipant" on - "TournamentMatchGameResultParticipant"."matchGameResultId" = "TournamentMatchGameResult"."id" - and - "TournamentTeamMember"."userId" = "TournamentMatchGameResultParticipant"."userId" - - where "TournamentTeam"."tournamentId" = @tournamentId - group by "User"."id" -`); - -export type PlayerThatPlayedByTeamId = Pick< - Tables["User"], - "id" | "username" | "discordAvatar" | "discordId" | "customUrl" | "country" -> & { tournamentTeamId: number }; - -export function playersThatPlayedByTournamentId(tournamentId: number) { - return stm.all({ tournamentId }) as PlayerThatPlayedByTeamId[]; -} diff --git a/app/routes.ts b/app/routes.ts index 20feb818b..e12044579 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -269,6 +269,10 @@ export default [ "/tournament/:id/teams", "features/api-public/routes/tournament.$id.teams.ts", ), + route( + "/tournament/:id/players", + "features/api-public/routes/tournament.$id.players.ts", + ), route( "/tournament/:id/casted", "features/api-public/routes/tournament.$id.casted.ts",