feat(notifications): add documentTitle to NotificationDTO via DocumentService lookup

Notification rows in the history page need the document title. Added
findTitlesByIds(Collection<UUID>) 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 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-29 13:50:19 +02:00
parent 69632c6bb6
commit 7026bdb780
4 changed files with 34 additions and 2 deletions

View File

@@ -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
) {}

View File

@@ -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<Document, UUID>, JpaSp
List<Document> findByFileHashIsNullAndFilePathIsNotNull();
@Query("SELECT d.id, d.title FROM Document d WHERE d.id IN :ids")
List<Object[]> findIdAndTitleByIdIn(@Param("ids") Collection<UUID> ids);
long countByMetadataCompleteFalse();
List<Document> findByMetadataCompleteFalse(Sort sort);

View File

@@ -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<UUID, String> findTitlesByIds(Collection<UUID> ids) {
if (ids.isEmpty()) return Map.of();
Map<UUID, String> 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.

View File

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