brackt/app/utils/sports-data-sync.server.ts

332 lines
9 KiB
TypeScript
Raw Normal View History

import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and } from "drizzle-orm";
interface ExportData {
version: string;
exportedAt: string;
sports: Array<{
name: string;
type: string;
slug: string;
description: string | null;
}>;
sportsSeasons: Array<{
sportSlug: string;
name: string;
year: number;
startDate: string | null;
endDate: string | null;
status: string;
scoringType: string;
}>;
participants: Array<{
sportSlug: string;
seasonName: string;
seasonYear: number;
name: string;
shortName: string | null;
externalId: string | null;
}>;
seasonTemplates: Array<{
name: string;
year: number;
description: string | null;
isActive: boolean;
sports: Array<{
sportSlug: string;
seasonName: string;
seasonYear: number;
}>;
}>;
}
export async function exportSportsDataToJSON(): Promise<ExportData> {
const db = database();
// Export sports
const sports = await db.query.sports.findMany({
orderBy: (sports, { asc }) => [asc(sports.name)],
});
// Export sports seasons with sport relation
const sportsSeasons = await db.query.sportsSeasons.findMany({
with: {
sport: true,
},
orderBy: (sportsSeasons, { desc, asc }) => [
desc(sportsSeasons.year),
asc(sportsSeasons.name),
],
});
// Export participants with relations
const participants = await db.query.participants.findMany({
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
orderBy: (participants, { asc }) => [asc(participants.name)],
});
// Export season templates with sports
const seasonTemplates = await db.query.seasonTemplates.findMany({
with: {
seasonTemplateSports: {
with: {
sportsSeason: {
with: {
sport: true,
},
},
},
},
},
orderBy: (templates, { desc }) => [desc(templates.year)],
});
// Build export data structure
return {
version: "1.0",
exportedAt: new Date().toISOString(),
sports: sports.map((sport) => ({
name: sport.name,
type: sport.type,
slug: sport.slug,
description: sport.description,
})),
sportsSeasons: sportsSeasons.map((season) => ({
sportSlug: season.sport.slug,
name: season.name,
year: season.year,
startDate: season.startDate,
endDate: season.endDate,
status: season.status,
scoringType: season.scoringType,
})),
participants: participants.map((participant) => ({
sportSlug: participant.sportsSeason.sport.slug,
seasonName: participant.sportsSeason.name,
seasonYear: participant.sportsSeason.year,
name: participant.name,
shortName: participant.shortName,
externalId: participant.externalId,
})),
seasonTemplates: seasonTemplates.map((template) => ({
name: template.name,
year: template.year,
description: template.description,
isActive: template.isActive,
sports: template.seasonTemplateSports.map((ts) => ({
sportSlug: ts.sportsSeason.sport.slug,
seasonName: ts.sportsSeason.name,
seasonYear: ts.sportsSeason.year,
})),
})),
};
}
export async function importSportsDataFromJSON(
jsonData: string,
mode: "merge" | "replace" = "merge"
): Promise<{ created: number; updated: number; skipped: number }> {
const db = database();
const importData: ExportData = JSON.parse(jsonData);
let created = 0;
let updated = 0;
let skipped = 0;
if (mode === "replace") {
// Delete in reverse order of dependencies
await db.delete(schema.seasonTemplateSports);
await db.delete(schema.seasonTemplates);
await db.delete(schema.participantResults);
await db.delete(schema.participants);
await db.delete(schema.seasonSports);
await db.delete(schema.sportsSeasons);
await db.delete(schema.sports);
}
// Track created IDs for relationships
const sportIdMap = new Map<string, string>();
const sportsSeasonIdMap = new Map<string, string>();
// Import sports
for (const sport of importData.sports) {
const existing = await db.query.sports.findFirst({
where: eq(schema.sports.slug, sport.slug),
});
if (existing) {
if (mode === "merge") {
await db
.update(schema.sports)
.set({
name: sport.name,
type: sport.type as "team" | "individual",
description: sport.description,
})
.where(eq(schema.sports.slug, sport.slug));
sportIdMap.set(sport.slug, existing.id);
updated++;
}
} else {
const [createdSport] = await db
.insert(schema.sports)
.values({
name: sport.name,
type: sport.type as "team" | "individual",
slug: sport.slug,
description: sport.description,
})
.returning();
sportIdMap.set(sport.slug, createdSport.id);
created++;
}
}
// Import sports seasons
for (const season of importData.sportsSeasons) {
const sportId = sportIdMap.get(season.sportSlug);
if (!sportId) {
skipped++;
continue;
}
const existing = await db.query.sportsSeasons.findFirst({
where: and(
eq(schema.sportsSeasons.sportId, sportId),
eq(schema.sportsSeasons.name, season.name),
eq(schema.sportsSeasons.year, season.year)
),
});
const seasonKey = `${season.sportSlug}:${season.name}:${season.year}`;
if (existing) {
if (mode === "merge") {
await db
.update(schema.sportsSeasons)
.set({
startDate: season.startDate,
endDate: season.endDate,
status: season.status as "upcoming" | "active" | "completed",
scoringType: season.scoringType as "playoffs" | "regular_season" | "majors",
})
.where(eq(schema.sportsSeasons.id, existing.id));
sportsSeasonIdMap.set(seasonKey, existing.id);
updated++;
}
} else {
const [createdSeason] = await db
.insert(schema.sportsSeasons)
.values({
sportId,
name: season.name,
year: season.year,
startDate: season.startDate,
endDate: season.endDate,
status: season.status as "upcoming" | "active" | "completed",
scoringType: season.scoringType as "playoffs" | "regular_season" | "majors",
})
.returning();
sportsSeasonIdMap.set(seasonKey, createdSeason.id);
created++;
}
}
// Import participants
for (const participant of importData.participants) {
const seasonKey = `${participant.sportSlug}:${participant.seasonName}:${participant.seasonYear}`;
const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
if (!sportsSeasonId) {
skipped++;
continue;
}
const existing = await db.query.participants.findFirst({
where: and(
eq(schema.participants.sportsSeasonId, sportsSeasonId),
eq(schema.participants.name, participant.name)
),
});
if (!existing) {
await db.insert(schema.participants).values({
sportsSeasonId,
name: participant.name,
shortName: participant.shortName,
externalId: participant.externalId,
});
created++;
} else {
skipped++;
}
}
// Import season templates
for (const template of importData.seasonTemplates) {
const existing = await db.query.seasonTemplates.findFirst({
where: and(
eq(schema.seasonTemplates.name, template.name),
eq(schema.seasonTemplates.year, template.year)
),
});
let templateId: string;
if (existing) {
if (mode === "merge") {
await db
.update(schema.seasonTemplates)
.set({
description: template.description,
isActive: template.isActive,
})
.where(eq(schema.seasonTemplates.id, existing.id));
templateId = existing.id;
updated++;
// Remove existing template sports to re-add them
await db
.delete(schema.seasonTemplateSports)
.where(eq(schema.seasonTemplateSports.templateId, templateId));
} else {
continue;
}
} else {
const [createdTemplate] = await db
.insert(schema.seasonTemplates)
.values({
name: template.name,
year: template.year,
description: template.description,
isActive: template.isActive,
})
.returning();
templateId = createdTemplate.id;
created++;
}
// Add template sports
for (const templateSport of template.sports) {
const seasonKey = `${templateSport.sportSlug}:${templateSport.seasonName}:${templateSport.seasonYear}`;
const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
if (sportsSeasonId) {
await db.insert(schema.seasonTemplateSports).values({
templateId,
sportsSeasonId,
});
}
}
}
return { created, updated, skipped };
}