From 0eea19c0d4d79bb19907caacf2c06ebd69658dc2 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 13 Jun 2026 10:45:45 +0200 Subject: [PATCH] feat(timeline): add TimelineEventRequest input DTO Flat input DTO with Bean Validation (@NotBlank/@NotNull/@Size). createdBy/ updatedBy deliberately absent (server-populated; CWE-639). version is an optional concurrency token, exempt from the server-only audit rule. Per #775. Co-Authored-By: Claude Fable 5 --- .../timeline/TimelineEventRequest.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRequest.java diff --git a/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRequest.java b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRequest.java new file mode 100644 index 00000000..dd25b3e9 --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEventRequest.java @@ -0,0 +1,40 @@ +package org.raddatz.familienarchiv.timeline; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import org.raddatz.familienarchiv.document.DatePrecision; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +/** + * Flat input DTO for creating/updating a {@link TimelineEvent}. Bean Validation fires at the + * controller boundary (via {@code @Valid}) and produces a 400 {@code VALIDATION_ERROR} for the + * presence/size constraints below; cross-field rules (the RANGE invariant), date normalization, + * id dedupe, and the title-length structured-error guard live in {@code TimelineEventService}. + * + *

{@code createdBy}/{@code updatedBy} are intentionally absent. Authorship is + * server-populated from the session principal only — accepting it from the body would be an + * authorship-forgery / mass-assignment vector (CWE-639; see ADR-040 §7). + * + * @param version optional optimistic-lock concurrency token (the {@code @Version} the client last + * saw), applied on update only. This is a concurrency token, not + * an authorship field, so it is deliberately exempt from the §7 server-only audit rule. + * Null on update means "no concurrency check" (last-write-wins). No range validation — + * a stale/negative value is simply a mismatch the lock rejects at flush; the lock, not + * a validator, is the control. + */ +public record TimelineEventRequest( + @NotBlank @Size(max = 255) String title, + @NotNull EventType type, + @NotNull LocalDate eventDate, + DatePrecision precision, + LocalDate eventDateEnd, + @Size(max = 5000) String description, + Long version, + @Size(max = 50) List personIds, + @Size(max = 50) List documentIds +) {}