From 7026bdb780ade98e1d961d941ce0bd45514188d4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 29 Mar 2026 13:50:19 +0200 Subject: [PATCH] feat(notifications): add documentTitle to NotificationDTO via DocumentService lookup Notification rows in the history page need the document title. Added findTitlesByIds(Collection) to DocumentService (one query via a new JPQL projection on DocumentRepository). NotificationService.getNotifications() now fetches all titles for the page in a single extra query and maps them into the DTO. documentTitle is null when the document has been deleted. Co-Authored-By: Claude Sonnet 4.6 --- .../familienarchiv/dto/NotificationDTO.java | 3 ++- .../repository/DocumentRepository.java | 5 +++++ .../familienarchiv/service/DocumentService.java | 12 ++++++++++++ .../controller/NotificationControllerTest.java | 16 +++++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/dto/NotificationDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/dto/NotificationDTO.java index 2a79864a..537bf0ee 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/dto/NotificationDTO.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/dto/NotificationDTO.java @@ -14,5 +14,6 @@ public record NotificationDTO( UUID annotationId, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean read, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) LocalDateTime createdAt, - String actorName + String actorName, + String documentTitle ) {} diff --git a/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java index 5614fa3b..c7db9cac 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentRepository.java @@ -12,7 +12,9 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDate; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -44,6 +46,9 @@ public interface DocumentRepository extends JpaRepository, JpaSp List findByFileHashIsNullAndFilePathIsNotNull(); + @Query("SELECT d.id, d.title FROM Document d WHERE d.id IN :ids") + List findIdAndTitleByIdIn(@Param("ids") Collection ids); + long countByMetadataCompleteFalse(); List findByMetadataCompleteFalse(Sort sort); diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java index e1c7434d..55c5ab98 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java @@ -25,8 +25,11 @@ import java.security.NoSuchAlgorithmException; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -46,6 +49,15 @@ public class DocumentService { public record StoreResult(Document document, boolean isNew) {} + public Map findTitlesByIds(Collection ids) { + if (ids.isEmpty()) return Map.of(); + Map titles = new HashMap<>(); + for (Object[] row : documentRepository.findIdAndTitleByIdIn(ids)) { + titles.put((UUID) row[0], (String) row[1]); + } + return titles; + } + /** * Lädt eine Datei hoch. * - Prüft, ob ein Eintrag (aus Excel) schon existiert. diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java index d7cf088b..c85d8e12 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java @@ -75,7 +75,7 @@ class NotificationControllerTest { AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); NotificationDTO dto = new NotificationDTO( UUID.randomUUID(), NotificationType.REPLY, UUID.randomUUID(), - UUID.randomUUID(), null, false, LocalDateTime.now(), "Anna Smith"); + UUID.randomUUID(), null, false, LocalDateTime.now(), "Anna Smith", "Testdokument"); when(userService.findByUsername("testuser")).thenReturn(user); when(notificationService.getNotifications(eq(USER_ID), any(), any(), any())) @@ -123,6 +123,20 @@ class NotificationControllerTest { .andExpect(status().isBadRequest()); } + @Test + @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) + void getNotifications_returns400_whenSizeExceedsMaximum() throws Exception { + mockMvc.perform(get("/api/notifications").param("size", "200")) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) + void getNotifications_returns400_whenSizeIsZero() throws Exception { + mockMvc.perform(get("/api/notifications").param("size", "0")) + .andExpect(status().isBadRequest()); + } + // ─── POST /api/notifications/read-all ──────────────────────────────────── @Test