From 2ad5c36e3cdb82ff7ea693b62fd7b523897057eb Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 8 Jun 2026 16:32:50 +0200 Subject: [PATCH] feat(geschichte): extend JourneyItemRepository and add item DTOs Repository: findByIdAndGeschichteId (IDOR-safe lookup), findByGeschichteIdOrderByPosition, findIdsByGeschichteId (Set for set-equality reorder check), findMaxPositionByGeschichteId, countByGeschichteId. DTOs: JourneyItemCreateDTO (documentId+note), JourneyItemUpdateDTO (JsonNullable note for 3-way PATCH), JourneyReorderDTO (List). Co-Authored-By: Claude Sonnet 4.6 --- .../journeyitem/JourneyItemCreateDTO.java | 12 +++++++++++ .../journeyitem/JourneyItemRepository.java | 21 +++++++++++++++++++ .../journeyitem/JourneyItemUpdateDTO.java | 16 ++++++++++++++ .../journeyitem/JourneyReorderDTO.java | 12 +++++++++++ 4 files changed, 61 insertions(+) create mode 100644 backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemCreateDTO.java create mode 100644 backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemUpdateDTO.java create mode 100644 backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyReorderDTO.java diff --git a/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemCreateDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemCreateDTO.java new file mode 100644 index 00000000..9a7c420d --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemCreateDTO.java @@ -0,0 +1,12 @@ +package org.raddatz.familienarchiv.geschichte.journeyitem; + +import lombok.Data; + +import java.util.UUID; + +/** Input for POST /api/geschichten/{id}/items. Both fields optional; at least one must be present. */ +@Data +public class JourneyItemCreateDTO { + private UUID documentId; + private String note; +} diff --git a/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemRepository.java index 5534195b..4a56ee07 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemRepository.java @@ -1,13 +1,34 @@ package org.raddatz.familienarchiv.geschichte.journeyitem; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.UUID; @Repository public interface JourneyItemRepository extends JpaRepository { List findAllByGeschichteId(UUID geschichteId); + + /** Returns items ordered by position ASC for the read-model assembly path. */ + List findByGeschichteIdOrderByPosition(UUID geschichteId); + + /** IDOR-safe lookup: returns empty when itemId exists but belongs to a different journey. */ + Optional findByIdAndGeschichteId(UUID id, UUID geschichteId); + + /** Returns only the IDs — used for set-equality check in reorder. */ + @Query("SELECT i.id FROM JourneyItem i WHERE i.geschichte.id = :geschichteId") + Set findIdsByGeschichteId(@Param("geschichteId") UUID geschichteId); + + /** MAX position for computing the next append position; returns empty when journey has no items. */ + @Query("SELECT MAX(i.position) FROM JourneyItem i WHERE i.geschichte.id = :geschichteId") + Optional findMaxPositionByGeschichteId(@Param("geschichteId") UUID geschichteId); + + /** COUNT for the 100-item cap check — COUNT(*)-based, never MAX(position)-derived. */ + long countByGeschichteId(UUID geschichteId); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemUpdateDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemUpdateDTO.java new file mode 100644 index 00000000..db8c4e6e --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemUpdateDTO.java @@ -0,0 +1,16 @@ +package org.raddatz.familienarchiv.geschichte.journeyitem; + +import lombok.Data; +import org.openapitools.jackson.nullable.JsonNullable; + +/** + * Input for PATCH /api/geschichten/{id}/items/{itemId}. + * JsonNullable enables three-way semantics: + * absent → field not present in JSON → leave unchanged + * null → {"note": null} → clear the note field + * string → {"note": "text"} → set the note + */ +@Data +public class JourneyItemUpdateDTO { + private JsonNullable note = JsonNullable.undefined(); +} diff --git a/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyReorderDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyReorderDTO.java new file mode 100644 index 00000000..b11f343b --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyReorderDTO.java @@ -0,0 +1,12 @@ +package org.raddatz.familienarchiv.geschichte.journeyitem; + +import lombok.Data; + +import java.util.List; +import java.util.UUID; + +/** Input for PUT /api/geschichten/{id}/items/reorder. */ +@Data +public class JourneyReorderDTO { + private List itemIds; +}