← back to Nineoh Guide
db/ingest-cast.mjs
60 lines
// Ingests FACTUAL cast data only — actor names + character names (both facts,
// not copyrightable). Deliberately does NOT store TVmaze person photos (publicity
// rights + image licensing are gated) or any biography text (we write our own).
// Run: node db/ingest-cast.mjs
import pg from "pg";
const DATABASE_URL =
process.env.DATABASE_URL ?? "postgresql://localhost/nineoh_guide?host=/tmp";
const pool = new pg.Pool({ connectionString: DATABASE_URL });
const show = (await pool.query(`select id from shows where canonical_title='90210'`)).rows[0];
if (!show) {
console.error("Run db/seed.mjs first.");
process.exit(1);
}
const showId = show.id;
// TVmaze show id 578 = 90210 (2008 CW). Cast endpoint returns main cast: person + character.
const res = await fetch("https://api.tvmaze.com/shows/578/cast");
const cast = await res.json();
let people = 0;
let characters = 0;
for (const c of cast) {
const personName = c.person?.name;
const charName = c.character?.name;
if (!personName) continue;
// Upsert person by name (names are facts; no photo, no bio stored).
const p = await pool.query(
`insert into cast_people (name, publicity_risk_level)
select $1, 'editorial-only'
where not exists (select 1 from cast_people where name = $1)
returning id`,
[personName]
);
if (p.rowCount) people++;
if (charName) {
const ch = await pool.query(
`insert into characters (name, show_id)
select $1, $2
where not exists (select 1 from characters where name = $1 and show_id = $2)
returning id`,
[charName, showId]
);
if (ch.rowCount) characters++;
}
}
const totals = await pool.query(
`select (select count(*) from cast_people) as people,
(select count(*) from characters where show_id=$1) as chars`,
[showId]
);
console.log(`inserted this run — people:${people} characters:${characters}`);
console.log(`totals — cast_people:${totals.rows[0].people} characters:${totals.rows[0].chars}`);
console.log("Names only. No photos (image-license gated), no bios (we write our own original).");
await pool.end();