sendou.ink/app/features/api-public/routes/tournament.$id.ts
2025-12-29 19:21:11 +02:00

89 lines
2.6 KiB
TypeScript

import { jsonArrayFrom } from "kysely/helpers/sqlite";
import type { LoaderFunctionArgs } from "react-router";
import { cors } from "remix-utils/cors";
import { z } from "zod/v4";
import { db } from "~/db/sql";
import { databaseTimestampToDate } from "~/utils/dates";
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
import { id } from "~/utils/zod";
import {
handleOptionsRequest,
requireBearerAuth,
} from "../api-public-utils.server";
import type { GetTournamentResponse } 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 tournament = notFoundIfFalsy(
await db
.selectFrom("Tournament")
.innerJoin("CalendarEvent", "CalendarEvent.tournamentId", "Tournament.id")
.innerJoin(
"CalendarEventDate",
"CalendarEventDate.eventId",
"CalendarEvent.id",
)
.select(({ eb, exists, selectFrom }) => [
"CalendarEvent.name",
"CalendarEvent.organizationId",
"CalendarEventDate.startTime",
"Tournament.settings",
exists(
selectFrom("TournamentResult")
.where("TournamentResult.tournamentId", "=", id)
.select("TournamentResult.tournamentId"),
).as("isFinalized"),
eb
.selectFrom("UserSubmittedImage")
.select(["UserSubmittedImage.url"])
.whereRef("CalendarEvent.avatarImgId", "=", "UserSubmittedImage.id")
.as("logoUrl"),
jsonArrayFrom(
eb
.selectFrom("TournamentTeam")
.leftJoin("TournamentTeamCheckIn", (join) =>
join
.onRef(
"TournamentTeam.id",
"=",
"TournamentTeamCheckIn.tournamentTeamId",
)
.on("TournamentTeamCheckIn.bracketIdx", "is", null),
)
.select(["TournamentTeamCheckIn.checkedInAt"])
.where("TournamentTeam.tournamentId", "=", id),
).as("teams"),
])
.where("Tournament.id", "=", id)
.executeTakeFirst(),
);
const result: GetTournamentResponse = {
name: tournament.name,
startTime: databaseTimestampToDate(tournament.startTime).toISOString(),
url: `https://sendou.ink/to/${id}/brackets`,
logoUrl: tournament.logoUrl,
teams: {
checkedInCount: tournament.teams.filter((team) => team.checkedInAt)
.length,
registeredCount: tournament.teams.length,
},
brackets: tournament.settings.bracketProgression.map((bp) => ({
name: bp.name,
type: bp.type,
})),
organizationId: tournament.organizationId,
isFinalized: Boolean(tournament.isFinalized),
};
return await cors(request, Response.json(result));
};