Add production deployment, photorealistic movement walls, and fix duplicate influence links.

Configure hosting for gallery.mysuperlab.netcraze.pro and LAN access, enhance movement gallery textures with period-appropriate painted materials, dedupe influenced-by API responses via painting_influence_sources, and refresh several painting image files.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-21 23:00:13 +03:00
co-authored by Cursor
parent df29848d89
commit 4eead54062
161 changed files with 668 additions and 187 deletions
+11 -7
View File
@@ -1,9 +1,11 @@
# Art Gallery — REST API
Base URL in development:
Base URL:
- **Direct:** `http://localhost:3001`
- **Via Vite proxy:** `http://localhost:5173` (same paths)
- **Production (public):** `http://gallery.mysuperlab.netcraze.pro`
- **Production (LAN):** `http://192.168.10.70:3520`
- **Development (direct):** `http://localhost:3520` or `http://localhost:3001` depending on `PORT` in `.env`
- **Development (Vite proxy):** `http://localhost:5173` (same paths)
All JSON responses use `Content-Type: application/json`. Errors return `{ "error": "message" }` with an appropriate HTTP status.
@@ -262,12 +264,12 @@ Max decoded size 15 MB (JSON body limit 20 MB on the server). **Response** — s
## `GET /api/artists/:id/navigation`
Related artists for hall-to-hall navigation at the exit doorway. Derived from `painting_influences`:
Related artists for hall-to-hall navigation at the exit doorway. Derived from **`painting_influence_sources`**:
- **Predecessors** — artists whose works influenced this artists paintings.
- **Successors** — artists whose works were influenced by this artists paintings.
- **Predecessors** — artists whose works (or artist records) influenced this artists paintings.
- **Successors** — artists whose paintings were influenced by this artists works (via painting sources), or whose paintings cite this artist as a source (via artist sources).
Both lists are grouped by art movement and exclude the current artist.
Both lists are grouped by art movement and exclude the current artist. Each artist appears once even when multiple influence edges exist.
**Response**
@@ -357,6 +359,8 @@ Painting detail with influence graph neighbours.
`painting.has_influence_links` matches the flag on `GET /api/artists/:id` paintings (see above).
Both **`influencedBy`** and **`influenced`** are read from **`painting_influence_sources`** only (one row per edge). Painting-type sources appear in *Influenced By* when they are predecessors; in *Influenced* when this painting is the source of a later work. Artist and movement sources appear only in *Influenced By*. The legacy `painting_influences` table is not merged into these responses — it mirrors painting edges for scripts only.
---
## `GET /api/paintings/:id/image`
+2 -2
View File
@@ -147,7 +147,7 @@ Directed edges: *this painting* was influenced by *that painting*.
Unique constraint on `(painting_id, influenced_by_painting_id)`.
Used by the 3D gallery exit panel: predecessors are artists of `influenced_by_painting_id` works; successors are artists of paintings influenced by this artists works (see [API.md](API.md#get-apiartistsidnavigation)). Legacy table; new polymorphic links live in `painting_influence_sources`.
**Legacy mirror table.** `npm run update-influences` still inserts painting-to-painting rows here when curating data. The same edges are copied into `painting_influence_sources`. The **REST API does not read this table** for painting detail or hall navigation — use `painting_influence_sources` as the source of truth for display.
### `painting_influence_sources`
@@ -175,7 +175,7 @@ Polymorphic influence links: *this painting* was influenced by a **painting**, *
Unique index on `(painting_id, source_type, source_painting_id, source_artist_id, source_movement_id)` with COALESCE for null FKs.
Used by painting detail API (`influencedBy`). Painting-type rows also feed `has_influence_links` and hall navigation (with legacy `painting_influences`).
**Canonical influence store.** Used by all API influence queries: painting detail (`influencedBy`, `influenced`), `has_influence_links`, and artist hall navigation (predecessors / successors). Legacy `painting_influences` rows are backfilled here on migration; new curated painting edges are written to both tables by `update-influences`.
## Indexes
+13 -7
View File
@@ -58,9 +58,11 @@ Gallery/
│ ├── famous-paintings-data.js
│ ├── fetch-missing-images.js
│ ├── find-duplicate-paintings.js
│ ├── audit-influence-duplicates.js
│ └── image-fetcher.js
├── data/images/ # Local portraits and paintings (+ thumbs/)
├── db/ # SQL schema and migrations (when present)
├── deploy/ # Production nginx + systemd examples
├── Documentation/ # This folder
└── .env # DB and port config (not committed)
```
@@ -70,16 +72,19 @@ Gallery/
### Production-style (single process)
```bash
npm run server # http://localhost:3001
npm run start:prod # build client + serve on PORT (default 3520)
# or: npm run build && npm run start
```
Serves `/api/*`, `/images/*`, and the built SPA from `client/dist` if it exists.
**Deployed URLs:** public http://gallery.mysuperlab.netcraze.pro · LAN http://192.168.10.70:3520 — see [setup.md](setup.md#production-deployment).
### Development (two processes)
```bash
npm run dev:server # API on :3001
npm run dev:client # Vite on :5173, proxies /api and /images
npm run dev:server # API on PORT from .env (3520 production, 3001 typical dev)
npm run dev:client # Vite on :5173, proxies /api and /images to PORT
```
Use the Vite URL during frontend work for HMR.
@@ -181,7 +186,7 @@ Each artist appears as a **portrait circle** on their movements stream row:
Artist portraits stop wheel/drag propagation so zooming over a face does not fight portrait clicks. Hovering a portrait highlights the artists lifespan on the era bar and brightens their segment on the movement stream.
**Note:** Movement lineage is **frontend curation** for layout and labels — it is not stored in PostgreSQL. Painting influence links (`painting_influence_sources`, plus legacy `painting_influences` for hall navigation) are separate and drive the 3D exit picker and detail panels.
**Note:** Movement lineage is **frontend curation** for layout and labels — it is not stored in PostgreSQL. Painting influence links live in **`painting_influence_sources`** (paintings, artists, or movements as sources). The API reads that table for detail panels, hall navigation, and `has_influence_links`. The legacy **`painting_influences`** table is still written in parallel when curators add painting-to-painting edges but is not queried for display.
## Virtual gallery (3D halls)
@@ -222,7 +227,7 @@ Each artist has **exactly one hall**. The hall is a rectangular room sized to fi
| Click painting | Open detail view |
| Exit doorway / `E` / **Exit →** header button | Open path picker |
Predecessors and successors come from the **painting influence graph** (`painting_influences` → other artists). Empty lists mean no influence edges are recorded yet for that artist — run `npm run update-influences` or extend seed data.
Predecessors and successors come from **`painting_influence_sources`** (painting and artist sources). Empty lists mean no influence edges are recorded yet for that artist — run `npm run update-influences` or extend `art-influences-data.js`.
### Movement galleries
@@ -236,7 +241,8 @@ Enter from the home page by clicking a **movement name** on the movement flow (`
| Wall order | Along each side wall: **later works on the left**, **earlier on the right** (same convention as artist halls) |
| Frame captions | **Year · artist** label below each frame |
| Period interior | Each of the 26 seeded movements maps to a unique style in `movement-interior-styles.ts` (Italian palazzo, Baroque palace, NYC loft, white cube, etc.) |
| Textures | Hi-res procedural wall/floor/ceiling maps with normal maps (`galleryProceduralTextures.ts`) |
| Wall materials | Hi-res **procedural textures** with normal maps (`galleryProceduralTextures.ts`): real-world stone, marble, wood panelling, brick, velvet, stucco — plus **single-colour painted walls** (`painted-lime`, `painted-oil-matte`, `painted-oil-satin`, `painted-emulsion`, `painted-flat`) tinted per movement for Renaissance salons through modern white cubes |
| Textures | Wall/floor/ceiling maps applied via `useTexturedMaterial`; movement **tints** drive painted-wall hue |
| Windows | **Side walls only** — placed in gaps between frames (high on the wall, no overlap with paintings); style matches the movement era |
| Lighting | Daylight from windows + ceiling track lights + ambient/sun fill |
| Back wall | **Exit double doors****Wing navigator** (jump to any wing) or **Exit to Timeline** |
@@ -301,7 +307,7 @@ When **Debug mode** is enabled from the home header, painting detail and artist
- **Movement galleries** complement artist halls: full movement corpus in period-themed wings, entered from the flow diagram.
- **Influence-based hall links** connect artists through documented painting relationships, grouped by movement at the exit.
- **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering.
- **Influence data** is stored as directed links from paintings to sources (another painting, an artist, or a movement), with optional period fields and citation metadata (source author, quote, URL).
- **Influence data** is stored in **`painting_influence_sources`** (directed links from paintings to source paintings, artists, or movements), with optional period fields and citation metadata. Legacy `painting_influences` mirrors painting-to-painting edges for scripts only.
## Developer tools (image audit)
+15 -5
View File
@@ -58,9 +58,9 @@ migrate → seed → fetch-artist-bios → expand-catalog → update-influences
2. For each curated **artist**:
- Creates **artist periods** and **paintings**.
- May download portraits and painting images (depending on seed script version).
3. Writes **painting_influences** edges from curated scholarship references.
3. Writes **painting_influences** edges from curated scholarship references (mirrored into `painting_influence_sources` when you run `npm run migrate:influence-sources` and `npm run update-influences`).
Those influence edges power **3D hall navigation**: predecessors and successors at each artists exit doorway are computed from this table (see `GET /api/artists/:id/navigation` in [API.md](API.md)).
Those influence edges power **3D hall navigation** and painting detail panels via **`painting_influence_sources`** (see `GET /api/artists/:id/navigation` and `GET /api/paintings/:id` in [API.md](API.md)).
Artists are grouped by movement and century; the seed list targets at most ~100 artists per century.
@@ -117,14 +117,24 @@ Renaissance and medieval masters with large museum catalog dumps (e.g. Raphael,
Directed influence links are stored in **`painting_influence_sources`**. Each row connects a painting to a **source** of type `painting`, `artist`, or `movement`, with optional period context (e.g. influence during the works creation year).
The legacy **`painting_influences`** table (painting-to-painting only) is still written for hall navigation compatibility and is backfilled into `painting_influence_sources` on migration.
The legacy **`painting_influences`** table (painting-to-painting only) is still written alongside sources when running `npm run update-influences` — it keeps script compatibility and matches the backfill migration. **The API reads only `painting_influence_sources`**, so each edge appears once in the UI.
Influence data drives:
- **Painting detail** — *Influenced By* (left) and *Influenced* (right) panels: painting thumbnails, artist portraits, or movement colour swatches, plus notes, aspects, period labels, and citations
- **3D hall exit** — predecessor and successor artists grouped by movement (painting edges only)
- **3D hall exit** — predecessor and successor artists grouped by movement (from painting and artist sources)
- **3D gallery lamps** — golden picture light above frames with any influence edge (`has_influence_links` on painting API responses)
### Audit duplicate influence links
Painting-to-painting edges exist in both tables by design. To confirm the database has no stray duplicates and that the API model is clean:
```bash
npm run audit-influence-duplicates
```
Reports: edges present in both tables, duplicate rows within either table (should be 0), and legacy-only / sources-only mismatches. If *Influenced* ever shows the same successor twice, restart the server after pulling API fixes — responses must not union legacy and sources tables.
### One-time migration
```bash
@@ -310,7 +320,7 @@ These live in `client/public/` (and `client/dist/` after build).
3. Run `npm run fetch-artist-bios` for the new artists biography.
4. Add entries to `famous-paintings-data.js` and run `npm run expand-catalog` if needed.
5. Run `npm run fetch-images -- --artist="…"` or rely on preload / on-demand sync.
6. Add influence rows to `painting_influences` with citation fields where possible.
6. Add influence rows via `npm run update-influences` / `art-influences-data.js` (writes both `painting_influence_sources` and legacy painting edges).
## Image fetcher overrides
+59 -7
View File
@@ -21,7 +21,10 @@ cp .env.example .env
| `DB_USER` | Database user |
| `DB_PASSWORD` | Database password |
| `DB_NAME` | Database name (`Gallery`) |
| `PORT` | API listen port (default `3001`) |
| `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:
@@ -80,19 +83,65 @@ Image fetch can take hours if you run it for the entire catalog. The first line
| Command | Purpose |
|---------|---------|
| `npm run server` | API + static SPA on `PORT` |
| `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 `server` |
| `npm run dev` | Alias for `start` |
**Production frontend:** build the client, then start the server:
```bash
cd client && npm run build && cd ..
npm run server
npm run build
# or: cd client && npm run build && cd ..
npm run start
```
Open http://localhost:3001 (or your configured `PORT`).
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:
```env
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 machines 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
```bash
npm install
cd client && npm install && cd ..
npm run start:prod
```
Or use the systemd unit in [`deploy/gallery.service`](../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`](../deploy/nginx-gallery.conf).
The proxy forwards to `127.0.0.1:3520`. 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
@@ -123,6 +172,7 @@ Open http://localhost:3001 (or your configured `PORT`).
| `npm run update-painting-annotations -- --wikipedia` | ↑ | Add intro sentences from each works 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) |
@@ -139,6 +189,7 @@ These are checked in and maintained:
- `influence-discovery.js` + `influence-resolver.js` — web discovery and polymorphic source resolution
- `fetch-missing-images.js` — batch image backfill
- `find-duplicate-paintings.js` — duplicate catalog audit
- `audit-influence-duplicates.js` — influence graph duplicate / mirror audit
- `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 detail
@@ -171,8 +222,9 @@ After clone: copy `.env.example` → `.env`, install dependencies, run `npm run
| 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 `painting_influences` edges for artist | `npm run update-influences` |
| 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 |