From 46588522816c75c6251a24b4bf1d69f21023b294 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 21 Apr 2026 13:09:30 +0200 Subject: [PATCH] fix(comment): populate annotationId on block comments from the block postBlockComment now looks up the block via TranscriptionService and sets comment.annotationId from block.getAnnotationId(). This closes the upstream root cause of issue #276, where notifications for block comments were stored with annotationId=null, breaking the notification deep-link flow on the document detail page. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../service/CommentService.java | 4 ++ .../service/CommentServiceTest.java | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java index e6157509..dff407d2 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java @@ -8,6 +8,7 @@ import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.AppUser; import org.raddatz.familienarchiv.model.DocumentComment; +import org.raddatz.familienarchiv.model.TranscriptionBlock; import org.raddatz.familienarchiv.repository.CommentRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,6 +27,7 @@ public class CommentService { private final UserService userService; private final NotificationService notificationService; private final AuditService auditService; + private final TranscriptionService transcriptionService; public List getCommentsForDocument(UUID documentId) { List roots = @@ -46,9 +48,11 @@ public class CommentService { @Transactional public DocumentComment postBlockComment(UUID documentId, UUID blockId, String content, List mentionedUserIds, AppUser author) { + TranscriptionBlock block = transcriptionService.getBlock(documentId, blockId); DocumentComment comment = DocumentComment.builder() .documentId(documentId) .blockId(blockId) + .annotationId(block.getAnnotationId()) .content(content) .authorId(author.getId()) .authorName(resolveAuthorName(author)) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java index a6596d80..b72cc9fb 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java @@ -10,6 +10,7 @@ import org.raddatz.familienarchiv.audit.AuditService; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.model.AppUser; import org.raddatz.familienarchiv.model.DocumentComment; +import org.raddatz.familienarchiv.model.TranscriptionBlock; import org.raddatz.familienarchiv.model.UserGroup; import org.raddatz.familienarchiv.repository.CommentRepository; @@ -39,6 +40,7 @@ class CommentServiceTest { @Mock UserService userService; @Mock NotificationService notificationService; @Mock AuditService auditService; + @Mock TranscriptionService transcriptionService; @InjectMocks CommentService commentService; // ─── postComment ────────────────────────────────────────────────────────── @@ -611,6 +613,8 @@ class CommentServiceTest { AppUser author = AppUser.builder().id(UUID.randomUUID()).email("felix@example.com").firstName("Felix").lastName("B").build(); DocumentComment saved = DocumentComment.builder() .id(savedId).documentId(docId).blockId(blockId).authorName("Felix B").content("Nice").build(); + when(transcriptionService.getBlock(docId, blockId)) + .thenReturn(TranscriptionBlock.builder().id(blockId).documentId(docId).annotationId(UUID.randomUUID()).sortOrder(0).build()); when(commentRepository.save(any())).thenReturn(saved); commentService.postBlockComment(docId, blockId, "Nice", List.of(), author); @@ -643,7 +647,10 @@ class CommentServiceTest { void postBlockComment_setsBlockIdOnComment() { UUID documentId = UUID.randomUUID(); UUID blockId = UUID.randomUUID(); + UUID annotationId = UUID.randomUUID(); AppUser author = AppUser.builder().id(UUID.randomUUID()).email("felix@example.com").firstName("Felix").lastName("Brandt").build(); + when(transcriptionService.getBlock(documentId, blockId)) + .thenReturn(TranscriptionBlock.builder().id(blockId).documentId(documentId).annotationId(annotationId).sortOrder(0).build()); when(commentRepository.save(any())).thenAnswer(inv -> { DocumentComment c = inv.getArgument(0); c.setId(UUID.randomUUID()); @@ -657,4 +664,39 @@ class CommentServiceTest { assertThat(result.getDocumentId()).isEqualTo(documentId); assertThat(result.getContent()).isEqualTo("Looks like Breslau"); } + + @Test + void postBlockComment_setsAnnotationIdFromBlock() { + UUID documentId = UUID.randomUUID(); + UUID blockId = UUID.randomUUID(); + UUID annotationId = UUID.randomUUID(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("felix@example.com").firstName("Felix").lastName("Brandt").build(); + when(transcriptionService.getBlock(documentId, blockId)) + .thenReturn(TranscriptionBlock.builder().id(blockId).documentId(documentId).annotationId(annotationId).sortOrder(0).build()); + when(commentRepository.save(any())).thenAnswer(inv -> { + DocumentComment c = inv.getArgument(0); + c.setId(UUID.randomUUID()); + return c; + }); + + DocumentComment result = commentService.postBlockComment( + documentId, blockId, "Nice work", List.of(), author); + + assertThat(result.getAnnotationId()).isEqualTo(annotationId); + } + + @Test + void postBlockComment_propagatesNotFound_whenBlockDoesNotExist() { + UUID documentId = UUID.randomUUID(); + UUID blockId = UUID.randomUUID(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("felix@example.com").firstName("Felix").lastName("Brandt").build(); + when(transcriptionService.getBlock(documentId, blockId)) + .thenThrow(DomainException.notFound( + org.raddatz.familienarchiv.exception.ErrorCode.TRANSCRIPTION_BLOCK_NOT_FOUND, + "Transcription block not found: " + blockId)); + + assertThatThrownBy(() -> commentService.postBlockComment(documentId, blockId, "Hi", List.of(), author)) + .isInstanceOf(DomainException.class) + .hasMessageContaining("Transcription block not found"); + } }