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:
co-authored by
Cursor
parent
df29848d89
commit
4eead54062
+11
-7
@@ -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 artist’s paintings.
|
||||
- **Successors** — artists whose works were influenced by this artist’s paintings.
|
||||
- **Predecessors** — artists whose works (or artist records) influenced this artist’s paintings.
|
||||
- **Successors** — artists whose paintings were influenced by this artist’s 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`
|
||||
|
||||
@@ -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 artist’s 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
@@ -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 movement’s stream row:
|
||||
|
||||
Artist portraits stop wheel/drag propagation so zooming over a face does not fight portrait clicks. Hovering a portrait highlights the artist’s 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)
|
||||
|
||||
|
||||
@@ -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 artist’s 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 work’s 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 artist’s 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
@@ -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 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
|
||||
|
||||
```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 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) |
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user