This commit is contained in:
84
scripts/seed-catalog.ts
Normal file
84
scripts/seed-catalog.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user