Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
import achievementArt from '../data/cod-achievement-art.json';
|
|
const achievementImages: Record<string, string> = achievementArt;
|
|
import artwork from '../data/cod-art.json';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
import { and, eq, inArray, notInArray, sql } from 'drizzle-orm';
|
|
import postgres from 'postgres';
|
|
import {
|
|
trackerGames,
|
|
trackerItems,
|
|
trackerProgress
|
|
} from '../src/lib/server/db/schema/game-tracker';
|
|
import type { MasteryRules } from '../src/lib/game-tracker/mastery';
|
|
|
|
type Catalog = {
|
|
games: (Omit<MasteryRules, 'classes'> & {
|
|
slug: string;
|
|
name: string;
|
|
shortName: string;
|
|
releaseYear: number;
|
|
classes: { name: string; weapons: string[]; postLaunch?: string[] }[];
|
|
achievements: {
|
|
key: string;
|
|
category: string;
|
|
name: string;
|
|
description: string | null;
|
|
icon: string;
|
|
}[];
|
|
})[];
|
|
};
|
|
|
|
// Keys are derived from weapon names, so renaming a weapon starts its progress over.
|
|
const weaponKey = (name: string) =>
|
|
`weapon:${name
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-|-$/g, '')}`;
|
|
|
|
if (!process.env.DB_URL) throw new Error('DB_URL is required');
|
|
const catalog: Catalog = JSON.parse(
|
|
await readFile(new URL('../data/call-of-duty.json', import.meta.url), 'utf8')
|
|
);
|
|
const client = postgres(process.env.DB_URL, { max: 1 });
|
|
const db = drizzle({ client });
|
|
try {
|
|
await db.transaction(async (tx) => {
|
|
for (const [order, game] of catalog.games.entries()) {
|
|
const rules: MasteryRules = {
|
|
milestones: game.milestones,
|
|
tiers: game.tiers,
|
|
classes: game.classes.map((c) => c.name),
|
|
steamApps: game.steamApps,
|
|
rankCaps: game.rankCaps
|
|
};
|
|
const values = {
|
|
slug: game.slug,
|
|
franchise: 'call-of-duty',
|
|
name: game.name,
|
|
shortName: game.shortName,
|
|
releaseYear: game.releaseYear,
|
|
sortOrder: order,
|
|
rules
|
|
};
|
|
const [saved] = await tx
|
|
.insert(trackerGames)
|
|
.values(values)
|
|
.onConflictDoUpdate({ target: trackerGames.slug, set: values })
|
|
.returning();
|
|
const weapons = game.classes.flatMap((c) =>
|
|
[
|
|
...c.weapons.map((name) => ({ name, postLaunch: false })),
|
|
...(c.postLaunch ?? []).map((name) => ({ name, postLaunch: true }))
|
|
].map((weapon) => ({ ...weapon, category: c.name }))
|
|
);
|
|
const items = [
|
|
...weapons.map((weapon, index) => ({
|
|
gameId: saved.id,
|
|
key: weaponKey(weapon.name),
|
|
kind: 'weapon' as const,
|
|
category: weapon.category,
|
|
name: weapon.name,
|
|
description: null,
|
|
icon:
|
|
artwork.find((a) => a.game === game.slug && a.key === weaponKey(weapon.name))?.path ??
|
|
null,
|
|
postLaunch: weapon.postLaunch,
|
|
sortOrder: index
|
|
})),
|
|
...game.achievements.map((achievement, index) => ({
|
|
gameId: saved.id,
|
|
key: achievement.key,
|
|
kind: 'achievement' as const,
|
|
category: achievement.category,
|
|
name: achievement.name,
|
|
description: achievement.description,
|
|
icon: achievementImages[achievement.icon] ?? achievement.icon,
|
|
postLaunch: false,
|
|
sortOrder: index
|
|
}))
|
|
];
|
|
await tx
|
|
.insert(trackerItems)
|
|
.values(items)
|
|
.onConflictDoUpdate({
|
|
target: [trackerItems.gameId, trackerItems.key],
|
|
set: {
|
|
kind: sql`excluded.kind`,
|
|
category: sql`excluded.category`,
|
|
name: sql`excluded.name`,
|
|
description: sql`excluded.description`,
|
|
icon: sql`excluded.icon`,
|
|
postLaunch: sql`excluded.post_launch`,
|
|
sortOrder: sql`excluded.sort_order`
|
|
}
|
|
});
|
|
// Items dropped from the catalog disappear unless someone has progress on them.
|
|
const stale = await tx
|
|
.select({ id: trackerItems.id, name: trackerItems.name })
|
|
.from(trackerItems)
|
|
.where(
|
|
and(
|
|
eq(trackerItems.gameId, saved.id),
|
|
notInArray(
|
|
trackerItems.key,
|
|
items.map((item) => item.key)
|
|
)
|
|
)
|
|
);
|
|
if (stale.length) {
|
|
const kept = await tx
|
|
.selectDistinct({ id: trackerProgress.itemId })
|
|
.from(trackerProgress)
|
|
.where(
|
|
inArray(
|
|
trackerProgress.itemId,
|
|
stale.map((item) => item.id)
|
|
)
|
|
);
|
|
const keep = new Set(kept.map((row) => row.id));
|
|
const remove = stale.filter((item) => !keep.has(item.id)).map((item) => item.id);
|
|
if (remove.length) await tx.delete(trackerItems).where(inArray(trackerItems.id, remove));
|
|
for (const item of stale.filter((item) => keep.has(item.id)))
|
|
console.warn(
|
|
`${game.shortName}: kept "${item.name}", which has progress but left the catalog`
|
|
);
|
|
}
|
|
console.log(
|
|
`${game.shortName}: ${weapons.length} weapons, ${game.achievements.length} achievements`
|
|
);
|
|
}
|
|
});
|
|
} finally {
|
|
await client.end();
|
|
}
|