require('dotenv').config(); const path = require('path'); const pool = require('../server/db'); const ADDITIONS = require('./famous-paintings-data'); const { savePaintingImages } = require('./image-fetcher'); const MIN_PAINTINGS = parseInt(process.env.MIN_PAINTINGS || '6', 10); const FETCH_IMAGES = process.argv.includes('--fetch-images'); const IMAGE_DIR = path.resolve(process.env.IMAGE_DIR || path.join(__dirname, '../data/images')); function normalizeTitle(s) { return s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim(); } function titleMatches(existingNorm, title) { const norm = normalizeTitle(title); if (existingNorm.has(norm)) return true; for (const e of existingNorm) { if (e.includes(norm) || norm.includes(e)) return true; } return false; } async function main() { const artistsRes = await pool.query('SELECT id, name FROM artists ORDER BY name'); const artistByName = new Map(artistsRes.rows.map((a) => [a.name, a])); const unknown = new Set(); for (const entry of ADDITIONS) { if (!artistByName.has(entry.artist)) unknown.add(entry.artist); } if (unknown.size) { console.warn('Unknown artist names in data (skipped):', [...unknown].join(', ')); } let inserted = 0; let skipped = 0; let imagesFetched = 0; const newIds = []; for (const artist of artistsRes.rows) { const countRes = await pool.query( 'SELECT COUNT(*)::int AS n FROM paintings WHERE artist_id = $1', [artist.id] ); let count = countRes.rows[0].n; if (count >= MIN_PAINTINGS) continue; const existingRes = await pool.query( 'SELECT title FROM paintings WHERE artist_id = $1', [artist.id] ); const existingNorm = new Set(existingRes.rows.map((r) => normalizeTitle(r.title))); const candidates = ADDITIONS.filter((e) => e.artist === artist.name); for (const entry of candidates) { if (count >= MIN_PAINTINGS) break; if (titleMatches(existingNorm, entry.title)) { skipped += 1; continue; } const wikiTitle = entry.wikipedia_title || entry.title; const sortRes = await pool.query( 'SELECT COALESCE(MAX(sort_order), 0) + 1 AS next FROM paintings WHERE artist_id = $1', [artist.id] ); const sortOrder = sortRes.rows[0].next; const insert = await pool.query( `INSERT INTO paintings (artist_id, title, year, wikipedia_title, sort_order) VALUES ($1, $2, $3, $4, $5) RETURNING id`, [artist.id, entry.title, entry.year ?? null, wikiTitle, sortOrder] ); const paintingId = insert.rows[0].id; existingNorm.add(normalizeTitle(entry.title)); count += 1; inserted += 1; newIds.push({ id: paintingId, artist: artist.name, title: entry.title, wikiTitle }); console.log(`+ ${artist.name}: ${entry.title}${entry.year ? ` (${entry.year})` : ''}`); } } console.log(`\nInserted ${inserted} paintings (${skipped} duplicates skipped)`); if (FETCH_IMAGES && newIds.length) { console.log(`\nFetching images for ${newIds.length} new paintings…`); for (const row of newIds) { try { const base = `${row.artist}_${row.title}`.replace(/[^a-zA-Z0-9_-]/g, '_'); const saved = await savePaintingImages(row.wikiTitle, base, IMAGE_DIR, { artistName: row.artist, paintingTitle: row.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, row.id] ); imagesFetched += 1; console.log(` ✓ image: ${row.artist} — ${row.title}`); } else { console.warn(` ✗ no image: ${row.artist} — ${row.title}`); } } catch (err) { console.warn(` ✗ ${row.artist} — ${row.title}: ${err.message}`); } } console.log(`\nImages fetched: ${imagesFetched}/${newIds.length}`); } else if (newIds.length) { console.log('Run with --fetch-images to download artwork files.'); } const summary = await pool.query(` SELECT COUNT(*) FILTER (WHERE cnt < $1)::int AS below_min, COUNT(*) FILTER (WHERE cnt >= $1)::int AS at_min, MAX(cnt)::int AS max_paintings, ROUND(AVG(cnt), 1) AS avg_paintings FROM ( SELECT COUNT(p.id)::int AS cnt FROM artists a LEFT JOIN paintings p ON p.artist_id = a.id GROUP BY a.id ) t `, [MIN_PAINTINGS]); console.log('\nCatalog summary:', summary.rows[0]); await pool.end(); } main().catch((err) => { console.error(err); process.exit(1); });