# Art Gallery — REST API Base URL in development: - **Direct:** `http://localhost:3001` - **Via Vite proxy:** `http://localhost:5173` (same paths) All JSON responses use `Content-Type: application/json`. Errors return `{ "error": "message" }` with an appropriate HTTP status. Static images are served at `/images/` from `IMAGE_DIR`. --- ## `GET /api/bounds` Returns the overall timeline year range used to initialise the zoomable timeline. **Response** ```json { "min_year": -800, "max_year": 2100 } ``` `min_year` is the earliest art movement start; `max_year` is the latest of era ends, movement ends, and artist death years. --- ## `GET /api/timeline` Historical eras and art movements overlapping a year window. **Query** | Param | Type | Default | Description | |-------|------|---------|-------------| | `start` | int | -3000 | Window start year | | `end` | int | 2100 | Window end year | **Response** ```json { "eras": [ { "id": 1, "name": "Renaissance", "start_year": 1300, "end_year": 1600, "start_definite": false, "end_definite": false, "description": "...", "sort_order": 3 } ], "movements": [ { "id": 5, "name": "Impressionism", "start_year": 1860, "end_year": 1890, "era_id": 6, "era_name": "Modern", "color": "#87CEEB", ... } ] } ``` Movements are filtered to those with at least one artist active in the requested range. --- ## `GET /api/artists` Artists for timeline portraits and movement bands. **Query** | Param | Type | Description | |-------|------|-------------| | `start` | int | Only artists alive after this year | | `end` | int | Only artists born before this year | | `movement_id` | int | Filter by movement | **Response** — array of artist objects with joined `movement_name` and `movement_color`. --- ## `GET /api/movements/:id/artists` Artists belonging to a single movement. **Response** — array of `{ id, name, birth_year, death_year, portrait_path, bio_short }`. --- ## `GET /api/artists/:id` Full artist profile for the bio page and 3D gallery entry. **Response** ```json { "artist": { "id": 1, "name": "...", "birth_year": 1840, "death_year": 1926, "movement_name": "...", "portrait_path": "portraits/Claude_Monet.jpg", "bio_short": "First two sentences from Wikipedia…", "bio_full": "Full Wikipedia lead section…", "wikipedia_title": "Claude Monet" }, "periods": [ { "id": 1, "name": "Milan Period", "start_year": 1482, "end_year": 1499, ... } ], "paintings": [ { "id": 10, "title": "...", "year": 1498, "image_path": "...", "thumbnail_path": "...", "wikipedia_title": "...", "has_influence_links": true, ... } ] } ``` Each painting includes `has_influence_links` (boolean) — `true` when the work appears in any `painting_influences` row as source or target. The 3D gallery uses this to show a golden lamp above the frame. Populate biographies with `npm run fetch-artist-bios` (see [data-and-images.md](data-and-images.md)). --- ## `GET /api/artists/:id/navigation` Related artists for hall-to-hall navigation at the exit doorway. Derived from `painting_influences`: - **Predecessors** — artists whose works influenced this artist’s paintings. - **Successors** — artists whose works were influenced by this artist’s paintings. Both lists are grouped by art movement and exclude the current artist. **Response** ```json { "predecessors": [ { "movement_id": 4, "movement_name": "Early Renaissance", "movement_color": "#B8860B", "artists": [ { "id": 12, "name": "Domenico Ghirlandaio", "birth_year": 1448, "death_year": 1494, "portrait_path": "portraits/Domenico_Ghirlandaio.jpg" } ] } ], "successors": [] } ``` --- ## `POST /api/artists/:id/preload-images` Fast local scan: links paintings to files already on disk. Does **not** download from the internet (safe to call before opening the 3D gallery). **Response** ```json { "fetched": 12, "total": 15 } ``` `fetched` counts paintings with a resolvable local image after the scan. --- ## `GET /api/paintings/:id` Painting detail with influence graph neighbours. **Response** ```json { "painting": { "id": 10, "title": "...", "artist_name": "...", "image_path": "...", "has_influence_links": true, ... }, "influencedBy": [ { "id": 3, "title": "...", "artist_name": "...", "notes": "...", "aspects": "...", "quote": "...", "source_author": "...", ... } ], "influenced": [ { "id": 20, "title": "...", ... } ] } ``` `painting.has_influence_links` matches the flag on `GET /api/artists/:id` paintings (see above). --- ## `GET /api/paintings/:id/image` Serves a painting image file. Resolves and caches from external sources only when no local file exists. **Query** | Param | Values | Default | |-------|--------|---------| | `size` | `thumb`, `full` | `thumb` | Returns the image bytes with `Cache-Control: public, max-age=86400`, or `404` if unavailable. --- ## Frontend helpers The React client wraps these endpoints in `client/src/api/client.ts`: | Function | Maps to | |----------|---------| | `api.getBounds()` | `GET /api/bounds` | | `api.getTimeline(start, end)` | `GET /api/timeline` | | `api.getArtists(...)` | `GET /api/artists` | | `api.getArtist(id)` | `GET /api/artists/:id` | | `api.getArtistNavigation(id)` | `GET /api/artists/:id/navigation` | | `api.getPainting(id)` | `GET /api/paintings/:id` | | `preloadArtistImages(id)` | `POST /api/artists/:id/preload-images` | | `imageUrl(path)` | `/images/` or placeholder | | `galleryImageUrl(painting)` | Local thumb/full only (3D) | | `paintingImageUrl(painting)` | Local file or on-demand API |