brackt/app/models/season.ts
Chris Parsons 6ef829f667 feat: add recharts library and implement PointProgressionChart component for historical views
- Added recharts dependency to package.json for data visualization.
- Implemented PointProgressionChart component to display team point progression over time.
- Updated scoring-system.md to reflect completion of historical views and added implementation notes.
- Created unit tests for PointProgressionChart to ensure correct rendering and data handling.
- Developed season completion detection and percentage calculation functions in season-helpers.server.ts.
- Added tests for season completion logic and point progression data formatting.
- Enhanced league loader to fetch necessary data for current season and teams.
2025-11-14 21:18:34 -08:00

187 lines
4.6 KiB
TypeScript

import { eq, and } 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;
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 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 });
}