From 27bfa7acbd33278afbcb139a55b148f840652d54 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 12 Jun 2026 23:11:08 +0200 Subject: [PATCH] docs(timeline): register timeline domain in package tables and diagrams Add timeline/ to the root and backend package tables, TimelineEvent to the domain-model entity tables, TimelineEvent/EventType/Zeitstrahl to the glossary, a new l3-backend-timeline C4 component diagram, and the timeline_events table + two join tables (with their CHECKs and cascade FKs) to the db-orm and db-relationships ER diagrams. Bumps the db-orm snapshot to V77. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 2 + backend/CLAUDE.md | 2 + docs/GLOSSARY.md | 6 +++ docs/architecture/c4/l3-backend-timeline.puml | 24 +++++++++++ docs/architecture/db/db-orm.puml | 43 ++++++++++++++++++- docs/architecture/db/db-relationships.puml | 15 +++++++ 6 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 docs/architecture/c4/l3-backend-timeline.puml diff --git a/CLAUDE.md b/CLAUDE.md index ae9127c0..810524b7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,6 +95,7 @@ backend/src/main/java/org/raddatz/familienarchiv/ │ └── relationship/ PersonRelationship sub-domain ├── security/ SecurityConfig, Permission, @RequirePermission, PermissionAspect ├── tag/ Tag domain +├── timeline/ Timeline (Zeitstrahl) domain — TimelineEvent, EventType, TimelineEventRepository └── user/ User domain — AppUser, UserGroup, UserService ``` @@ -115,6 +116,7 @@ backend/src/main/java/org/raddatz/familienarchiv/ | `UserGroup` | `user_groups` | Has a `Set permissions` | | `Geschichte` | `geschichten` | `GeschichteType` (`STORY`/`JOURNEY`); ManyToMany `persons` (Person); OneToMany `items` (JourneyItem) | | `JourneyItem` | `journey_items` | ManyToOne `geschichte` (Geschichte, ON DELETE CASCADE); ManyToOne `document` (Document, ON DELETE SET NULL); `position`, optional `note` | +| `TimelineEvent` | `timeline_events` | `EventType` (`PERSONAL`/`HISTORICAL`); ManyToMany `persons` (Person) + `documents` (Document), both join FKs ON DELETE CASCADE; `DatePrecision` date block; `@Version` + NOT NULL `createdBy`/`updatedBy` audit trail | **`DocumentStatus` lifecycle:** `PLACEHOLDER → UPLOADED → TRANSCRIBED → REVIEWED → ARCHIVED` diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index 38d5b08b..cf063271 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -42,6 +42,7 @@ src/main/java/org/raddatz/familienarchiv/ │ └── relationship/ # PersonRelationship sub-domain ├── security/ # SecurityConfig, Permission, @RequirePermission, PermissionAspect ├── tag/ # Tag domain — Tag, TagService, TagController +├── timeline/ # Timeline (Zeitstrahl) domain — TimelineEvent, EventType, TimelineEventRepository └── user/ # User domain — AppUser, UserGroup, UserService ``` @@ -67,6 +68,7 @@ For per-domain ownership and public surface, see each domain's `README.md`. | `Comment` | `document_comments` | Threaded comments with mentions | | `Notification` | `notifications` | User notification feed | | `OcrJob` / `OcrJobDocument` | `ocr_jobs`, `ocr_job_documents` | Batch OCR job tracking | +| `TimelineEvent` | `timeline_events` | Curated Zeitstrahl event; ManyToMany persons + documents (join FKs ON DELETE CASCADE); `@Version` + NOT NULL createdBy/updatedBy | **`DocumentStatus` lifecycle:** `PLACEHOLDER → UPLOADED → TRANSCRIBED → REVIEWED → ARCHIVED` diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index 8fd84f5c..e6f50f52 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -164,6 +164,12 @@ _Not to be confused with a document item's optional note_ — a document item's **Lesereise** `[user-facing]` — a curated reading journey through a sequence of family documents, optionally annotated with editorial notes. Implemented as a `Geschichte` with `type=JOURNEY`. The reader UI (follow-on issue) renders items as a sequential reading experience. +**TimelineEvent** (`TimelineEvent`, table `timeline_events`) `[internal]` — a curated event on the family timeline (*Zeitstrahl*), authored by a curator rather than OCR-derived. Carries the same date block as a `Document` (`eventDate` + `precision` + nullable `eventDateEnd`) so events and letters render through one path, plus a `title`, optional `description`, an `EventType`, and `ManyToMany` links to the `Person`s it involves and the `Document`s that support it (both join FKs `ON DELETE CASCADE`). Diverges from `Document` with an optimistic-lock `@Version` and a NOT NULL `createdBy`/`updatedBy` audit trail (bare UUIDs, no FK to `app_users`) for the multi-curator edit flow. Two DB CHECKs: `event_date_end` is non-null **iff** precision is `RANGE` (a strict biconditional, intentionally tighter than `Document`'s open-ended ranges), and `precision` is never `UNKNOWN` (a curated event always has at least a year; `SEASON`/`APPROX` stay legal). See ADR-040. + +**EventType** (`EventType`) `[user-facing]` — the kind of a `TimelineEvent`: `PERSONAL` (a family event, rendered with the family accent) or `HISTORICAL` (world/historical context, rendered with a muted world accent). The string value names are a stable frontend styling contract — renaming requires a coordinated frontend change (ADR-040). + +**Zeitstrahl** `[user-facing]` — the family timeline view, rendering curated `TimelineEvent`s (and, in later issues, derived life-events) chronologically. The milestone home of the `timeline` domain. + **Notification** (`Notification`) — an in-app message delivered to an `AppUser`. No email or SMS delivery exists today. Delivered via Server-Sent Events (`SseEmitterRegistry`) and persisted in the `notifications` table. **Audit log** (`AuditLog`, table `audit_log`) — an append-only event store recording domain-level activity (document edits, user actions, etc.). Append-only by application convention; a `REVOKE UPDATE, DELETE` is attempted at the DB layer (see migrations V46, V47) but is a no-op if the application role is the table owner in PostgreSQL. Do not rely on DB-enforced immutability — the constraint is application-layer only. diff --git a/docs/architecture/c4/l3-backend-timeline.puml b/docs/architecture/c4/l3-backend-timeline.puml new file mode 100644 index 00000000..a1c6d4a1 --- /dev/null +++ b/docs/architecture/c4/l3-backend-timeline.puml @@ -0,0 +1,24 @@ +@startuml +!include + +title Component Diagram: API Backend — Timeline (Zeitstrahl) + +ContainerDb(db, "PostgreSQL", "PostgreSQL 16") + +System_Boundary(backend, "API Backend (Spring Boot)") { + Component(timelineRepo, "TimelineEventRepository", "Spring Data JPA", "Reads and writes TimelineEvent rows and their persons/documents join tables (timeline_event_persons, timeline_event_documents). Issue #774 ships the repository empty; the per-person filter query lands in a later issue.") + + Component(timelineSvc, "TimelineEventService", "Spring Service (planned, issue 3)", "Will own curated-event CRUD: assemble TimelineEventView/Summary inside the transaction (lazy ManyToMany + open-in-view=false, per ADR-036/ADR-040), populate createdBy/updatedBy from the session principal, and translate optimistic-lock conflicts to DomainException.conflict.") + Component(timelineCtrl, "TimelineEventController", "Spring MVC (planned, issue 3)", "Will expose /api/timeline reads (READ_ALL) and writes (WRITE_ALL). createdBy/updatedBy are never bound from request bodies (CWE-639).") +} + +System_Ext(documentDomain, "Document domain", "Provides DatePrecision (shared value type) and Document references for linked letters") +System_Ext(personDomain, "Person domain", "Provides Person references for who an event involves") + +Rel(timelineRepo, db, "SQL queries", "JDBC") +Rel(timelineSvc, timelineRepo, "Reads / writes events (planned)") +Rel(timelineCtrl, timelineSvc, "Delegates to (planned)") +Rel(timelineRepo, personDomain, "References persons via join table") +Rel(timelineRepo, documentDomain, "References documents via join table") + +@enduml diff --git a/docs/architecture/db/db-orm.puml b/docs/architecture/db/db-orm.puml index 8b54f2cc..f84d2112 100644 --- a/docs/architecture/db/db-orm.puml +++ b/docs/architecture/db/db-orm.puml @@ -1,6 +1,6 @@ @startuml db-orm -' Schema source: Flyway V1–V76 (excl. V37, V43 — intentionally removed) -' Schema as of: V76 (2026-06-12) +' Schema source: Flyway V1–V77 (excl. V37, V43 — intentionally removed) +' Schema as of: V77 (2026-06-12) ' ⚠ This is a versioned snapshot. Update when the schema changes significantly. hide circle @@ -386,6 +386,39 @@ package "Supporting" { } } +' ── Timeline (Zeitstrahl) ── +package "Timeline" { + + entity timeline_events { + id : 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 + == + CHECK ((date_precision = 'RANGE') = (event_date_end IS NOT NULL)) + CHECK (date_precision <> 'UNKNOWN') + } + + entity timeline_event_persons { + timeline_event_id : UUID <> + person_id : UUID <> + } + + entity timeline_event_documents { + timeline_event_id : UUID <> + document_id : UUID <> + } +} + ' Auth relationships app_users_groups }o--|| app_users : app_user_id app_users_groups }o--|| user_groups : group_id @@ -449,4 +482,10 @@ geschichten_persons }o--|| persons : person_id journey_items }o--|| geschichten : geschichte_id (ON DELETE CASCADE) journey_items }o--o| documents : document_id (ON DELETE SET NULL) +' Timeline relationships +timeline_event_persons }o--|| timeline_events : timeline_event_id (ON DELETE CASCADE) +timeline_event_persons }o--|| persons : person_id (ON DELETE CASCADE) +timeline_event_documents }o--|| timeline_events : timeline_event_id (ON DELETE CASCADE) +timeline_event_documents }o--|| documents : document_id (ON DELETE CASCADE) + @enduml diff --git a/docs/architecture/db/db-relationships.puml b/docs/architecture/db/db-relationships.puml index 3695a9d6..eccc8501 100644 --- a/docs/architecture/db/db-relationships.puml +++ b/docs/architecture/db/db-relationships.puml @@ -6,6 +6,7 @@ ' precision/attribution fields); no new FK relationships, so this diagram is unchanged. ' Note: V76 swaps persons.birth_year/death_year for birth_date/death_date + ' precision columns; columns only, no new FK relationships, diagram unchanged. +' Note: V77 adds the timeline_events table + two join tables (Timeline package below). hide circle skinparam linetype ortho @@ -71,6 +72,13 @@ package "Supporting" { entity journey_items } +' ── Timeline (Zeitstrahl) ── +package "Timeline" { + entity timeline_events + entity timeline_event_persons + entity timeline_event_documents +} + ' Auth relationships app_users_groups }o--|| app_users : app_user_id app_users_groups }o--|| user_groups : group_id @@ -136,4 +144,11 @@ journey_items }o--o| documents : document_id (ON DELETE SET NULL) note right of journey_items : partial UNIQUE (geschichte_id, document_id)\nWHERE document_id IS NOT NULL (V74) note right of geschichten : CHECK length(body) <= 4000\nfor type = JOURNEY (V75) +' Timeline relationships (V77) +timeline_event_persons }o--|| timeline_events : timeline_event_id (ON DELETE CASCADE) +timeline_event_persons }o--|| persons : person_id (ON DELETE CASCADE) +timeline_event_documents }o--|| timeline_events : timeline_event_id (ON DELETE CASCADE) +timeline_event_documents }o--|| documents : document_id (ON DELETE CASCADE) +note right of timeline_events : CHECK event_date_end non-null IFF RANGE\nCHECK date_precision <> 'UNKNOWN' (V77) + @enduml