Files
Art-gallery/db/schema.sql
T
Danila Khodjaef 99b8559607 Speed up timeline load with portrait thumbs, bootstrap API, and caching.
Add catalog bootstrap endpoint, portrait thumbnail pipeline, lazy queued timeline images, gzip compression, and 3D texture throttling with code-split VirtualGallery.
2026-07-06 14:27:29 +03:00

166 lines
6.3 KiB
SQL

-- Virtual Art Gallery — canonical PostgreSQL schema
-- Applied by: npm run migrate (server/migrate.js)
-- Incremental ALTER migrations in db/migrate-*.sql run after this on existing databases.
CREATE TABLE IF NOT EXISTS historical_eras (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
start_year INTEGER NOT NULL,
end_year INTEGER NOT NULL,
start_definite BOOLEAN DEFAULT true,
end_definite BOOLEAN DEFAULT true,
description TEXT,
sort_order INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS art_movements (
id SERIAL PRIMARY KEY,
name VARCHAR(150) NOT NULL,
start_year INTEGER NOT NULL,
end_year INTEGER NOT NULL,
start_definite BOOLEAN DEFAULT true,
end_definite BOOLEAN DEFAULT true,
era_id INTEGER REFERENCES historical_eras(id),
description TEXT,
color VARCHAR(20) DEFAULT '#8B7355'
);
CREATE TABLE IF NOT EXISTS artists (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
birth_year INTEGER,
death_year INTEGER,
movement_id INTEGER REFERENCES art_movements(id),
portrait_path VARCHAR(500),
portrait_thumb_path VARCHAR(500),
bio_short TEXT,
bio_full TEXT,
wikipedia_title VARCHAR(300),
century INTEGER,
checkup_checked BOOLEAN NOT NULL DEFAULT false,
checkup_fixed BOOLEAN NOT NULL DEFAULT false,
palette_metadata JSONB
);
CREATE TABLE IF NOT EXISTS artist_periods (
id SERIAL PRIMARY KEY,
artist_id INTEGER NOT NULL REFERENCES artists(id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
start_year INTEGER,
end_year INTEGER,
description TEXT,
sort_order INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS paintings (
id SERIAL PRIMARY KEY,
artist_id INTEGER NOT NULL REFERENCES artists(id) ON DELETE CASCADE,
period_id INTEGER REFERENCES artist_periods(id),
title VARCHAR(300) NOT NULL,
year INTEGER,
year_end INTEGER,
description TEXT,
image_path VARCHAR(500),
thumbnail_path VARCHAR(500),
wikipedia_title VARCHAR(300),
sort_order INTEGER DEFAULT 0,
checkup_checked BOOLEAN NOT NULL DEFAULT false,
checkup_fixed BOOLEAN NOT NULL DEFAULT false
);
CREATE TABLE IF NOT EXISTS painting_influences (
id SERIAL PRIMARY KEY,
painting_id INTEGER NOT NULL REFERENCES paintings(id) ON DELETE CASCADE,
influenced_by_painting_id INTEGER NOT NULL REFERENCES paintings(id) ON DELETE CASCADE,
notes TEXT,
source VARCHAR(500),
aspects TEXT,
quote TEXT,
source_author VARCHAR(200),
source_url VARCHAR(500),
UNIQUE(painting_id, influenced_by_painting_id)
);
CREATE TABLE IF NOT EXISTS painting_influence_sources (
id SERIAL PRIMARY KEY,
painting_id INTEGER NOT NULL REFERENCES paintings(id) ON DELETE CASCADE,
source_type VARCHAR(20) NOT NULL CHECK (source_type IN ('painting', 'artist', 'movement')),
source_painting_id INTEGER REFERENCES paintings(id) ON DELETE CASCADE,
source_artist_id INTEGER REFERENCES artists(id) ON DELETE CASCADE,
source_movement_id INTEGER REFERENCES art_movements(id) ON DELETE CASCADE,
period_note VARCHAR(240),
period_start_year INTEGER,
period_end_year INTEGER,
notes TEXT,
source VARCHAR(500),
aspects TEXT,
quote TEXT,
source_author VARCHAR(200),
source_url VARCHAR(500),
discovered_via VARCHAR(120),
confidence VARCHAR(20) NOT NULL DEFAULT 'curated',
CONSTRAINT painting_influence_sources_source_shape CHECK (
(source_type = 'painting' AND source_painting_id IS NOT NULL AND source_artist_id IS NULL AND source_movement_id IS NULL)
OR (source_type = 'artist' AND source_artist_id IS NOT NULL AND source_painting_id IS NULL AND source_movement_id IS NULL)
OR (source_type = 'movement' AND source_movement_id IS NOT NULL AND source_painting_id IS NULL AND source_artist_id IS NULL)
)
);
CREATE TABLE IF NOT EXISTS painting_annotations (
id SERIAL PRIMARY KEY,
painting_id INTEGER NOT NULL REFERENCES paintings(id) ON DELETE CASCADE,
label VARCHAR(80),
body TEXT NOT NULL,
category VARCHAR(30) NOT NULL DEFAULT 'subject',
pos_x NUMERIC(5, 2),
pos_y NUMERIC(5, 2),
source_author VARCHAR(200),
source VARCHAR(500),
source_url VARCHAR(500),
sort_order INTEGER NOT NULL DEFAULT 0,
confidence VARCHAR(20) NOT NULL DEFAULT 'curated'
);
CREATE INDEX IF NOT EXISTS idx_artists_movement ON artists(movement_id);
CREATE INDEX IF NOT EXISTS idx_artists_birth_year ON artists(birth_year);
CREATE INDEX IF NOT EXISTS idx_artists_death_year ON artists(death_year);
CREATE INDEX IF NOT EXISTS idx_artists_century ON artists(century);
CREATE INDEX IF NOT EXISTS artists_checkup_checked_idx ON artists (checkup_checked);
CREATE INDEX IF NOT EXISTS artists_checkup_fixed_idx ON artists (checkup_fixed);
CREATE INDEX IF NOT EXISTS artists_palette_metadata_idx ON artists USING gin (palette_metadata);
CREATE INDEX IF NOT EXISTS idx_paintings_artist ON paintings(artist_id);
CREATE INDEX IF NOT EXISTS idx_paintings_period ON paintings(period_id);
CREATE INDEX IF NOT EXISTS paintings_checkup_checked_idx ON paintings (checkup_checked);
CREATE INDEX IF NOT EXISTS paintings_checkup_fixed_idx ON paintings (checkup_fixed);
CREATE INDEX IF NOT EXISTS idx_movements_era ON art_movements(era_id);
CREATE INDEX IF NOT EXISTS idx_movements_years ON art_movements(start_year, end_year);
CREATE INDEX IF NOT EXISTS idx_influences_painting ON painting_influences(painting_id);
CREATE INDEX IF NOT EXISTS idx_influences_by ON painting_influences(influenced_by_painting_id);
CREATE UNIQUE INDEX IF NOT EXISTS painting_influence_sources_unique
ON painting_influence_sources (
painting_id,
source_type,
COALESCE(source_painting_id, 0),
COALESCE(source_artist_id, 0),
COALESCE(source_movement_id, 0)
);
CREATE INDEX IF NOT EXISTS painting_influence_sources_painting_idx
ON painting_influence_sources (painting_id);
CREATE INDEX IF NOT EXISTS idx_influence_sources_source_painting
ON painting_influence_sources (source_painting_id);
CREATE INDEX IF NOT EXISTS painting_influence_sources_artist_idx
ON painting_influence_sources (source_artist_id);
CREATE INDEX IF NOT EXISTS painting_influence_sources_movement_idx
ON painting_influence_sources (source_movement_id);
CREATE INDEX IF NOT EXISTS painting_annotations_painting_idx
ON painting_annotations (painting_id);