Files
Art-gallery/Documentation/DB_structure.md
T
Danila KhodjaefandCursor f542c689c9 Add debug Remove entry, Show more auto-picker, and update docs.
DELETE /api/paintings/:id removes works and image files with gallery refresh and catalog navigation; Show more opens the search modal on load; documentation updated for migrate schema and debug workflow.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 22:32:23 +03:00

218 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Art Gallery — database structure
PostgreSQL schema for the virtual gallery. Canonical DDL lives in **`db/schema.sql`**; **`server/migrate.js`** (`npm run migrate`) applies that file plus idempotent incremental scripts in `db/migrate-*.sql`. This document describes the logical model.
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
paintings ||--o{ painting_influence_sources : influenced_by
artists ||--o{ painting_influence_sources : artist_source
art_movements ||--o{ painting_influence_sources : movement_source
```
## 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 stream in the flow diagram |
### `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/`; nullable after debug **Clear** |
| `bio_short`, `bio_full` | TEXT | Wikipedia lead section (`npm run fetch-artist-bios`) |
| `wikipedia_title` | VARCHAR(300) | Source page title |
| `century` | INTEGER | Rounded century bucket for seeding limits |
| `checkup_checked` | BOOLEAN NOT NULL DEFAULT false | Portrait reviewed in debug workflow (gold border on bio when true) |
| `checkup_fixed` | BOOLEAN NOT NULL DEFAULT false | Portrait replaced, cleared, or uploaded via debug |
| `palette_metadata` | JSONB | Enrichment from `Inputs/PainterPalette.csv` (`npm run import-painter-palette`) |
Applied by `npm run migrate:artist-checkup-flags` (`db/migrate-artist-checkup-flags.sql`) and `npm run migrate:artist-palette` (`db/migrate-artist-palette.sql`).
### `artist_periods`
Phases within an artists 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; nullable after debug **Clear** |
| `thumbnail_path` | VARCHAR(500) | Smaller variant for lists / 3D; nullable after **Clear** |
| `wikipedia_title` | VARCHAR(300) | Used by image fetcher |
| `sort_order` | INTEGER | |
| `checkup_checked` | BOOLEAN NOT NULL DEFAULT false | Reviewed in image checkup workflow (UI label: **Reviewed**) |
| `checkup_fixed` | BOOLEAN NOT NULL DEFAULT false | Image corrected, cleared, or uploaded via checkup / debug |
When `checkup_fixed` is true, `checkup_checked` is set automatically and cannot be cleared until **Fixed** is off. A cleared painting (`image_path` and `thumbnail_path` both null, `checkup_fixed` true) is shown as an empty frame in detail view and is not refetched on demand.
Applied by `npm run migrate:checkup-flags` (`db/migrate-checkup-flags.sql`).
### `painting_annotations`
Short art-history notes shown on painting detail (`PaintingAnnotations.tsx`).
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `painting_id` | FK → `paintings` | ON DELETE CASCADE |
| `label` | VARCHAR(80) | Optional short heading (e.g. figure name) |
| `body` | TEXT | Note text |
| `category` | VARCHAR(30) | Default `subject`; also `technique`, `context`, `symbolism`, etc. |
| `pos_x`, `pos_y` | NUMERIC(5,2) | Optional marker position on image (percent 0100) |
| `source_author` | VARCHAR(200) | e.g. Gombrich, Met catalog |
| `source` | VARCHAR(500) | Citation label |
| `source_url` | VARCHAR(500) | Reference link |
| `sort_order` | INTEGER | Display order within the painting |
| `confidence` | VARCHAR(20) | Default `curated`; Wikipedia pass uses `wikipedia` |
Applied by `npm run migrate:painting-annotations` (`db/migrate-painting-annotations.sql`). Load data with `npm run update-painting-annotations` (curated entries in `scripts/painting-annotations-data.js`; add `--wikipedia` for intro sentences from each works `wikipedia_title`).
### `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)`.
**Legacy mirror table.** `npm run update-influences` still inserts painting-to-painting rows here when curating data. The same edges are copied into `painting_influence_sources`. The **REST API does not read this table** for painting detail or hall navigation — use `painting_influence_sources` as the source of truth for display.
### `painting_influence_sources`
Polymorphic influence links: *this painting* was influenced by a **painting**, **artist**, or **movement**.
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `painting_id` | FK → `paintings` | The work being explained |
| `source_type` | VARCHAR(20) | `painting`, `artist`, or `movement` |
| `source_painting_id` | FK → `paintings` | When `source_type = painting` |
| `source_artist_id` | FK → `artists` | When `source_type = artist` |
| `source_movement_id` | FK → `art_movements` | When `source_type = movement` |
| `period_note` | VARCHAR(240) | Human-readable period label |
| `period_start_year` | INTEGER | Optional span start |
| `period_end_year` | INTEGER | Optional span end |
| `notes` | TEXT | Curator summary |
| `source` | VARCHAR(500) | General attribution |
| `aspects` | TEXT | What was borrowed |
| `quote` | TEXT | Short citation |
| `source_author` | VARCHAR(200) | e.g. Gombrich, Janson |
| `source_url` | VARCHAR(500) | Reference link |
| `discovered_via` | VARCHAR(120) | e.g. `wikipedia`, `wikidata`, `web:metmuseum.org` |
| `confidence` | VARCHAR(20) | `curated` (default) or `discovered` |
Unique index on `(painting_id, source_type, source_painting_id, source_artist_id, source_movement_id)` with COALESCE for null FKs.
**Canonical influence store.** Used by all API influence queries: painting detail (`influencedBy`, `influenced`), `has_influence_links`, and artist hall navigation (predecessors / successors). Legacy `painting_influences` rows are backfilled here on migration; new curated painting edges are written to both tables by `update-influences`.
## Indexes
- `artists(movement_id)`, `artists(century)`
- `paintings(artist_id)`, `paintings(period_id)`, `paintings(checkup_checked)`, `paintings(checkup_fixed)`
- `artists(checkup_checked)`, `artists(checkup_fixed)`
- `art_movements(era_id)`, `art_movements(start_year, end_year)`
- `painting_influences(painting_id)`, `painting_influences(influenced_by_painting_id)`
- `painting_influence_sources(painting_id)`, `painting_influence_sources(source_artist_id)`, `painting_influence_sources(source_movement_id)`
## First-time setup
The `gallery` database user needs `CREATE` on schema `public` for migrations. If tables cannot be created, run **`db/setup-admin.sql`** as PostgreSQL superuser (or the grants below) 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
npm run fetch-artist-bios
npm run expand-catalog
```
## 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.
- **Catalog expansion**: `scripts/famous-paintings-data.js` plus `npm run expand-catalog` raises thin artist catalogs to at least six notable paintings (`MIN_PAINTINGS`, default 6).
- **Biographies**: `bio_short` and `bio_full` are populated by `npm run fetch-artist-bios` from English Wikipedia lead sections; `wikipedia_title` on the artist row is the source article.