New routes /admin/tournaments and /admin/tournaments/:id. The detail page lets admins enter tournament results once via paste-and-parse, then calls syncTournamentResults to fan out to every linked window. Phase 3 Task 4 of canonical tournament layer migration.
138 lines
3.3 KiB
TypeScript
138 lines
3.3 KiB
TypeScript
import { eq, and, asc, desc } from "drizzle-orm";
|
|
import { database } from "~/database/context";
|
|
import * as schema from "~/database/schema";
|
|
|
|
export type Tournament = typeof schema.tournaments.$inferSelect;
|
|
export type NewTournament = typeof schema.tournaments.$inferInsert;
|
|
export type TournamentStatus = Tournament["status"];
|
|
|
|
export async function createTournament(
|
|
data: NewTournament
|
|
): Promise<Tournament> {
|
|
const db = database();
|
|
const [tournament] = await db
|
|
.insert(schema.tournaments)
|
|
.values(data)
|
|
.returning();
|
|
return tournament;
|
|
}
|
|
|
|
export async function getTournamentById(
|
|
id: string
|
|
): Promise<Tournament | null> {
|
|
const db = database();
|
|
const results = await db
|
|
.select()
|
|
.from(schema.tournaments)
|
|
.where(eq(schema.tournaments.id, id))
|
|
.limit(1);
|
|
return results[0] ?? null;
|
|
}
|
|
|
|
export async function findTournamentsBySport(
|
|
sportId: string
|
|
): Promise<Tournament[]> {
|
|
const db = database();
|
|
return await db
|
|
.select()
|
|
.from(schema.tournaments)
|
|
.where(eq(schema.tournaments.sportId, sportId))
|
|
.orderBy(asc(schema.tournaments.year), asc(schema.tournaments.startsAt));
|
|
}
|
|
|
|
export async function findTournamentBySportNameYear(
|
|
sportId: string,
|
|
name: string,
|
|
year: number
|
|
): Promise<Tournament | null> {
|
|
const db = database();
|
|
const results = await db
|
|
.select()
|
|
.from(schema.tournaments)
|
|
.where(
|
|
and(
|
|
eq(schema.tournaments.sportId, sportId),
|
|
eq(schema.tournaments.name, name),
|
|
eq(schema.tournaments.year, year)
|
|
)
|
|
)
|
|
.limit(1);
|
|
return results[0] ?? null;
|
|
}
|
|
|
|
export async function upsertTournament(
|
|
data: NewTournament
|
|
): Promise<Tournament> {
|
|
if (!data.sportId || !data.name || !data.year) {
|
|
throw new Error("sportId, name, and year are required for upsert");
|
|
}
|
|
|
|
const existing = await findTournamentBySportNameYear(
|
|
data.sportId,
|
|
data.name,
|
|
data.year
|
|
);
|
|
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
|
|
return await createTournament(data);
|
|
}
|
|
|
|
export async function findAllTournamentsGroupedBySport(): Promise<
|
|
Array<{
|
|
sport: { id: string; name: string };
|
|
tournaments: Tournament[];
|
|
}>
|
|
> {
|
|
const db = database();
|
|
|
|
const rows = await db
|
|
.select({
|
|
tournament: schema.tournaments,
|
|
sport: {
|
|
id: schema.sports.id,
|
|
name: schema.sports.name,
|
|
},
|
|
})
|
|
.from(schema.tournaments)
|
|
.innerJoin(schema.sports, eq(schema.tournaments.sportId, schema.sports.id))
|
|
.orderBy(
|
|
asc(schema.sports.name),
|
|
desc(schema.tournaments.year),
|
|
asc(schema.tournaments.startsAt)
|
|
);
|
|
|
|
const groupMap = new Map<
|
|
string,
|
|
{ sport: { id: string; name: string }; tournaments: Tournament[] }
|
|
>();
|
|
|
|
for (const row of rows) {
|
|
const existing = groupMap.get(row.sport.id);
|
|
if (existing) {
|
|
existing.tournaments.push(row.tournament);
|
|
} else {
|
|
groupMap.set(row.sport.id, {
|
|
sport: row.sport,
|
|
tournaments: [row.tournament],
|
|
});
|
|
}
|
|
}
|
|
|
|
return Array.from(groupMap.values());
|
|
}
|
|
|
|
export async function updateTournamentStatus(
|
|
id: string,
|
|
status: TournamentStatus
|
|
): Promise<Tournament> {
|
|
const db = database();
|
|
const [tournament] = await db
|
|
.update(schema.tournaments)
|
|
.set({ status, updatedAt: new Date() })
|
|
.where(eq(schema.tournaments.id, id))
|
|
.returning();
|
|
return tournament;
|
|
}
|