From de30f66a2da2e1bbd3469152a3f3be55d8f97111 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 13 Jun 2026 16:06:03 +0200 Subject: [PATCH] feat(timeline): add PersonService.getPersonsByGeneration + DocumentService.getAllForTimeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PersonRepository.findByGeneration(Integer) — boxed to match nullable entity field. DocumentRepository.findAllForTimeline() — Document.list entity-graph, single query. Both services delegate with one-liner methods. Refs #777 Co-Authored-By: Claude Sonnet 4.6 --- .../document/DocumentRepository.java | 5 +++++ .../document/DocumentService.java | 4 ++++ .../person/PersonRepository.java | 3 +++ .../familienarchiv/person/PersonService.java | 4 ++++ .../document/DocumentServiceTest.java | 13 ++++++++++++ .../person/PersonServiceTest.java | 21 +++++++++++++++++++ 6 files changed, 50 insertions(+) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentRepository.java index 72a9fd03..c33480f1 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentRepository.java @@ -56,6 +56,11 @@ public interface DocumentRepository extends JpaRepository, JpaSp // Prüft effizient, ob ein Dateiname schon existiert (gibt true/false zurück) boolean existsByOriginalFilename(String originalFilename); + // Bulk-fetch for global timeline path — single query with sender+receivers eager-loaded. + @EntityGraph("Document.list") + @Query("SELECT d FROM Document d") + List findAllForTimeline(); + // lazy – @BatchSize(50) fallback active; see ADR-022 @EntityGraph("Document.full") List findBySenderId(UUID senderId); diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java index 61b578de..99e48f48 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java @@ -1051,6 +1051,10 @@ public class DocumentService { return documentRepository.findDocumentsWithoutVersions(); } + public List getAllForTimeline() { + return documentRepository.findAllForTimeline(); + } + public List getDocumentsBySender(UUID senderId) { return documentRepository.findBySenderId(senderId); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/person/PersonRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/person/PersonRepository.java index 18c2e8a4..0b12270e 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/person/PersonRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/person/PersonRepository.java @@ -242,4 +242,7 @@ public interface PersonRepository extends JpaRepository { ) """, nativeQuery = true) void insertMissingReceiverReference(@Param("source") UUID source, @Param("target") UUID target); + + // Boxed Integer — matches the nullable person.generation column (primitive int would reject null rows). + List findByGeneration(Integer generation); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/person/PersonService.java b/backend/src/main/java/org/raddatz/familienarchiv/person/PersonService.java index e7a9ea1f..f51d2d47 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/person/PersonService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/person/PersonService.java @@ -210,6 +210,10 @@ public class PersonService { return personRepository.findByFamilyMemberTrueOrderByLastNameAscFirstNameAsc(); } + public List getPersonsByGeneration(Integer generation) { + return personRepository.findByGeneration(generation); + } + @Transactional public Person setFamilyMember(UUID personId, boolean familyMember) { Person person = getById(personId); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java index eadad6ac..fb77f39e 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java @@ -2943,4 +2943,17 @@ class DocumentServiceTest { assertThat(result.buckets()).isEmpty(); verify(documentRepository, org.mockito.Mockito.never()).findAll(any(Specification.class)); } + + // --- getAllForTimeline --- + + @Test + void getAllForTimeline_delegates_bulk_fetch_to_repository() { + Document doc = Document.builder().id(UUID.randomUUID()).title("Brief").build(); + when(documentRepository.findAllForTimeline()).thenReturn(List.of(doc)); + + List result = documentService.getAllForTimeline(); + + assertThat(result).containsExactly(doc); + verify(documentRepository).findAllForTimeline(); + } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceTest.java index 161e359c..f253f0a0 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceTest.java @@ -1105,4 +1105,25 @@ class PersonServiceTest { assertThat(result.direct()).hasSize(1); assertThat(result.partial()).isEmpty(); } + + // --- getPersonsByGeneration --- + + @Test + void getPersonsByGeneration_delegates_to_repository() { + Person p = Person.builder().id(UUID.randomUUID()).lastName("Müller").generation(2).build(); + when(personRepository.findByGeneration(2)).thenReturn(List.of(p)); + + List result = personService.getPersonsByGeneration(2); + + assertThat(result).containsExactly(p); + } + + @Test + void getPersonsByGeneration_returns_emptyList_when_no_match() { + when(personRepository.findByGeneration(99)).thenReturn(List.of()); + + List result = personService.getPersonsByGeneration(99); + + assertThat(result).isEmpty(); + } }