Support artist and movement influence links with web discovery, a developer checkup table with gallery/detail thumbnails, and debug image search with fix-it workflow. Co-authored-by: Cursor <cursoragent@cursor.com>
223 lines
6.1 KiB
Markdown
223 lines
6.1 KiB
Markdown
# 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/<relative-path>` 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 the movement flow diagram.
|
||
|
||
**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": [
|
||
{
|
||
"source_type": "painting",
|
||
"id": 3, "title": "...", "artist_name": "...", "year": 1885,
|
||
"notes": "...", "aspects": "...", "quote": "...", "source_author": "...", "source_url": "..."
|
||
},
|
||
{
|
||
"source_type": "artist",
|
||
"source_artist_id": 12, "source_artist_name": "Paul Cézanne",
|
||
"artist_portrait": "...", "period_note": "during creation (1907)",
|
||
"period_start_year": 1907, "period_end_year": 1907
|
||
},
|
||
{
|
||
"source_type": "movement",
|
||
"movement_id": 8, "movement_name": "Fauvism", "movement_color": "#c45c26",
|
||
"period_start_year": 1905, "period_end_year": 1907
|
||
}
|
||
],
|
||
"influenced": [ { "source_type": "painting", "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/<path>` or placeholder |
|
||
| `galleryImageUrl(painting)` | Local thumb/full only (3D) |
|
||
| `paintingImageUrl(painting)` | Local file or on-demand API |
|