feat(journey-editor): JourneyEditor frontend — issue #753 #792

Open
marcel wants to merge 82 commits from feat/issue-753-journey-editor into feat/issue-750-lesereisen-data-model
4 changed files with 25 additions and 0 deletions
Showing only changes of commit 353945c952 - Show all commits

View File

@@ -128,6 +128,8 @@ public enum ErrorCode {
JOURNEY_ITEM_POSITION_CONFLICT,
/** The journey already has the maximum allowed number of items (100). 400 */
JOURNEY_AT_CAPACITY,
/** The document is already present in this journey — duplicate items are not allowed. 409 */
JOURNEY_DOCUMENT_ALREADY_ADDED,
/** The Geschichte is not of type JOURNEY — journey-item operations are not allowed on it. 400 */
GESCHICHTE_TYPE_MISMATCH,

View File

@@ -30,6 +30,9 @@ public interface JourneyItemRepository extends JpaRepository<JourneyItem, UUID>
/** COUNT for the 100-item cap check — COUNT(*)-based, never MAX(position)-derived. */
long countByGeschichteId(UUID geschichteId);
/** Dedup guard: true when the document is already linked to this journey. */
boolean existsByGeschichteIdAndDocumentId(UUID geschichteId, UUID documentId);
/**
* Loads journey items with their linked Document in a single JOIN FETCH query,
* eliminating the N+1 SELECT that would occur when accessing item.getDocument()

View File

@@ -69,6 +69,10 @@ public class JourneyItemService {
Document doc = null;
if (dto.getDocumentId() != null) {
if (journeyItemRepository.existsByGeschichteIdAndDocumentId(geschichteId, dto.getDocumentId())) {
throw DomainException.conflict(ErrorCode.JOURNEY_DOCUMENT_ALREADY_ADDED,
"Document already in journey: " + dto.getDocumentId());
}
doc = documentService.findSummaryByIdInternal(dto.getDocumentId());
}

View File

@@ -288,6 +288,22 @@ class JourneyItemServiceTest {
.isEqualTo(ErrorCode.JOURNEY_AT_CAPACITY));
}
@Test
void append_returns409_when_document_already_in_journey() {
Geschichte journey = journey(geschichteId);
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(1L);
when(journeyItemRepository.existsByGeschichteIdAndDocumentId(geschichteId, docId)).thenReturn(true);
JourneyItemCreateDTO dto = new JourneyItemCreateDTO();
dto.setDocumentId(docId);
assertThatThrownBy(() -> journeyItemService.append(geschichteId, dto))
.isInstanceOf(DomainException.class)
.satisfies(e -> assertThat(((DomainException) e).getCode())
.isEqualTo(ErrorCode.JOURNEY_DOCUMENT_ALREADY_ADDED));
}
@Test
void cap_is_COUNT_based_not_MAX_position_based() {
// 99 rows with MAX(position)=2000 should still accept the 100th append