Some checks failed
Test and deploy staging / staging (push) Has been cancelled
85 lines
3.1 KiB
TypeScript
85 lines
3.1 KiB
TypeScript
import { readFile } from 'node:fs/promises';
|
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
import { and, eq, isNull, sql } from 'drizzle-orm';
|
|
import postgres from 'postgres';
|
|
import { pokemon, games, pokemonGameEntries } from '../src/lib/server/db/schema/catch-em-all';
|
|
|
|
type Catalog = {
|
|
source: { revision: string };
|
|
pokemon: (typeof pokemon.$inferInsert)[];
|
|
games: { sourceId: number; name: string; generation: number }[];
|
|
entries: [number, number, 'catchable' | 'unknown'][];
|
|
};
|
|
if (!process.env.DB_URL) throw new Error('DB_URL is required');
|
|
const catalog: Catalog = JSON.parse(
|
|
await readFile(new URL('../data/pokemon-catalog.json', import.meta.url), 'utf8')
|
|
);
|
|
const client = postgres(process.env.DB_URL, { max: 1 });
|
|
const db = drizzle({ client });
|
|
try {
|
|
await db.transaction(async (tx) => {
|
|
const pokemonIds = new Map<number, number>();
|
|
const gameIds = new Map<number, number>();
|
|
for (const row of catalog.pokemon) {
|
|
const [saved] = await tx
|
|
.insert(pokemon)
|
|
.values(row)
|
|
.onConflictDoUpdate({ target: pokemon.number, set: row })
|
|
.returning();
|
|
pokemonIds.set(saved.number, saved.id);
|
|
}
|
|
for (const row of catalog.games) {
|
|
// Adopt older rows by exact name to preserve existing progress and game IDs.
|
|
const [imported] = await tx.select().from(games).where(eq(games.sourceId, row.sourceId));
|
|
if (!imported) {
|
|
const legacy = await tx
|
|
.select()
|
|
.from(games)
|
|
.where(and(eq(games.name, row.name), isNull(games.sourceId)));
|
|
if (legacy.length > 1) throw new Error(`Ambiguous legacy game: ${row.name}`);
|
|
if (legacy[0])
|
|
await tx.update(games).set({ sourceId: row.sourceId }).where(eq(games.id, legacy[0].id));
|
|
}
|
|
const [saved] = await tx
|
|
.insert(games)
|
|
.values(row)
|
|
.onConflictDoUpdate({ target: games.sourceId, set: row })
|
|
.returning();
|
|
gameIds.set(row.sourceId, saved.id);
|
|
}
|
|
for (let offset = 0; offset < catalog.entries.length; offset += 1000) {
|
|
const rows = catalog.entries
|
|
.slice(offset, offset + 1000)
|
|
.map(([number, version, availability]) => {
|
|
const pokemonId = pokemonIds.get(number),
|
|
gameId = gameIds.get(version);
|
|
if (!pokemonId || !gameId)
|
|
throw new Error(`Unknown catalog reference ${number}/${version}`);
|
|
return {
|
|
pokemonId,
|
|
gameId,
|
|
availability,
|
|
note:
|
|
availability === 'catchable'
|
|
? 'PokéAPI: documented encounter; conditions, location, or a specific form may be required.'
|
|
: 'PokéAPI: Pokédex or game-index record only; catch, evolution, gift, trade, transfer, and event availability is not confirmed.'
|
|
};
|
|
});
|
|
await tx
|
|
.insert(pokemonGameEntries)
|
|
.values(rows)
|
|
.onConflictDoUpdate({
|
|
target: [pokemonGameEntries.pokemonId, pokemonGameEntries.gameId],
|
|
set: { availability: sql`excluded.availability`, note: sql`excluded.note` },
|
|
// Preserve hand-maintained availability and notes.
|
|
setWhere: sql`${pokemonGameEntries.note} like 'PokéAPI:%'`
|
|
});
|
|
}
|
|
});
|
|
console.log(
|
|
`Seeded ${catalog.pokemon.length} species, ${catalog.games.length} versions, ${catalog.entries.length} entries from ${catalog.source.revision}`
|
|
);
|
|
} finally {
|
|
await client.end();
|
|
}
|