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