Load the home-page catalog once with a lightweight artists API, batch pan/zoom updates per frame, use dynamic year labels, and speed up movement-flow zoom. Add sync-image-paths and fetch-artist-images plus docs for the post-seed pipeline.
17 KiB
Art Gallery — setup and operations
Prerequisites
- Node.js 20+
- PostgreSQL reachable from the app host
- Network access to Wikipedia / Wikimedia (for biographies, seeding, and on-demand image fetch)
Environment
Copy the example file and fill in credentials:
cp .env.example .env
| Variable | Description |
|---|---|
DB_HOST |
PostgreSQL host |
DB_PORT |
Port (default 5432) |
DB_USER |
Database user |
DB_PASSWORD |
Database password |
DB_NAME |
Database name (Gallery) |
PORT |
API listen port (default 3001; production uses 3520) |
HOST |
Bind address (default 0.0.0.0 — required for LAN access) |
PUBLIC_URL |
Optional public URL shown at startup (e.g. http://gallery.mysuperlab.netcraze.pro) |
TRUST_PROXY |
Set to true when behind nginx/reverse proxy (honours X-Forwarded-*) |
IMAGE_DIR |
Root for cached images (default ./data/images) |
Optional script tuning:
| Variable | Used by | Description |
|---|---|---|
MIN_PAINTINGS |
expand-catalog |
Minimum paintings per artist (default 6) |
FETCH_MAX_WAIT_SEC |
fetch-images |
Max seconds per painting in batch runs (default 10) |
.env is git-ignored; never commit passwords.
Install
npm install
cd client && npm install && cd ..
Database bootstrap
One-shot setup (migrate + seed):
npm run setup
Or step by step:
npm run migrate # db/schema.sql + db/migrate-*.sql via server/migrate.js
npm run seed # eras, movements, artists, flagship paintings (one per artist)
npm run migrate is safe to re-run on existing databases (uses IF NOT EXISTS / ADD COLUMN IF NOT EXISTS).
If migration fails with permission errors, grant schema rights to the app user first (see DB_structure.md).
Recommended post-seed steps
After a fresh seed, run these to match a fully populated local install:
npm run sync-image-paths # import paintings from data/images/paintings/ (clone with image files)
npm run fetch-artist-images # link local portraits or download from Wikipedia
npm run fetch-artist-bios # bio_short / bio_full from Wikipedia
npm run expand-catalog # famous works for artists below MIN_PAINTINGS
npm run update-influences # painting influence graph for detail view + hall exits
npm run migrate:checkup-flags # optional: review/fixed flags for Checkup page (paintings)
npm run migrate:artist-checkup-flags # optional: same flags for artist portraits (bio debug)
npm run migrate:painting-annotations # optional: art-history notes table
npm run update-painting-annotations # optional: load curated notes (+ --wikipedia for Wikipedia intros)
npm run fetch-images -- --limit=50 # random sample; 10s max per painting (default)
npm run fetch-images -- --limit=50 --max-wait=120 # same batch size, longer lookup per work
cd client && npm run build && cd ..
Order matters: sync-image-paths should run when the repo already contains painting files under data/images/paintings/ but the database only has one flagship work per artist (typical after npm run setup on a clone). update-influences is required for Influenced By / Influenced panels and golden lamps in the 3D hall — seed does not insert influence edges.
Image fetch can take hours if you run it for the entire catalog. The first line of each run reports Missing local files: N. Use npm run fetch-images -- --limit=N for random batches (10s per painting by default), --artist="…" for one artist in catalog order, or on-demand resolution when viewing a painting in the detail view.
Run
| Command | Purpose |
|---|---|
npm run build |
Build production SPA into client/dist |
npm run start |
API + static SPA on HOST:PORT |
npm run start:prod |
Build client, then start server |
npm run server |
Alias for start |
npm run dev:server |
API with nodemon reload |
npm run dev:client |
Vite dev server on :5173 |
npm run dev |
Alias for start |
Production frontend: build the client, then start the server:
npm run build
# or: cd client && npm run build && cd ..
npm run start
Open http://localhost:3520 (or your configured HOST/PORT).
Production deployment
This install is intended to run at:
| Access | URL |
|---|---|
| Public (reverse proxy) | http://gallery.mysuperlab.netcraze.pro |
| LAN direct | http://192.168.10.70:3520 |
1. Configure .env
Copy .env.example → .env and set at least:
PORT=3520
HOST=0.0.0.0
PUBLIC_URL=http://gallery.mysuperlab.netcraze.pro
TRUST_PROXY=true
HOST=0.0.0.0 lets the app accept connections on the machine’s LAN IP (192.168.10.70). The client uses relative /api and /images paths, so no frontend URL changes are needed.
2. Build and start
npm install
cd client && npm install && cd ..
npm run start:prod
Or use the systemd unit in deploy/gallery.service (adjust User, WorkingDirectory, and EnvironmentFile).
3. Reverse proxy (public domain)
Point gallery.mysuperlab.netcraze.pro at the host running the app. Example nginx config: deploy/nginx-gallery.conf.
Development (default in repo): nginx forwards to Vite on 127.0.0.1:5173. Run both npm run dev:server (API on 3520) and npm run dev:client (5173). Vite proxies /api and /images to the API. If either process stops, the public hostname may return 503 (reverse proxy cannot reach upstream).
Production (built SPA): change nginx proxy_pass to http://127.0.0.1:3520 after npm run build and npm run start — Node serves client/dist and the API on one port. Prefer the systemd unit in deploy/gallery.service so the process restarts automatically.
Keep TRUST_PROXY=true in .env so Express sees the correct client IP and scheme.
4. Firewall
Allow inbound TCP 3520 on the gallery host if clients reach it directly on the LAN (192.168.10.70:3520). The public hostname only needs 80/443 on the reverse-proxy host.
Maintenance scripts
| Command | Script | Purpose |
|---|---|---|
npm run seed |
scripts/seed-wikipedia.js |
Reload curated Wikipedia data |
npm run fetch-artist-bios |
scripts/fetch-artist-bios.js |
Wikipedia lead sections → bio_short / bio_full |
npm run fetch-artist-bios -- --force |
↑ | Refresh bios even when already set |
npm run expand-catalog |
scripts/expand-paintings.js |
Insert famous works from famous-paintings-data.js |
npm run expand-catalog -- --fetch-images |
↑ | Also download images for new rows (slow) |
npm run fetch-images |
scripts/fetch-missing-images.js |
Search Wikipedia, Commons, museums for missing files |
npm run search-missing-paintings |
↑ (alias) | Same as fetch-images |
npm run fetch-images -- --artist="Name" |
↑ | Limit to one artist (catalog order) |
npm run fetch-images -- --limit=50 |
↑ | Random sample of N missing paintings |
npm run fetch-images -- --limit=250 |
↑ | Larger random batch (processes min(N, missing count)) |
npm run fetch-images -- --limit=50 --max-wait=120 |
↑ | Random batch; 120s cap per painting |
npm run fetch-images -- --discover-only --limit=20 |
↑ | Fix wikipedia_title via search only |
npm run regenerate-thumbnails |
scripts/regenerate-thumbnails.js |
Rebuild all thumbs from full local files |
npm run audit-painting-images |
scripts/audit-painting-images.js |
List thumb/full aspect-ratio mismatches |
npm run migrate:thumbnails |
db/migrate-thumbnails.sql |
Add thumbnail columns |
npm run fetch-artist-images |
scripts/fetch-artist-images.js |
Download portraits or link existing files under data/images/portraits/ |
npm run fetch-artist-images -- --force |
↑ | Re-fetch even when portrait_path is set |
npm run sync-image-paths |
scripts/sync-image-paths.js |
Link image_path / thumbnail_path and import missing painting rows from disk |
npm run sync-image-paths -- --dry-run |
↑ | Report only, no DB writes |
npm run migrate:influence-sources |
scripts/migrate-influence-sources.js |
Create painting_influence_sources + backfill legacy edges |
npm run migrate:checkup-flags |
scripts/migrate-checkup-flags.js |
Add checkup_checked / checkup_fixed on paintings |
npm run migrate:artist-checkup-flags |
scripts/migrate-artist-checkup-flags.js |
Add checkup_checked / checkup_fixed on artists (bio debug) |
npm run migrate:painting-annotations |
scripts/migrate-painting-annotations.js |
Create painting_annotations table |
npm run migrate:artist-palette |
scripts/migrate-artist-palette.js |
Add palette_metadata JSONB on artists |
npm run import-painter-palette |
scripts/import-painter-palette.js |
Enrich artists + influence links from Inputs/PainterPalette.csv |
npm run analyze-painter-palette |
scripts/analyze-painter-palette.js |
Report CSV ↔ gallery artist name matches |
npm run export-paintings |
scripts/export-paintings-csv.js |
Write Output/paintings.csv (artist, painting, year) |
npm run update-painting-annotations |
scripts/update-painting-annotations.js |
Load curated notes from painting-annotations-data.js |
npm run update-painting-annotations -- --wikipedia |
↑ | Add intro sentences from each work’s Wikipedia page |
npm run update-painting-annotations -- --wikipedia --wiki-delay=3000 |
↑ | Slower Wikipedia pass when rate-limited (429) |
npm run find-duplicates |
scripts/find-duplicate-paintings.js |
Report duplicate and near-duplicate painting rows |
npm run audit-influence-duplicates |
scripts/audit-influence-duplicates.js |
Report mirrored legacy/sources edges and duplicate influence rows |
npm run update-influences |
scripts/update-influences.js |
Insert influence links (painting / artist / movement) from art-influences-data.js |
npm run update-influences -- --fetch-images |
↑ | Also download images for newly created works |
npm run update-influences -- --discover |
↑ | Curated pass + web discovery (Wikipedia, Wikidata, Met, art-history sites) |
npm run discover-influences |
update-influences.js --discover-only |
Discovery pass only |
Scripts in the repository
These are checked in and maintained:
seed-wikipedia.js,seed-catalog-data.js— initial catalog (npm run seed)image-fetcher.js— Wikimedia / museum image resolutionfetch-artist-images.js— artist portrait download / disk linkingsync-image-paths.js— import paintings and align paths fromdata/images/paintings/fetch-artist-bios.js— artist biographiesexpand-paintings.js+famous-paintings-data.js— catalog expansionupdate-influences.js+art-influences-data.js— influence graph (paintings, artists, movements)influence-discovery.js+influence-resolver.js— web discovery and polymorphic source resolutionfetch-missing-images.js— batch image backfillfind-duplicate-paintings.js— duplicate catalog auditaudit-influence-duplicates.js— influence graph duplicate / mirror auditpainter-palette-lib.js,import-painter-palette.js,migrate-artist-palette.js,analyze-painter-palette.js— PainterPalette CSV integrationexport-paintings-csv.js— catalog CSV export toOutput/migrate-checkup-flags.js— checkup workflow columns (paintings)migrate-artist-checkup-flags.js— checkup workflow columns (artist portraits)migrate-painting-annotations.js,update-painting-annotations.js,painting-annotations-data.js— art-history notes on painting detailregenerate-thumbnails.js,audit-painting-images.js
See data-and-images.md for pipeline details and override maps.
Remote repository
Gitea: Danilka/Art-gallery
git clone https://gitea.mysuperlab.netcraze.pro/Danilka/Art-gallery.git
After clone: copy .env.example → .env, install dependencies, run npm run setup against your Postgres instance, then the post-seed steps above.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Empty timeline | DB not seeded | npm run seed |
| 503 on public URL | Vite or API not running behind nginx | Start npm run dev:server + npm run dev:client, or switch nginx to production :3520 |
500 on all /api/* |
Wrong .env or Postgres down |
Check connection, logs |
| “Biographical information not yet available” | Bios not fetched | npm run fetch-artist-bios |
| Placeholder portraits on timeline | portrait_path not set |
npm run fetch-artist-images |
| Artist hall has only 1–2 paintings | DB not expanded / disk not imported | npm run sync-image-paths then npm run expand-catalog; extend famous-paintings-data.js |
| Movement flow shows “Loading art history…” repeatedly | Stale client refetching on every pan/zoom | Pull latest client; timeline loads catalog once — hard-refresh |
| Movement flow slow on first load | Large artist payload | Client uses GET /api/artists?timeline=1 (no full bios); rebuild client |
| Movement flow zoom sluggish / dead over portraits | Stale client build | Rebuild client — wheel uses capture listener + interaction-mode rendering; hard-refresh |
| Timeline year labels overlap when zoomed out | Stale client | Rebuild client — chooseTimelineTickInterval() adapts step to span and bar width |
| Black frames / canvas covers in 3D gallery | No local image for painting | npm run fetch-images -- --limit=50 or --artist="…"; then POST …/preload-images |
Many ⏱ timeout lines in fetch batch |
Default 10s cap too short for hard works | --max-wait=120 or raise FETCH_MAX_WAIT_SEC |
| White/grey flicker on frames | Texture loading or z-fighting with wall | Rebuild client (cd client && npm run build); ensure latest VirtualGallery.tsx |
| Wrong painting in 3D gallery frame | Stale or mismatched thumbnail file | npm run regenerate-thumbnails; gallery prefers full image_path |
| Wrong painting image (fetch) | Bad museum / search match on first download | Add entry to DIRECT_IMAGE_OVERRIDES in scripts/image-fetcher.js, re-fetch file |
| Empty exit navigation lists | No influence edges for artist | npm run update-influences; check painting_influence_sources |
| Empty Influenced By / Influenced on painting detail | No edges for that work | npm run migrate:influence-sources then npm run update-influences; extend art-influences-data.js or run --discover |
| Same work listed twice under Influenced | Stale server merging legacy + sources tables | Restart server; API reads painting_influence_sources only — run npm run audit-influence-duplicates to verify DB |
| No golden lamps above frames in 3D hall | Stale API process or no influence edges | Restart server after API changes; run npm run update-influences |
| Default Vite page instead of gallery | client/dist missing or stale |
cd client && npm run build |
| Permission denied creating tables | gallery user lacks CREATE |
Run admin grants, then migrate |
| Wikipedia API rate limit during fetch | Too many requests in a row | Wait and re-run; scripts retry with backoff |
| Checkup Reviewed toggle returns 404 | Stale server process missing new routes | Restart npm run dev after pulling API changes |
| Debug More / Clear / Upload / Remove entry returns 404 | Stale server process | Restart npm run start or npm run dev:server; routes in server/index.js + server/image-service.js |
| Debug Remove entry — button stuck or missing on next painting | Stale client build | cd client && npm run build; hard-refresh — detail view remounts per painting id |
| Debug Upload returns 413 Payload Too Large | Base64 JSON exceeds body limit | Server allows 20 MB JSON / 15 MB decoded image; compress file or resize before upload |
| No art-history notes on painting detail | Annotations not migrated or loaded | npm run migrate:painting-annotations then npm run update-painting-annotations |
Fix it fails with read ECONNRESET |
Remote host dropped connection | Restart server; client sends searchUrl / source; retry or use Commons URL in overrides |
| Fixed image not shown in 3D gallery | Stale gallery session or cached texture | Rebuild client; fix updates session + ?v= revision — use Back to Gallery (not browser back) |
| Frame still black after Checked | Gallery session not synced | Re-enter hall or toggle debug Checked from detail with gallery open behind overlay |
| Duplicate works in gallery / timeline | Double import or variant Wikipedia titles | npm run find-duplicates; merge or delete spare rows manually |
Failed to load movement gallery / Cannot GET /api/movements/:id/gallery |
Stale server process missing route | Restart npm run dev or npm run dev:server after pulling API changes |
| Movement gallery shows generic cream walls | Stale client build | cd client && npm run build; hard-refresh browser |
| Windows overlap paintings in movement wing | Stale client | Rebuild client — windows are placed only on side walls in gaps between frames |
| Influence thumbnails cropped on painting detail | Stale client build | npm run build — panels use object-fit: contain for full image |