diff --git a/backend/src/main/java/org/raddatz/familienarchiv/model/Geschichte.java b/backend/src/main/java/org/raddatz/familienarchiv/model/Geschichte.java new file mode 100644 index 00000000..1e1c0116 --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/model/Geschichte.java @@ -0,0 +1,69 @@ +package org.raddatz.familienarchiv.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Entity +@Table(name = "geschichten") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Geschichte { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private UUID id; + + @Column(nullable = false) + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String title; + + @Column(columnDefinition = "TEXT") + private String body; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Builder.Default + private GeschichteStatus status = GeschichteStatus.DRAFT; + + @ManyToOne + @JoinColumn(name = "author_id") + private AppUser author; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "geschichten_persons", + joinColumns = @JoinColumn(name = "geschichte_id"), + inverseJoinColumns = @JoinColumn(name = "person_id")) + @Builder.Default + private Set persons = new HashSet<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "geschichten_documents", + joinColumns = @JoinColumn(name = "geschichte_id"), + inverseJoinColumns = @JoinColumn(name = "document_id")) + @Builder.Default + private Set documents = new HashSet<>(); + + @CreationTimestamp + @Column(updatable = false) + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updatedAt; + + @Column(name = "published_at") + private LocalDateTime publishedAt; +} diff --git a/backend/src/main/java/org/raddatz/familienarchiv/model/GeschichteStatus.java b/backend/src/main/java/org/raddatz/familienarchiv/model/GeschichteStatus.java new file mode 100644 index 00000000..5e85cc22 --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/model/GeschichteStatus.java @@ -0,0 +1,6 @@ +package org.raddatz.familienarchiv.model; + +public enum GeschichteStatus { + DRAFT, + PUBLISHED +} diff --git a/backend/src/main/resources/db/migration/V58__add_geschichten.sql b/backend/src/main/resources/db/migration/V58__add_geschichten.sql new file mode 100644 index 00000000..14bc8eee --- /dev/null +++ b/backend/src/main/resources/db/migration/V58__add_geschichten.sql @@ -0,0 +1,34 @@ +-- Geschichten: blog-like family memory stories linked to persons and documents (issue #381). +-- BLOG_WRITE permission gates authoring; DRAFT stories are never returned to readers. + +CREATE TABLE geschichten ( + id UUID PRIMARY KEY, + title VARCHAR(255) NOT NULL, + body TEXT, + status VARCHAR(32) NOT NULL, + author_id UUID REFERENCES app_users (id) ON DELETE SET NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + published_at TIMESTAMP +); + +CREATE TABLE geschichten_persons ( + geschichte_id UUID NOT NULL REFERENCES geschichten (id) ON DELETE CASCADE, + person_id UUID NOT NULL REFERENCES persons (id) ON DELETE CASCADE, + PRIMARY KEY (geschichte_id, person_id) +); + +CREATE TABLE geschichten_documents ( + geschichte_id UUID NOT NULL REFERENCES geschichten (id) ON DELETE CASCADE, + document_id UUID NOT NULL REFERENCES documents (id) ON DELETE CASCADE, + PRIMARY KEY (geschichte_id, document_id) +); + +-- Index page query: WHERE status = 'PUBLISHED' ORDER BY published_at DESC. +CREATE INDEX idx_geschichten_published + ON geschichten (published_at DESC) + WHERE status = 'PUBLISHED'; + +-- Reverse-lookup indexes for the ?personId / ?documentId filters. +CREATE INDEX idx_geschichten_persons_person ON geschichten_persons (person_id); +CREATE INDEX idx_geschichten_documents_document ON geschichten_documents (document_id);