brackt/app/models/season.ts

221 lines
5.5 KiB
TypeScript

import { eq, and, sql } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
export type Season = typeof schema.seasons.$inferSelect;
export type NewSeason = Omit<typeof schema.seasons.$inferInsert, 'inviteCode'> & {
inviteCode?: string;
};
export type SeasonStatus = "pre_draft" | "draft" | "active" | "completed";
export interface SeasonWithSportsSeasons extends Season {
seasonSports: Array<{
id: string;
sportsSeason: {
id: string;
name: string;
year: number;
status: string;
scoringType: string;
scoringPattern: string | null;
draftOn: string; // 'YYYY-MM-DD'
draftOff: string; // 'YYYY-MM-DD'
sport: {
id: string;
name: string;
type: string;
slug: string;
};
};
}>;
}
export async function createSeason(data: NewSeason): Promise<Season> {
const db = database();
// Generate invite code if not provided
const seasonData = {
...data,
inviteCode: data.inviteCode || generateInviteCode(),
};
const [season] = await db
.insert(schema.seasons)
.values(seasonData)
.returning();
return season;
}
export async function findSeasonById(id: string): Promise<Season | undefined> {
const db = database();
return await db.query.seasons.findFirst({
where: eq(schema.seasons.id, id),
});
}
export async function findSeasonsByLeagueId(leagueId: string): Promise<Season[]> {
const db = database();
return await db.query.seasons.findMany({
where: eq(schema.seasons.leagueId, leagueId),
orderBy: (seasons, { desc }) => [desc(seasons.year)],
});
}
export async function findCurrentSeason(
leagueId: string
): Promise<Season | undefined> {
const db = database();
return await db.query.seasons.findFirst({
where: eq(schema.seasons.leagueId, leagueId),
orderBy: (seasons, { desc }) => [desc(seasons.year)],
});
}
export async function findCurrentSeasonWithSports(
leagueId: string
): Promise<SeasonWithSportsSeasons | undefined> {
const db = database();
return await db.query.seasons.findFirst({
where: eq(schema.seasons.leagueId, leagueId),
orderBy: (seasons, { desc }) => [desc(seasons.year)],
with: {
seasonSports: {
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
},
},
}) as SeasonWithSportsSeasons | undefined;
}
export async function findSeasonByLeagueAndYear(
leagueId: string,
year: number
): Promise<Season | undefined> {
const db = database();
return await db.query.seasons.findFirst({
where: and(
eq(schema.seasons.leagueId, leagueId),
eq(schema.seasons.year, year)
),
});
}
export async function updateSeason(
id: string,
data: Partial<NewSeason>
): Promise<Season> {
const db = database();
const [season] = await db
.update(schema.seasons)
.set({ ...data, updatedAt: new Date() })
.where(eq(schema.seasons.id, id))
.returning();
return season;
}
export async function updateSeasonStatus(
id: string,
status: SeasonStatus
): Promise<Season> {
return await updateSeason(id, { status });
}
export async function deleteSeason(id: string): Promise<void> {
const db = database();
await db.delete(schema.seasons).where(eq(schema.seasons.id, id));
}
export async function findSeasonWithTeamsAndLeague(seasonId: string) {
const db = database();
return await db.query.seasons.findFirst({
where: eq(schema.seasons.id, seasonId),
with: {
league: true,
teams: true,
},
});
}
export async function findSeasonWithSportsSeasons(
id: string
): Promise<SeasonWithSportsSeasons | undefined> {
const db = database();
return await db.query.seasons.findFirst({
where: eq(schema.seasons.id, id),
with: {
seasonSports: {
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
},
},
}) as SeasonWithSportsSeasons | undefined;
}
/**
* Generate a short, URL-safe invite code
* Uses base62 encoding (alphanumeric) for readability
*/
export function generateInviteCode(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const length = 10;
let result = '';
const randomValues = new Uint8Array(length);
crypto.getRandomValues(randomValues);
for (let i = 0; i < length; i++) {
result += chars[randomValues[i] % chars.length];
}
return result;
}
/**
* Find a season by its invite code
*/
export async function findSeasonByInviteCode(
inviteCode: string
): Promise<Season | undefined> {
const db = database();
return await db.query.seasons.findFirst({
where: eq(schema.seasons.inviteCode, inviteCode),
});
}
/**
* Regenerate invite code for a season
*/
export async function regenerateInviteCode(seasonId: string): Promise<Season> {
const newInviteCode = generateInviteCode();
return await updateSeason(seasonId, { inviteCode: newInviteCode });
}
export async function countSeasonsByStatus(): Promise<Record<SeasonStatus, number>> {
const db = database();
const rows = await db
.select({ status: schema.seasons.status, count: sql<number>`count(*)::int` })
.from(schema.seasons)
.groupBy(schema.seasons.status);
const result: Record<SeasonStatus, number> = {
pre_draft: 0,
draft: 0,
active: 0,
completed: 0,
};
for (const row of rows) {
if (row.status in result) {
result[row.status as SeasonStatus] = row.count;
}
}
return result;
}