Files
Art-gallery/Documentation/API.md
T
Danila KhodjaefandCursor 019ce4e136 Improve 3D gallery and debug checkup workflow for image curation.
Add parquet floor, museum-style exit, movement-tinted walls, and gold/black
frames with gallery sync after Fix it. Debug panel gets Checked and Fix it
buttons; documentation and image-fetch reliability updates included.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-20 22:05:24 +03:00

9.8 KiB
Raw Blame History

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

{
  "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

{
  "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

{
  "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, "checkup_checked": false, "checkup_fixed": false, ... } ]
}

Each painting includes:

Field Meaning
has_influence_links true when the work appears in any influence row — 3D gallery shows a golden lamp above the frame
checkup_checked Reviewed in checkup / debug workflow (gold frame in 3D when true)
checkup_fixed Image replaced via Fix it

Populate biographies with npm run fetch-artist-bios (see 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 artists paintings.
  • Successors — artists whose works were influenced by this artists paintings.

Both lists are grouped by art movement and exclude the current artist.

Response

{
  "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

{ "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

{
  "painting": { "id": 10, "title": "...", "artist_name": "...", "image_path": "...", "checkup_checked": false, "checkup_fixed": false, "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.


Developer image audit

Routes for the Checkup page and Debug mode on painting detail. Register GET /api/paintings/checkup before GET /api/paintings/:id so "checkup" is not parsed as a painting id.

GET /api/paintings/checkup

Full-catalog audit table for the Checkup UI.

Response

{
  "paintings": [
    {
      "id": 36,
      "title": "Trinity",
      "artist_name": "Andrei Rublev",
      "year": 1411,
      "gallery_file": "paintings/Andrei_Rublev_Trinity.jpg",
      "gallery_preview": "paintings/thumbs/Andrei_Rublev_Trinity_thumb.jpg",
      "detail_file": "paintings/Andrei_Rublev_Trinity.jpg",
      "detail_preview": "paintings/thumbs/Andrei_Rublev_Trinity_thumb.jpg",
      "detail_on_demand": false,
      "gallery_file_exists": true,
      "detail_file_exists": true,
      "checked": false,
      "fixed": false
    }
  ],
  "total": 1201
}
Field Meaning
checked Reviewed in checkup workflow (checkup_checked)
fixed Image replaced via Fix (checkup_fixed)
detail_on_demand true when detail would use GET /api/paintings/:id/image?size=full
gallery_file_exists / detail_file_exists Disk check under IMAGE_DIR

PATCH /api/paintings/:id/checkup-flags

Update review flags. Body: { "checked"?: boolean, "fixed"?: boolean } — at least one field required.

Setting fixed: true also sets checked: true. While fixed is true, the API rejects clearing checked.

Response

{ "checked": true, "fixed": false }

Google-family image search for debug / checkup (Custom Search → Google Arts & Culture → scrape → DuckDuckGo). Used by Search visible on the Checkup page and the debug panel on painting detail.

Response

{
  "query": "Andrei Rublev Trinity painting",
  "imageUrl": "https://…",
  "source": "google-arts",
  "pageUrl": "https://…"
}

POST /api/paintings/:id/fix-image

Download a remote URL and replace the paintings local full image + thumbnail. Sets checkup_fixed = true and checkup_checked = true.

Uses downloadImageForFix in scripts/image-fetcher.js (browser User-Agent, referer fallbacks, Wikimedia URL upgrades) for reliable downloads from Google Arts, Commons, etc.

Body

{
  "imageUrl": "https://…",
  "searchUrl": "https://…",
  "source": "google-arts",
  "thumbUrl": "https://…"
}

Only imageUrl is required; optional fields improve fetch success for hotlinked URLs.

Response

{
  "imagePath": "paintings/Artist_Title.jpg",
  "thumbnailPath": "paintings/thumbs/Artist_Title_thumb.jpg",
  "fixed": true,
  "checked": true
}

GET /api/debug/image-proxy

Proxy a remote image URL for debug preview (avoids hotlink / CORS blocks in the browser).

Query: url — must be http:// or https://

Returns image bytes with appropriate Content-Type.


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)
galleryImageUrlWithRevision(painting, revision) Local URL with ?v= cache buster after fix
paintingImageUrl(painting) Local file or on-demand API
api.getPaintingCheckup() GET /api/paintings/checkup
api.updatePaintingCheckupFlags(id, flags) PATCH /api/paintings/:id/checkup-flags
api.getPaintingDebugImageSearch(id) GET /api/paintings/:id/debug-image-search
api.fixPaintingImage(id, imageUrl, context?) POST /api/paintings/:id/fix-image
debugImageProxyUrl(imageUrl, context?) GET /api/debug/image-proxy?url=…