From 23e53bf30bb770aed9a4117651d264d7e0bc99b5 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 12 Jun 2026 23:04:02 +0200 Subject: [PATCH] feat(timeline): add V77 migration for timeline_events table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates timeline_events plus the timeline_event_persons and timeline_event_documents join tables, all FK columns ON DELETE CASCADE (a person/document delete drops the join row, the event survives — V71-class hardening). Two CHECK constraints push integrity to Postgres: chk_timeline_event_range enforces event_date_end non-null IFF RANGE (a strict biconditional, intentionally tighter than Document's open-ended ranges), and chk_timeline_event_precision forbids exactly UNKNOWN while keeping SEASON/APPROX legal. FK and query-column indexes added up-front to avoid the V62 retrofit debt. Forward-only, additive DDL. Co-Authored-By: Claude Fable 5 --- .../db/migration/V77__add_timeline_events.sql | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 backend/src/main/resources/db/migration/V77__add_timeline_events.sql diff --git a/backend/src/main/resources/db/migration/V77__add_timeline_events.sql b/backend/src/main/resources/db/migration/V77__add_timeline_events.sql new file mode 100644 index 00000000..aba99c10 --- /dev/null +++ b/backend/src/main/resources/db/migration/V77__add_timeline_events.sql @@ -0,0 +1,65 @@ +-- V77: timeline domain foundation (Zeitstrahl) — curated timeline events. +-- Forward-only, additive DDL. No rollback script: rollback requires manual DROP TABLE +-- (timeline_event_documents, timeline_event_persons, then timeline_events). See ADR-040. +-- +-- The date block (event_date / date_precision / event_date_end) mirrors documents' so events +-- and letters share one rendering path. Two divergences from documents are INTENTIONAL and +-- enforced here in Postgres (ADR-040): +-- 1. The RANGE rule is a strict biconditional (event_date_end non-null IFF RANGE), unlike +-- documents' open-ended ranges — a curated event always has a known, closed end. +-- 2. date_precision <> 'UNKNOWN' — only OCR-inferred letters are ever undated; a curated +-- event always has at least a year. SEASON and APPROX stay legal (Sommer/ca. 1914). + +CREATE TABLE timeline_events ( + id UUID NOT NULL DEFAULT gen_random_uuid(), + title VARCHAR(255) NOT NULL, + type VARCHAR(16) NOT NULL, + event_date DATE NOT NULL, + date_precision VARCHAR(16) NOT NULL DEFAULT 'YEAR', + event_date_end DATE, + description TEXT, + created_by UUID NOT NULL, + created_at TIMESTAMP, + updated_by UUID NOT NULL, + updated_at TIMESTAMP, + version BIGINT, + CONSTRAINT pk_timeline_events PRIMARY KEY (id), + -- event_date_end is non-null IFF precision is RANGE (both directions). + CONSTRAINT chk_timeline_event_range + CHECK ((date_precision = 'RANGE') = (event_date_end IS NOT NULL)), + -- Curated events are never undated. Forbids exactly UNKNOWN — every other + -- DatePrecision value (DAY, MONTH, SEASON, YEAR, RANGE, APPROX) stays legal. + CONSTRAINT chk_timeline_event_precision + CHECK (date_precision <> 'UNKNOWN') +); + +-- Join table: events ↔ persons involved. +CREATE TABLE timeline_event_persons ( + timeline_event_id UUID NOT NULL, + person_id UUID NOT NULL, + CONSTRAINT pk_timeline_event_persons PRIMARY KEY (timeline_event_id, person_id), + CONSTRAINT fk_tep_event + FOREIGN KEY (timeline_event_id) REFERENCES timeline_events(id) ON DELETE CASCADE, + CONSTRAINT fk_tep_person + FOREIGN KEY (person_id) REFERENCES persons(id) ON DELETE CASCADE +); + +-- Join table: events ↔ supporting letters. +CREATE TABLE timeline_event_documents ( + timeline_event_id UUID NOT NULL, + document_id UUID NOT NULL, + CONSTRAINT pk_timeline_event_documents PRIMARY KEY (timeline_event_id, document_id), + CONSTRAINT fk_ted_event + FOREIGN KEY (timeline_event_id) REFERENCES timeline_events(id) ON DELETE CASCADE, + CONSTRAINT fk_ted_document + FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE +); + +-- Indexes added up-front (avoid the V62 FK-index retrofit debt): the two query columns plus +-- explicit indexes on all four FK columns. +CREATE INDEX idx_timeline_events_event_date ON timeline_events (event_date); +CREATE INDEX idx_timeline_events_type ON timeline_events (type); +CREATE INDEX idx_timeline_event_persons_person_id ON timeline_event_persons (person_id); +CREATE INDEX idx_timeline_event_persons_event_id ON timeline_event_persons (timeline_event_id); +CREATE INDEX idx_timeline_event_documents_document_id ON timeline_event_documents (document_id); +CREATE INDEX idx_timeline_event_documents_event_id ON timeline_event_documents (timeline_event_id);