feat(timeline): add PersonService.getPersonsByGeneration + DocumentService.getAllForTimeline

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 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-13 16:06:03 +02:00
parent 184fc9814a
commit de30f66a2d
6 changed files with 50 additions and 0 deletions

View File

@@ -56,6 +56,11 @@ public interface DocumentRepository extends JpaRepository<Document, UUID>, JpaSp
// Prüft effizient, ob ein Dateiname schon existiert (gibt true/false zurück) // Prüft effizient, ob ein Dateiname schon existiert (gibt true/false zurück)
boolean existsByOriginalFilename(String originalFilename); 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<Document> findAllForTimeline();
// lazy @BatchSize(50) fallback active; see ADR-022 // lazy @BatchSize(50) fallback active; see ADR-022
@EntityGraph("Document.full") @EntityGraph("Document.full")
List<Document> findBySenderId(UUID senderId); List<Document> findBySenderId(UUID senderId);

View File

@@ -1051,6 +1051,10 @@ public class DocumentService {
return documentRepository.findDocumentsWithoutVersions(); return documentRepository.findDocumentsWithoutVersions();
} }
public List<Document> getAllForTimeline() {
return documentRepository.findAllForTimeline();
}
public List<Document> getDocumentsBySender(UUID senderId) { public List<Document> getDocumentsBySender(UUID senderId) {
return documentRepository.findBySenderId(senderId); return documentRepository.findBySenderId(senderId);
} }

View File

@@ -242,4 +242,7 @@ public interface PersonRepository extends JpaRepository<Person, UUID> {
) )
""", nativeQuery = true) """, nativeQuery = true)
void insertMissingReceiverReference(@Param("source") UUID source, @Param("target") UUID target); 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<Person> findByGeneration(Integer generation);
} }

View File

@@ -210,6 +210,10 @@ public class PersonService {
return personRepository.findByFamilyMemberTrueOrderByLastNameAscFirstNameAsc(); return personRepository.findByFamilyMemberTrueOrderByLastNameAscFirstNameAsc();
} }
public List<Person> getPersonsByGeneration(Integer generation) {
return personRepository.findByGeneration(generation);
}
@Transactional @Transactional
public Person setFamilyMember(UUID personId, boolean familyMember) { public Person setFamilyMember(UUID personId, boolean familyMember) {
Person person = getById(personId); Person person = getById(personId);

View File

@@ -2943,4 +2943,17 @@ class DocumentServiceTest {
assertThat(result.buckets()).isEmpty(); assertThat(result.buckets()).isEmpty();
verify(documentRepository, org.mockito.Mockito.never()).findAll(any(Specification.class)); 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<Document> result = documentService.getAllForTimeline();
assertThat(result).containsExactly(doc);
verify(documentRepository).findAllForTimeline();
}
} }

View File

@@ -1105,4 +1105,25 @@ class PersonServiceTest {
assertThat(result.direct()).hasSize(1); assertThat(result.direct()).hasSize(1);
assertThat(result.partial()).isEmpty(); 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<Person> result = personService.getPersonsByGeneration(2);
assertThat(result).containsExactly(p);
}
@Test
void getPersonsByGeneration_returns_emptyList_when_no_match() {
when(personRepository.findByGeneration(99)).thenReturn(List.of());
List<Person> result = personService.getPersonsByGeneration(99);
assertThat(result).isEmpty();
}
} }