diff --git a/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEvent.java b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEvent.java index e9978022..d6be0355 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEvent.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEvent.java @@ -122,7 +122,7 @@ public class TimelineEvent { private LocalDateTime updatedAt; /** - * Optimistic-lock version for the multi-curator edit flow (issue 3). Object {@code Long} + * Optimistic-lock version for the multi-curator edit flow (#775). Object {@code Long} * (not primitive) so it is {@code null} before first persist; Hibernate sets {@code 0} on * insert. A concurrent-write conflict must be translated to {@code DomainException.conflict} * in the service layer (ADR-040) — otherwise it surfaces as HTTP 500 with Hibernate diff --git a/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRepository.java index 524d7482..db3b923b 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRepository.java @@ -5,5 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.UUID; public interface TimelineEventRepository extends JpaRepository { - // TODO(issue 5): findByPersonsContaining(Person) needed for the per-person filter + // TODO(#777): findByPersonsContaining(Person) needed for the per-person filter } diff --git a/docs/adr/040-timeline-domain-data-model.md b/docs/adr/040-timeline-domain-data-model.md index b832a7eb..5ae61f17 100644 --- a/docs/adr/040-timeline-domain-data-model.md +++ b/docs/adr/040-timeline-domain-data-model.md @@ -23,11 +23,11 @@ audit footprint deliberately diverges (see below). Curated timeline events are their own concern, not a Lesereise/`Geschichte` subtype. The domain owns `TimelineEvent`, `EventType`, and `TimelineEventRepository`. -### 2. Responses (issue 3) will be views, not serialized entities +### 2. Responses (#775) will be views, not serialized entities `TimelineEvent` has two LAZY `ManyToMany` collections (`persons`, `documents`) and `open-in-view` is `false` — exactly the shape that motivated ADR-036 for `geschichte`. -Issue 3 must assemble `TimelineEventView`/`TimelineEventSummary` inside the service +#775 must assemble `TimelineEventView`/`TimelineEventSummary` inside the service transaction; a serialized entity is a 500 waiting to happen. Decided up front so it is not retrofitted later. @@ -64,7 +64,7 @@ inferred. This divergence is deliberate: a future "bug fix" must not relax it to `Document` has neither a version nor a creator. A curated entity edited by multiple curators warrants real protection, so `TimelineEvent` adds: -- `@Version Long version` — optimistic locking for the multi-curator edit flow (issue 3). +- `@Version Long version` — optimistic locking for the multi-curator edit flow (#775). Object `Long` (not primitive) so it is `null` before first persist; Hibernate sets `0` on insert. The service **must** catch `ObjectOptimisticLockingFailureException` and translate it to `DomainException.conflict(...)`. Without that translation a concurrent-write conflict @@ -82,7 +82,7 @@ session principal before every `save()`, or the timestamp moves while the "who" ### 7. `createdBy`/`updatedBy` are server-populated only — never bound from client input Both are set from the session principal in the service, never from a request body. Binding -them from client input is an authorship-forgery / mass-assignment vector (CWE-639). Issue 3's +them from client input is an authorship-forgery / mass-assignment vector (CWE-639). #775's regression suite must include forgery cases on **both** write paths (`POST` body with `createdBy`, `PUT` body with `updatedBy`) — create and update are separate binding paths, so testing only one leaves half the vector open. The update test must assert `updatedBy` equals @@ -108,5 +108,5 @@ the event intact (V71/ADR-032 hardening — a person delete must never 500). `timeline_events`). No rollback script, no rollback test. - The `timeline → document.DatePrecision` compile coupling is permanent until a shared-package refactor; precedent already exists (`importing/DocumentImporter`, `person`). -- The service/controller/DTO layer (issue 3) inherits the view-assembly, optimistic-lock +- The service/controller/DTO layer (#775) inherits the view-assembly, optimistic-lock translation, forgery-guard, and permission obligations recorded above. diff --git a/docs/architecture/c4/l3-backend-timeline.puml b/docs/architecture/c4/l3-backend-timeline.puml index a1c6d4a1..b0072429 100644 --- a/docs/architecture/c4/l3-backend-timeline.puml +++ b/docs/architecture/c4/l3-backend-timeline.puml @@ -6,10 +6,10 @@ 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(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 #777.") - 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).") + Component(timelineSvc, "TimelineEventService", "Spring Service (planned, #775)", "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, #775)", "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")