brackt/app/models/participant-season-result.ts

244 lines
6.1 KiB
TypeScript

import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and, inArray, desc } from "drizzle-orm";
export interface UpsertSeasonResultData {
participantId: string;
sportsSeasonId: string;
currentPoints?: number;
currentPosition?: number;
}
export interface UpdateSeasonResultData {
currentPoints?: number;
currentPosition?: number;
}
/**
* Upsert a participant's season result (for F1, etc.)
* Creates if doesn't exist, updates if it does
*/
export async function upsertParticipantSeasonResult(
data: UpsertSeasonResultData,
providedDb?: ReturnType<typeof database>
) {
const db = providedDb || database();
// Check if result exists
const existing = await db.query.participantSeasonResults.findFirst({
where: and(
eq(schema.participantSeasonResults.participantId, data.participantId),
eq(schema.participantSeasonResults.sportsSeasonId, data.sportsSeasonId)
),
});
if (existing) {
// Update existing
const [updated] = await db
.update(schema.participantSeasonResults)
.set({
currentPoints: data.currentPoints?.toString(),
currentPosition: data.currentPosition,
updatedAt: new Date(),
})
.where(eq(schema.participantSeasonResults.id, existing.id))
.returning();
return updated;
} else {
// Insert new
const [created] = await db
.insert(schema.participantSeasonResults)
.values({
participantId: data.participantId,
sportsSeasonId: data.sportsSeasonId,
currentPoints: data.currentPoints?.toString() || "0",
currentPosition: data.currentPosition,
})
.returning();
return created;
}
}
/**
* Bulk upsert season results for multiple participants
* Useful for entering standings for an entire F1 grid at once
*/
export async function upsertSeasonResultsBulk(
results: UpsertSeasonResultData[],
providedDb?: ReturnType<typeof database>
) {
const db = providedDb || database();
const upserted = [];
for (const data of results) {
const result = await upsertParticipantSeasonResult(data, db);
upserted.push(result);
}
return upserted;
}
/**
* Get a participant's season result
*/
export async function getParticipantSeasonResult(
participantId: string,
sportsSeasonId: string,
providedDb?: ReturnType<typeof database>
) {
const db = providedDb || database();
return await db.query.participantSeasonResults.findFirst({
where: and(
eq(schema.participantSeasonResults.participantId, participantId),
eq(schema.participantSeasonResults.sportsSeasonId, sportsSeasonId)
),
with: {
participant: {
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
},
},
});
}
/**
* Get all season results for a sports season
* Returns participants ordered by current position (or points if position not set)
*/
export async function getSeasonResults(
sportsSeasonId: string,
providedDb?: ReturnType<typeof database>
) {
const db = providedDb || database();
const results = await db.query.participantSeasonResults.findMany({
where: eq(schema.participantSeasonResults.sportsSeasonId, sportsSeasonId),
with: {
participant: {
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
},
},
});
// Sort by position (nulls last), then by points descending
return results.sort((a, b) => {
if (a.currentPosition !== null && b.currentPosition !== null) {
return a.currentPosition - b.currentPosition;
}
if (a.currentPosition !== null) return -1;
if (b.currentPosition !== null) return 1;
// Both null positions, sort by points
const aPoints = parseFloat(a.currentPoints || "0");
const bPoints = parseFloat(b.currentPoints || "0");
return bPoints - aPoints;
});
}
/**
* Get season results for specific participants
*/
export async function getSeasonResultsForParticipants(
sportsSeasonId: string,
participantIds: string[],
providedDb?: ReturnType<typeof database>
) {
const db = providedDb || database();
if (participantIds.length === 0) return [];
return await db.query.participantSeasonResults.findMany({
where: and(
eq(schema.participantSeasonResults.sportsSeasonId, sportsSeasonId),
inArray(schema.participantSeasonResults.participantId, participantIds)
),
with: {
participant: {
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
},
},
});
}
/**
* Update a participant's season result
*/
export async function updateParticipantSeasonResult(
participantId: string,
sportsSeasonId: string,
data: UpdateSeasonResultData,
providedDb?: ReturnType<typeof database>
) {
const db = providedDb || database();
const [updated] = await db
.update(schema.participantSeasonResults)
.set({
currentPoints: data.currentPoints?.toString(),
currentPosition: data.currentPosition,
updatedAt: new Date(),
})
.where(
and(
eq(schema.participantSeasonResults.participantId, participantId),
eq(schema.participantSeasonResults.sportsSeasonId, sportsSeasonId)
)
)
.returning();
return updated;
}
/**
* Delete all season results for a sports season
*/
export async function deleteSeasonResults(
sportsSeasonId: string,
providedDb?: ReturnType<typeof database>
) {
const db = providedDb || database();
await db
.delete(schema.participantSeasonResults)
.where(eq(schema.participantSeasonResults.sportsSeasonId, sportsSeasonId));
}
/**
* Check if a participant has a season result
*/
export async function hasParticipantSeasonResult(
participantId: string,
sportsSeasonId: string,
providedDb?: ReturnType<typeof database>
): Promise<boolean> {
const db = providedDb || database();
const result = await db.query.participantSeasonResults.findFirst({
where: and(
eq(schema.participantSeasonResults.participantId, participantId),
eq(schema.participantSeasonResults.sportsSeasonId, sportsSeasonId)
),
});
return !!result;
}