Add project documentation under Documentation/.
Covers architecture, setup, database schema, API reference, and image pipeline; link from README. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
40db99d734
commit
44d3d4359a
@@ -0,0 +1,153 @@
|
|||||||
|
# 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 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": "...", "movement_name": "...", "bio_full": "...", ... },
|
||||||
|
"periods": [ { "id": 1, "name": "Milan Period", "start_year": 1482, "end_year": 1499, ... } ],
|
||||||
|
"paintings": [ { "id": 10, "title": "...", "year": 1498, "image_path": "...", "thumbnail_path": "...", ... } ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `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": "...", ... },
|
||||||
|
"influencedBy": [ { "id": 3, "title": "...", "artist_name": "...", "notes": "...", "aspects": "...", "quote": "...", "source_author": "...", ... } ],
|
||||||
|
"influenced": [ { "id": 20, "title": "...", ... } ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `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.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 |
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# Art Gallery — database structure
|
||||||
|
|
||||||
|
PostgreSQL schema for the virtual gallery. Canonical DDL is intended to live in `db/schema.sql` when checked in; this document describes the logical model either way.
|
||||||
|
|
||||||
|
Connection settings come from `.env` (see [setup.md](setup.md)).
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
| Item | Typical value |
|
||||||
|
|------|----------------|
|
||||||
|
| Engine | PostgreSQL 14+ |
|
||||||
|
| Database | `Gallery` |
|
||||||
|
| App user | `gallery` |
|
||||||
|
| Time fields | Integer years (negative = BCE) |
|
||||||
|
|
||||||
|
## Entity relationship
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
historical_eras ||--o{ art_movements : contains
|
||||||
|
art_movements ||--o{ artists : groups
|
||||||
|
artists ||--o{ artist_periods : has
|
||||||
|
artists ||--o{ paintings : created
|
||||||
|
artist_periods ||--o{ paintings : groups
|
||||||
|
paintings ||--o{ painting_influences : influenced_by
|
||||||
|
paintings ||--o{ painting_influences : influences
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tables
|
||||||
|
|
||||||
|
### `historical_eras`
|
||||||
|
|
||||||
|
Broad chronological buckets (Ancient, Medieval, Renaissance, …).
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| `id` | SERIAL PK | |
|
||||||
|
| `name` | VARCHAR(100) | Display label |
|
||||||
|
| `start_year`, `end_year` | INTEGER | Inclusive range |
|
||||||
|
| `start_definite`, `end_definite` | BOOLEAN | `false` → render as gradient edge on timeline |
|
||||||
|
| `description` | TEXT | Tooltip / sidebar copy |
|
||||||
|
| `sort_order` | INTEGER | Display order |
|
||||||
|
|
||||||
|
### `art_movements`
|
||||||
|
|
||||||
|
Finer-grained styles (Impressionism, Cubism, Suprematism, …).
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| `id` | SERIAL PK | |
|
||||||
|
| `name` | VARCHAR(150) | |
|
||||||
|
| `start_year`, `end_year` | INTEGER | |
|
||||||
|
| `start_definite`, `end_definite` | BOOLEAN | Same visual semantics as eras |
|
||||||
|
| `era_id` | FK → `historical_eras` | Optional parent era |
|
||||||
|
| `description` | TEXT | |
|
||||||
|
| `color` | VARCHAR(20) | Hex colour for movement band |
|
||||||
|
|
||||||
|
### `artists`
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| `id` | SERIAL PK | |
|
||||||
|
| `name` | VARCHAR(200) | |
|
||||||
|
| `birth_year`, `death_year` | INTEGER | Nullable; used for timeline portrait placement |
|
||||||
|
| `movement_id` | FK → `art_movements` | Primary movement |
|
||||||
|
| `portrait_path` | VARCHAR(500) | Relative to `data/images/` |
|
||||||
|
| `bio_short`, `bio_full` | TEXT | From Wikipedia extracts |
|
||||||
|
| `wikipedia_title` | VARCHAR(300) | Source page title |
|
||||||
|
| `century` | INTEGER | Rounded century bucket for seeding limits |
|
||||||
|
|
||||||
|
### `artist_periods`
|
||||||
|
|
||||||
|
Phases within an artist’s career (e.g. “Blue Period”, “Roman Period”).
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| `id` | SERIAL PK | |
|
||||||
|
| `artist_id` | FK → `artists` | ON DELETE CASCADE |
|
||||||
|
| `name` | VARCHAR(200) | |
|
||||||
|
| `start_year`, `end_year` | INTEGER | |
|
||||||
|
| `description` | TEXT | |
|
||||||
|
| `sort_order` | INTEGER | Wall order in 3D gallery |
|
||||||
|
|
||||||
|
### `paintings`
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| `id` | SERIAL PK | |
|
||||||
|
| `artist_id` | FK → `artists` | ON DELETE CASCADE |
|
||||||
|
| `period_id` | FK → `artist_periods` | Optional grouping |
|
||||||
|
| `title` | VARCHAR(300) | |
|
||||||
|
| `year`, `year_end` | INTEGER | Creation date(s) |
|
||||||
|
| `description` | TEXT | |
|
||||||
|
| `image_path` | VARCHAR(500) | Full-size local file |
|
||||||
|
| `thumbnail_path` | VARCHAR(500) | Smaller variant for lists / 3D |
|
||||||
|
| `wikipedia_title` | VARCHAR(300) | Used by image fetcher |
|
||||||
|
| `sort_order` | INTEGER | |
|
||||||
|
|
||||||
|
### `painting_influences`
|
||||||
|
|
||||||
|
Directed edges: *this painting* was influenced by *that painting*.
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| `id` | SERIAL PK | |
|
||||||
|
| `painting_id` | FK → `paintings` | The work being explained |
|
||||||
|
| `influenced_by_painting_id` | FK → `paintings` | The earlier / source work |
|
||||||
|
| `notes` | TEXT | Curator summary |
|
||||||
|
| `source` | VARCHAR(500) | General attribution |
|
||||||
|
| `aspects` | TEXT | What was borrowed (composition, colour, …) |
|
||||||
|
| `quote` | TEXT | Short citation |
|
||||||
|
| `source_author` | VARCHAR(200) | e.g. Gombrich, Janson |
|
||||||
|
| `source_url` | VARCHAR(500) | Reference link |
|
||||||
|
|
||||||
|
Unique constraint on `(painting_id, influenced_by_painting_id)`.
|
||||||
|
|
||||||
|
## Indexes
|
||||||
|
|
||||||
|
- `artists(movement_id)`, `artists(century)`
|
||||||
|
- `paintings(artist_id)`, `paintings(period_id)`
|
||||||
|
- `art_movements(era_id)`, `art_movements(start_year, end_year)`
|
||||||
|
- `painting_influences(painting_id)`, `painting_influences(influenced_by_painting_id)`
|
||||||
|
|
||||||
|
## First-time setup
|
||||||
|
|
||||||
|
The `gallery` database user needs `CREATE` on schema `public` for migrations. If tables cannot be created, run admin grants as PostgreSQL superuser before `npm run migrate`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
GRANT CREATE ON SCHEMA public TO gallery;
|
||||||
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO gallery;
|
||||||
|
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO gallery;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO gallery;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO gallery;
|
||||||
|
```
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run migrate
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data conventions
|
||||||
|
|
||||||
|
- **Year zero** is not used; BCE years are negative integers.
|
||||||
|
- **Image paths** are relative to `IMAGE_DIR` (default `./data/images`), e.g. `portraits/Claude_Monet.jpg`, `paintings/thumbs/Raphael_The_School_of_Athens_thumb.jpg`.
|
||||||
|
- **Seeding cap**: curated ingest targets at most ~100 artists per century to keep the catalog manageable.
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Art Gallery — architecture basics
|
||||||
|
|
||||||
|
Interactive virtual museum spanning art history: zoomable timeline, movement bands, 3D gallery halls, and painting influence graphs. See [README.md](../README.md) for quick start.
|
||||||
|
|
||||||
|
## Concept
|
||||||
|
|
||||||
|
The app is organised as a **drill-down hierarchy**:
|
||||||
|
|
||||||
|
1. **Timeline** — historical eras (Ancient → Contemporary) with definite or fuzzy date boundaries.
|
||||||
|
2. **Movement bands** — art movements aligned to the same time axis, each showing portrait thumbnails of key artists.
|
||||||
|
3. **3D gallery** — classic hall environment; paintings grouped by the artist’s creative periods.
|
||||||
|
4. **Painting detail** — full work in the centre, *Influenced By* on the left, *Influenced* on the right, link to artist biography.
|
||||||
|
|
||||||
|
All artwork images are stored locally under `data/images/` — the UI never hot-links to Wikipedia or Commons at runtime (except optional on-demand fetch when a file is missing).
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
| Layer | Choice | Role |
|
||||||
|
|--------|--------|------|
|
||||||
|
| API | **Node.js + Express 5** | REST API, static image serving, optional SPA hosting from `client/dist` |
|
||||||
|
| Database | **PostgreSQL** | Eras, movements, artists, paintings, influence edges |
|
||||||
|
| Frontend | **React 19 + Vite 8** | SPA routing and UI |
|
||||||
|
| 3D | **Three.js** via `@react-three/fiber`, `@react-three/drei` | Virtual gallery navigation |
|
||||||
|
| Data ingestion | Node scripts | Wikipedia summaries, Commons images, curated influence data |
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
Gallery/
|
||||||
|
├── server/ # Express API, DB pool, image service
|
||||||
|
├── client/ # React/Vite frontend
|
||||||
|
│ ├── src/ # Source (components, pages, 3D scene)
|
||||||
|
│ └── dist/ # Production build (served by API when present)
|
||||||
|
├── scripts/ # Seed, image fetch, catalog expansion
|
||||||
|
├── data/images/ # Local portraits and paintings (+ thumbs/)
|
||||||
|
├── db/ # SQL schema and migrations (when present)
|
||||||
|
├── Documentation/ # This folder
|
||||||
|
└── .env # DB and port config (not committed)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime modes
|
||||||
|
|
||||||
|
### Production-style (single process)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run server # http://localhost:3001
|
||||||
|
```
|
||||||
|
|
||||||
|
Serves `/api/*`, `/images/*`, and the built SPA from `client/dist` if it exists.
|
||||||
|
|
||||||
|
### Development (two processes)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:server # API on :3001
|
||||||
|
npm run dev:client # Vite on :5173, proxies /api and /images
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the Vite URL during frontend work for HMR.
|
||||||
|
|
||||||
|
## User navigation flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Home — timeline + movements] -->|scroll / drag / zoom| A
|
||||||
|
A -->|click portrait| B[Artist bio]
|
||||||
|
B -->|Enter Gallery| C[3D hall]
|
||||||
|
C -->|click painting| D[Painting detail + influences]
|
||||||
|
D -->|thumbnail left/right| D
|
||||||
|
D -->|artist link| B
|
||||||
|
B -->|Back| A
|
||||||
|
C -->|Back| A
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key design decisions
|
||||||
|
|
||||||
|
- **Timeline bounds** derive from the earliest art movement start year, not ancient-era metadata alone, so the default view opens where catalogued content begins.
|
||||||
|
- **Movement filtering** on zoom only shows movements that have at least one artist active in the visible year range.
|
||||||
|
- **3D gallery images** use locally cached files only (`galleryImageUrl`); slow remote fetches would break realtime rendering.
|
||||||
|
- **Influence data** is stored as directed edges between paintings, with optional citation fields (source author, quote, URL) for art-historical references.
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
| Document | Contents |
|
||||||
|
|----------|----------|
|
||||||
|
| [setup.md](setup.md) | Install, database, npm scripts |
|
||||||
|
| [DB_structure.md](DB_structure.md) | Tables and relationships |
|
||||||
|
| [API.md](API.md) | REST endpoints |
|
||||||
|
| [data-and-images.md](data-and-images.md) | Image pipeline and seeding |
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Art Gallery — data and images
|
||||||
|
|
||||||
|
How catalog content and artwork files enter the system.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
1. **No runtime hot-linking** — the UI reads from `/images/…` (local disk). External URLs are used only during ingest.
|
||||||
|
2. **No AI-generated art or text** — biographies and descriptions come from Wikipedia; influence notes from curated art-history sources.
|
||||||
|
3. **Local copies** — every displayed image should exist under `data/images/` after seeding or fetch.
|
||||||
|
|
||||||
|
## Directory layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/images/
|
||||||
|
├── portraits/ # Artist headshots
|
||||||
|
│ └── Claude_Monet.jpg
|
||||||
|
└── paintings/
|
||||||
|
├── Claude_Monet_Water_Lilies.jpg
|
||||||
|
└── thumbs/
|
||||||
|
└── Claude_Monet_Water_Lilies_thumb.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
File names are sanitised `{Artist}_{Title}.{ext}`. The image service can rediscover files on disk even when DB paths are empty (`server/image-service.js` → `syncPaintingFromDisk`).
|
||||||
|
|
||||||
|
## Seeding pipeline
|
||||||
|
|
||||||
|
`npm run seed` runs `scripts/seed-wikipedia.js`, which:
|
||||||
|
|
||||||
|
1. Inserts **historical eras** and **art movements** (curated date ranges and colours).
|
||||||
|
2. For each curated **artist**:
|
||||||
|
- Fetches Wikipedia intro text for `bio_short` / `bio_full`.
|
||||||
|
- Downloads a Commons portrait into `portraits/`.
|
||||||
|
- Creates **artist periods** and **paintings**.
|
||||||
|
- Downloads painting images into `paintings/`.
|
||||||
|
3. Writes **painting_influences** edges from curated scholarship references.
|
||||||
|
|
||||||
|
Artists are grouped by movement and century; the seed list targets at most ~100 artists per century.
|
||||||
|
|
||||||
|
## On-demand image resolution
|
||||||
|
|
||||||
|
When a painting has no local file, `GET /api/paintings/:id/image` triggers `ensurePaintingImages()`:
|
||||||
|
|
||||||
|
1. Check DB paths → verify file on disk.
|
||||||
|
2. Scan disk by `{artist}_{title}` pattern.
|
||||||
|
3. If still missing and `wikipedia_title` is set, call `scripts/image-fetcher.js`:
|
||||||
|
- Wikidata → Wikimedia Commons → Wikipedia page image
|
||||||
|
- Fallbacks: Met Museum, Art Institute of Chicago, Rijksmuseum APIs
|
||||||
|
4. Save full + thumbnail, update DB, serve file.
|
||||||
|
|
||||||
|
Requests are deduplicated (`inflight` map) and timeout after 15 seconds. A 2.5 s delay between external requests reduces rate-limit risk.
|
||||||
|
|
||||||
|
## Preload before 3D gallery
|
||||||
|
|
||||||
|
`POST /api/artists/:id/preload-images` runs **local-only** linking — no network. Call this when entering an artist’s 3D hall so textures use files already on disk.
|
||||||
|
|
||||||
|
The 3D scene uses `galleryImageUrl()`, which never hits the on-demand API (remote latency breaks WebGL texture loading).
|
||||||
|
|
||||||
|
## Placeholders
|
||||||
|
|
||||||
|
When no image is available:
|
||||||
|
|
||||||
|
- `/placeholder-portrait.svg` — timeline / movement band portraits
|
||||||
|
- `/placeholder-art.svg` — paintings in lists and detail view
|
||||||
|
|
||||||
|
These live in `client/public/` (and `client/dist/` after build).
|
||||||
|
|
||||||
|
## Adding new artists manually
|
||||||
|
|
||||||
|
1. Insert rows into `artists`, `artist_periods`, `paintings` (or extend the `ARTISTS` array in the seed script).
|
||||||
|
2. Place image files under `data/images/` using the naming convention.
|
||||||
|
3. Run `npm run sync-image-paths` if that script is available, or rely on preload / on-demand sync.
|
||||||
|
4. Add influence rows to `painting_influences` with citation fields where possible.
|
||||||
|
|
||||||
|
## Image fetcher overrides
|
||||||
|
|
||||||
|
`scripts/image-fetcher.js` includes hand-maintained overrides for ambiguous Wikipedia titles and direct URLs (e.g. works whose Commons name does not match the article title). Extend `PAINTING_WIKI_OVERRIDES` and `DIRECT_IMAGE_OVERRIDES` when automated resolution fails.
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# Art Gallery — setup and operations
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Node.js** 20+
|
||||||
|
- **PostgreSQL** reachable from the app host
|
||||||
|
- Network access to Wikipedia / Wikimedia (only for seeding and on-demand image fetch)
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Copy the example file and fill in credentials:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `DB_HOST` | PostgreSQL host |
|
||||||
|
| `DB_PORT` | Port (default `5432`) |
|
||||||
|
| `DB_USER` | Database user |
|
||||||
|
| `DB_PASSWORD` | Database password |
|
||||||
|
| `DB_NAME` | Database name (`Gallery`) |
|
||||||
|
| `PORT` | API listen port (default `3001`) |
|
||||||
|
| `IMAGE_DIR` | Root for cached images (default `./data/images`) |
|
||||||
|
|
||||||
|
`.env` is git-ignored; never commit passwords.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
cd client && npm install && cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database bootstrap
|
||||||
|
|
||||||
|
One-shot setup (migrate + seed):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run setup
|
||||||
|
```
|
||||||
|
|
||||||
|
Or step by step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run migrate # apply db/schema.sql
|
||||||
|
npm run seed # eras, movements, artists, paintings, influences
|
||||||
|
```
|
||||||
|
|
||||||
|
If migration fails with permission errors, grant schema rights to the app user first (see [DB_structure.md](DB_structure.md)).
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `npm run server` | API + static SPA on `PORT` |
|
||||||
|
| `npm run dev:server` | API with nodemon reload |
|
||||||
|
| `npm run dev:client` | Vite dev server on :5173 |
|
||||||
|
| `npm run dev` | Alias for `server` |
|
||||||
|
|
||||||
|
**Production frontend:** build the client, then start the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd client && npm run build && cd ..
|
||||||
|
npm run server
|
||||||
|
```
|
||||||
|
|
||||||
|
Open http://localhost:3001 (or your configured `PORT`).
|
||||||
|
|
||||||
|
## Maintenance scripts
|
||||||
|
|
||||||
|
| Script | Command | Purpose |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| Seed | `npm run seed` | Reload curated Wikipedia data |
|
||||||
|
| Thumbnails migration | `npm run migrate:thumbnails` | Add thumbnail columns |
|
||||||
|
| Fetch missing images | `npm run fetch-images` | Backfill painting files |
|
||||||
|
| Fetch artist portraits | `npm run fetch-artist-images` | Backfill portrait files |
|
||||||
|
| Sync image paths | `npm run sync-image-paths` | Align DB paths with disk |
|
||||||
|
| Expand catalog | `npm run expand-catalog` | Add paintings from Wikipedia lists |
|
||||||
|
| Update influences | `npm run update-influences` | Refresh influence edges |
|
||||||
|
|
||||||
|
Scripts under `scripts/` that are not yet present locally may need to be restored from git history or re-added; core paths (`seed-wikipedia.js`, `image-fetcher.js`) are in the repository.
|
||||||
|
|
||||||
|
## Remote repository
|
||||||
|
|
||||||
|
Gitea: [Danilka/Art-gallery](https://gitea.mysuperlab.netcraze.pro/Danilka/Art-gallery)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.mysuperlab.netcraze.pro/Danilka/Art-gallery.git
|
||||||
|
```
|
||||||
|
|
||||||
|
After clone: copy `.env.example` → `.env`, install dependencies, run `npm run setup` against your Postgres instance.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Likely cause | Fix |
|
||||||
|
|---------|--------------|-----|
|
||||||
|
| Empty timeline | DB not seeded | `npm run seed` |
|
||||||
|
| 500 on all `/api/*` | Wrong `.env` or Postgres down | Check connection, logs |
|
||||||
|
| Black frames in 3D gallery | No local image for painting | `POST …/preload-images` or `npm run fetch-images` |
|
||||||
|
| 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 |
|
||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
Interactive virtual art gallery: zoomable historical timeline, art movement bands, 3D navigable gallery halls, and painting influence graphs.
|
Interactive virtual art gallery: zoomable historical timeline, art movement bands, 3D navigable gallery halls, and painting influence graphs.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
| Document | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| [Documentation/basics.md](Documentation/basics.md) | Architecture, layout, user flow |
|
||||||
|
| [Documentation/setup.md](Documentation/setup.md) | Install, env, npm scripts |
|
||||||
|
| [Documentation/DB_structure.md](Documentation/DB_structure.md) | PostgreSQL tables |
|
||||||
|
| [Documentation/API.md](Documentation/API.md) | REST endpoints |
|
||||||
|
| [Documentation/data-and-images.md](Documentation/data-and-images.md) | Seeding and image pipeline |
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
- **Backend:** Node.js, Express, PostgreSQL
|
- **Backend:** Node.js, Express, PostgreSQL
|
||||||
|
|||||||
Reference in New Issue
Block a user