Timeline: TimelineEvent entity + Flyway migration (#774) #816

Merged
marcel merged 11 commits from feat/issue-774-timeline-event-entity into main 2026-06-13 09:30:33 +02:00
Showing only changes of commit 513cdb7a4d - Show all commits

View File

@@ -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);