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 |
|
||||
Reference in New Issue
Block a user