Add curator Movement links config with proof URLs and admin-gated delete votes.

Persist lineage in PostgreSQL, seed documented edges, and keep timeline/tree consumers API-backed with a static fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila KhodjaefandCursor committed 2026-09-08 14:37:20 +03:00
1 parent 2b4cdc5a9f
commit 1b760faf0d
36 files changed
+2051 -43

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
-- Curator votes to delete a movement lineage link (admin performs the actual delete).
CREATE TABLE IF NOT EXISTS movement_lineage_delete_votes (
id SERIAL PRIMARY KEY,
link_id INTEGER NOT NULL REFERENCES movement_lineage_links(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
reason TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT movement_lineage_delete_votes_unique UNIQUE (link_id, user_id),
CONSTRAINT movement_lineage_delete_votes_reason_len CHECK (char_length(trim(reason)) >= 10)
);
CREATE INDEX IF NOT EXISTS movement_lineage_delete_votes_link_idx
ON movement_lineage_delete_votes (link_id);
CREATE INDEX IF NOT EXISTS movement_lineage_delete_votes_user_idx
ON movement_lineage_delete_votes (user_id);
+37
View File
@@ -0,0 +1,37 @@
-- Art-movement predecessor → successor edges for timeline / Tree of Art.
-- Curators manage these with a required proof URL (literature or web essay).
CREATE TABLE IF NOT EXISTS movement_lineage_links (
id SERIAL PRIMARY KEY,
source_movement_id INTEGER NOT NULL REFERENCES art_movements(id) ON DELETE CASCADE,
target_movement_id INTEGER NOT NULL REFERENCES art_movements(id) ON DELETE CASCADE,
link_year_approx INTEGER,
proof_url TEXT NOT NULL,
proof_label TEXT,
notes TEXT,
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT movement_lineage_links_distinct CHECK (source_movement_id <> target_movement_id),
CONSTRAINT movement_lineage_links_unique UNIQUE (source_movement_id, target_movement_id)
);
CREATE INDEX IF NOT EXISTS movement_lineage_links_source_idx
ON movement_lineage_links (source_movement_id);
CREATE INDEX IF NOT EXISTS movement_lineage_links_target_idx
ON movement_lineage_links (target_movement_id);
CREATE INDEX IF NOT EXISTS movement_lineage_links_year_idx
ON movement_lineage_links (link_year_approx);
CREATE OR REPLACE FUNCTION movement_lineage_links_touch_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS movement_lineage_links_updated_at ON movement_lineage_links;
CREATE TRIGGER movement_lineage_links_updated_at
BEFORE UPDATE ON movement_lineage_links
FOR EACH ROW EXECUTE PROCEDURE movement_lineage_links_touch_updated_at();