feat/issue-751-journey-item-crud-api #791

Merged
marcel merged 69 commits from feat/issue-751-journey-item-crud-api into feat/issue-750-lesereisen-data-model 2026-06-09 12:08:58 +02:00
4 changed files with 61 additions and 0 deletions
Showing only changes of commit 2ad5c36e3c - Show all commits

View File

@@ -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;
}

View File

@@ -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<JourneyItem, UUID> {
List<JourneyItem> findAllByGeschichteId(UUID geschichteId);
/** Returns items ordered by position ASC for the read-model assembly path. */
List<JourneyItem> findByGeschichteIdOrderByPosition(UUID geschichteId);
/** IDOR-safe lookup: returns empty when itemId exists but belongs to a different journey. */
Optional<JourneyItem> 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<UUID> 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<Integer> findMaxPositionByGeschichteId(@Param("geschichteId") UUID geschichteId);
/** COUNT for the 100-item cap check — COUNT(*)-based, never MAX(position)-derived. */
long countByGeschichteId(UUID geschichteId);
}

View File

@@ -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<String> note = JsonNullable.undefined();
}

View File

@@ -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<UUID> itemIds;
}