diff --git a/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEvent.java b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEvent.java new file mode 100644 index 00000000..86155500 --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/timeline/TimelineEvent.java @@ -0,0 +1,129 @@ +package org.raddatz.familienarchiv.timeline; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.raddatz.familienarchiv.document.DatePrecision; +import org.raddatz.familienarchiv.document.Document; +import org.raddatz.familienarchiv.person.Person; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * A curated event on the family timeline (Zeitstrahl). Unlike a {@link Document}, which is + * OCR-derived, a {@code TimelineEvent} is authored by curators — hence the optimistic-lock + * {@link #version} and the {@link #createdBy}/{@link #updatedBy} audit trail that + * {@code Document} lacks. + * + *
The date block ({@link #eventDate}, {@link #precision}, {@link #eventDateEnd}) mirrors
+ * {@code Document}'s so events and letters share one rendering path. The mirror applies to
+ * the date block only — the audit footprint deliberately diverges (see ADR-040).
+ */
+@Entity
+@Table(name = "timeline_events")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TimelineEvent {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
+ private UUID id;
+
+ @Column(nullable = false)
+ @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
+ private String title;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false, length = 16)
+ @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
+ private EventType type;
+
+ /** The most precise date known for the event. Always present — a curated event is never undated. */
+ @Column(name = "event_date", nullable = false)
+ @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDate eventDate;
+
+ /**
+ * Precision of {@link #eventDate}. Reuses {@code document.DatePrecision} (one rendering
+ * path; see ADR-025 / ADR-040). Every value except {@code UNKNOWN} is legal for a curated
+ * event — including {@code SEASON} ("Sommer 1914") and {@code APPROX} ("ca. 1914"). The DB
+ * CHECK forbids exactly {@code UNKNOWN}; do not narrow it further.
+ */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "date_precision", nullable = false, length = 16)
+ @Builder.Default
+ @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
+ private DatePrecision precision = DatePrecision.YEAR;
+
+ /** Range end — non-null iff {@link #precision} is {@code RANGE} (DB CHECK, both directions). */
+ @Column(name = "event_date_end")
+ private LocalDate eventDateEnd;
+
+ @Column(columnDefinition = "TEXT")
+ private String description;
+
+ /** People the event involves. */
+ @ManyToMany(fetch = FetchType.LAZY)
+ @JoinTable(name = "timeline_event_persons",
+ joinColumns = @JoinColumn(name = "timeline_event_id"),
+ inverseJoinColumns = @JoinColumn(name = "person_id"))
+ @BatchSize(size = 50)
+ @Builder.Default
+ private Set