Files
Art-gallery/scripts/seed-wikipedia.js
T
Danila Khodjaef 2b4274093e Add seed scripts, proxy Vite on public domain, and seed painting images.
Restore seed-wikipedia.js and seed-catalog-data.js so npm run setup works on a fresh clone. Point nginx at the Vite dev client and allow the public hostname in Vite config.
2026-07-03 13:52:57 +03:00

197 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Seed historical eras, art movements, artists, periods, and flagship paintings.
* Run: npm run seed
* Flags: --force (re-seed), --fetch-images (download portraits and paintings from Wikipedia)
*/
require('dotenv').config();
const path = require('path');
const pool = require('../server/db');
const { ERAS, MOVEMENTS, buildArtists } = require('./seed-catalog-data');
const { savePaintingImages, saveImageForItem } = require('./image-fetcher');
const FORCE = process.argv.includes('--force');
const FETCH_IMAGES = process.argv.includes('--fetch-images');
const IMAGE_DIR = path.resolve(process.env.IMAGE_DIR || path.join(__dirname, '../data/images'));
async function tableCount(table) {
const { rows } = await pool.query(`SELECT COUNT(*)::int AS n FROM ${table}`);
return rows[0].n;
}
async function clearCatalog() {
await pool.query(`
TRUNCATE TABLE
painting_annotations,
painting_influence_sources,
painting_influences,
paintings,
artist_periods,
artists,
art_movements,
historical_eras
RESTART IDENTITY CASCADE
`);
}
async function insertEras() {
const eraIds = new Map();
for (const era of ERAS) {
const { rows } = await pool.query(
`INSERT INTO historical_eras (name, start_year, end_year, start_definite, end_definite, description, sort_order)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id`,
[era.name, era.start_year, era.end_year, era.start_definite, era.end_definite, era.description, era.sort_order]
);
eraIds.set(era.name, rows[0].id);
}
return eraIds;
}
async function insertMovements(eraIds) {
const movementIds = new Map();
for (const mov of MOVEMENTS) {
const eraId = eraIds.get(mov.era) ?? null;
const { rows } = await pool.query(
`INSERT INTO art_movements (name, start_year, end_year, era_id, description, color)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id`,
[mov.name, mov.start_year, mov.end_year, eraId, mov.description, mov.color]
);
movementIds.set(mov.name, rows[0].id);
}
return movementIds;
}
function periodName(artist) {
if (artist.birth_year != null && artist.death_year != null) {
return `${artist.birth_year}${artist.death_year}`;
}
return 'Main period';
}
async function insertArtists(movementIds, artists) {
let inserted = 0;
let paintings = 0;
let skippedPaintings = 0;
let portraitsFetched = 0;
let imagesFetched = 0;
for (const artist of artists) {
const movementId = movementIds.get(artist.movement);
if (!movementId) {
console.warn(` Unknown movement "${artist.movement}" for ${artist.name} — skipped`);
continue;
}
const artistRes = await pool.query(
`INSERT INTO artists (name, birth_year, death_year, movement_id, wikipedia_title, century)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id`,
[artist.name, artist.birth_year, artist.death_year, movementId, artist.wikipedia_title, artist.century]
);
const artistId = artistRes.rows[0].id;
inserted += 1;
const periodRes = await pool.query(
`INSERT INTO artist_periods (artist_id, name, start_year, end_year, sort_order)
VALUES ($1, $2, $3, $4, 0)
RETURNING id`,
[artistId, periodName(artist), artist.birth_year, artist.death_year]
);
const periodId = periodRes.rows[0].id;
if (FETCH_IMAGES) {
try {
const portraitBase = artist.name.replace(/[^a-zA-Z0-9_-]/g, '_');
const saved = await saveImageForItem(artist.wikipedia_title, 'portraits', portraitBase, IMAGE_DIR);
if (saved.path) {
await pool.query('UPDATE artists SET portrait_path = $1 WHERE id = $2', [saved.path, artistId]);
portraitsFetched += 1;
}
} catch (err) {
console.warn(` portrait: ${artist.name}: ${err.message}`);
}
}
if (!artist.painting?.title) {
skippedPaintings += 1;
console.warn(` no flagship painting for ${artist.name}`);
continue;
}
const wikiTitle = artist.painting.wikipedia_title || artist.painting.title;
const paintingRes = await pool.query(
`INSERT INTO paintings (artist_id, period_id, title, year, wikipedia_title, sort_order)
VALUES ($1, $2, $3, $4, $5, 0)
RETURNING id`,
[artistId, periodId, artist.painting.title, artist.painting.year ?? null, wikiTitle]
);
const paintingId = paintingRes.rows[0].id;
paintings += 1;
if (FETCH_IMAGES) {
try {
const base = `${artist.name}_${artist.painting.title}`.replace(/[^a-zA-Z0-9_-]/g, '_');
const saved = await savePaintingImages(wikiTitle, base, IMAGE_DIR, {
artistName: artist.name,
paintingTitle: artist.painting.title,
});
if (saved.imagePath || saved.thumbnailPath) {
await pool.query(
'UPDATE paintings SET image_path = $1, thumbnail_path = $2 WHERE id = $3',
[saved.imagePath, saved.thumbnailPath, paintingId]
);
imagesFetched += 1;
}
} catch (err) {
console.warn(` image: ${artist.name}${artist.painting.title}: ${err.message}`);
}
}
console.log(`+ ${artist.name} (${artist.movement}) — ${artist.painting.title}`);
}
return { inserted, paintings, skippedPaintings, portraitsFetched, imagesFetched };
}
async function main() {
const existing = await tableCount('artists');
if (existing > 0 && !FORCE) {
console.log(`Catalog already has ${existing} artists. Use --force to re-seed.`);
return;
}
if (FORCE && existing > 0) {
console.log('Clearing existing catalog…');
await clearCatalog();
}
const artists = buildArtists();
console.log(`Seeding ${ERAS.length} eras, ${MOVEMENTS.length} movements, ${artists.length} artists…`);
const eraIds = await insertEras();
const movementIds = await insertMovements(eraIds);
const stats = await insertArtists(movementIds, artists);
console.log('\nSeed complete.');
console.log(` Artists: ${stats.inserted}`);
console.log(` Paintings: ${stats.paintings}${stats.skippedPaintings ? ` (${stats.skippedPaintings} without flagship)` : ''}`);
if (FETCH_IMAGES) {
console.log(` Portraits downloaded: ${stats.portraitsFetched}`);
console.log(` Painting images: ${stats.imagesFetched}`);
} else {
console.log(' Run with --fetch-images to download artwork files, or npm run fetch-images later.');
}
console.log('\nNext steps:');
console.log(' npm run fetch-artist-bios');
console.log(' npm run expand-catalog');
console.log(' npm run update-influences');
}
main()
.then(() => pool.end())
.catch((err) => {
console.error('Seed failed:', err.message);
pool.end().finally(() => process.exit(1));
});