diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java index add2a9f4..990218b5 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentController.java @@ -3,6 +3,7 @@ package org.raddatz.familienarchiv.document; import java.io.IOException; import java.time.LocalDate; import java.util.ArrayList; +import java.util.concurrent.TimeUnit; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -48,6 +49,7 @@ import org.raddatz.familienarchiv.filestorage.FileService; import org.raddatz.familienarchiv.user.UserService; import org.springframework.data.domain.Sort; import org.springframework.security.core.Authentication; +import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -388,6 +390,16 @@ public class DocumentController { return ResponseEntity.ok(documentService.searchDocuments(q, from, to, senderId, receiverId, tags, tagQ, status, sort, dir, operator, pageable)); } + @GetMapping("/density") + public ResponseEntity density( + @RequestParam(required = false) LocalDate from, + @RequestParam(required = false) LocalDate to) { + DocumentDensityResult result = documentService.getDensity(from, to); + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES).cachePrivate()) + .body(result); + } + // --- TRAINING LABELS --- public record TrainingLabelRequest(String label, boolean enrolled) {} diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java index ad07afb7..484d6259 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java @@ -1240,4 +1240,60 @@ class DocumentControllerTest { .andExpect(jsonPath("$.errors[0].message").value( org.hamcrest.Matchers.containsString("not found"))); } + + // ─── GET /api/documents/density ─────────────────────────────────────────── + + @Test + void density_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(get("/api/documents/density")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void density_returns200_withResultBody_whenAuthenticated() throws Exception { + when(documentService.getDensity(any(), any())).thenReturn( + new DocumentDensityResult( + List.of(new MonthBucket("1915-08", 2), new MonthBucket("1915-09", 1)), + java.time.LocalDate.of(1915, 8, 3), + java.time.LocalDate.of(1915, 9, 1))); + + mockMvc.perform(get("/api/documents/density")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.buckets").isArray()) + .andExpect(jsonPath("$.buckets[0].month").value("1915-08")) + .andExpect(jsonPath("$.buckets[0].count").value(2)) + .andExpect(jsonPath("$.minDate").value("1915-08-03")) + .andExpect(jsonPath("$.maxDate").value("1915-09-01")); + } + + @Test + @WithMockUser + void density_emitsPrivateCacheControlHeader() throws Exception { + when(documentService.getDensity(any(), any())).thenReturn( + new DocumentDensityResult(List.of(), null, null)); + + mockMvc.perform(get("/api/documents/density")) + .andExpect(status().isOk()) + .andExpect(header().string("Cache-Control", + org.hamcrest.Matchers.containsString("max-age=300"))) + .andExpect(header().string("Cache-Control", + org.hamcrest.Matchers.containsString("private"))); + } + + @Test + @WithMockUser + void density_passesFromAndToParametersToService() throws Exception { + when(documentService.getDensity(any(), any())).thenReturn( + new DocumentDensityResult(List.of(), null, null)); + + mockMvc.perform(get("/api/documents/density") + .param("from", "1914-01-01") + .param("to", "1918-12-31")) + .andExpect(status().isOk()); + + verify(documentService).getDensity( + java.time.LocalDate.of(1914, 1, 1), + java.time.LocalDate.of(1918, 12, 31)); + } }