diff --git a/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardController.java b/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardController.java index b7b34b7e..940733af 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardController.java @@ -1,6 +1,7 @@ package org.raddatz.familienarchiv.dashboard; import lombok.RequiredArgsConstructor; +import org.raddatz.familienarchiv.audit.AuditKind; import org.raddatz.familienarchiv.security.Permission; import org.raddatz.familienarchiv.security.RequirePermission; import org.raddatz.familienarchiv.security.SecurityUtils; @@ -9,6 +10,7 @@ import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Set; import java.util.UUID; @RestController @@ -35,8 +37,10 @@ public class DashboardController { @GetMapping("/activity") public List getActivity( Authentication authentication, - @RequestParam(defaultValue = "7") int limit) { + @RequestParam(defaultValue = "7") int limit, + @RequestParam(required = false) Set kinds) { UUID userId = SecurityUtils.requireUserId(authentication, userService); - return dashboardService.getActivity(userId, Math.min(limit, 40)); + Set effectiveKinds = (kinds == null || kinds.isEmpty()) ? AuditKind.ROLLUP_ELIGIBLE : kinds; + return dashboardService.getActivity(userId, Math.min(limit, 40), effectiveKinds); } } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardService.java b/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardService.java index 47f5fa13..054ea91b 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/dashboard/DashboardService.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.raddatz.familienarchiv.audit.ActivityActorDTO; import org.raddatz.familienarchiv.audit.ActivityFeedRow; +import org.raddatz.familienarchiv.audit.AuditKind; import org.raddatz.familienarchiv.audit.AuditLogQueryService; import org.raddatz.familienarchiv.audit.PulseStatsRow; import org.raddatz.familienarchiv.model.AppUser; @@ -110,8 +111,8 @@ public class DashboardService { ); } - public List getActivity(UUID currentUserId, int limit) { - List rows = auditLogQueryService.findActivityFeed(currentUserId, limit); + public List getActivity(UUID currentUserId, int limit, Set kinds) { + List rows = auditLogQueryService.findActivityFeed(currentUserId, limit, kinds); List docIds = rows.stream() .map(ActivityFeedRow::getDocumentId) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardControllerTest.java index 1bfc37ce..7dc70435 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardControllerTest.java @@ -1,6 +1,7 @@ package org.raddatz.familienarchiv.dashboard; import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.audit.AuditKind; import org.raddatz.familienarchiv.config.SecurityConfig; import org.raddatz.familienarchiv.model.AppUser; import org.raddatz.familienarchiv.security.PermissionAspect; @@ -15,10 +16,12 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import java.util.List; +import java.util.Set; import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -134,7 +137,7 @@ class DashboardControllerTest { UUID userId = UUID.randomUUID(); when(userService.findByEmail(any())).thenReturn( AppUser.builder().id(userId).email("u@test.com").password("pw").build()); - when(dashboardService.getActivity(any(UUID.class), anyInt())).thenReturn(List.of()); + when(dashboardService.getActivity(any(UUID.class), anyInt(), any(Set.class))).thenReturn(List.of()); mockMvc.perform(get("/api/dashboard/activity")) .andExpect(status().isOk()) @@ -147,11 +150,66 @@ class DashboardControllerTest { UUID userId = UUID.randomUUID(); when(userService.findByEmail(any())).thenReturn( AppUser.builder().id(userId).email("u@test.com").password("pw").build()); - when(dashboardService.getActivity(any(UUID.class), anyInt())).thenReturn(List.of()); + when(dashboardService.getActivity(any(UUID.class), anyInt(), any(Set.class))).thenReturn(List.of()); mockMvc.perform(get("/api/dashboard/activity").param("limit", "9999")) .andExpect(status().isOk()); - org.mockito.Mockito.verify(dashboardService).getActivity(any(UUID.class), org.mockito.ArgumentMatchers.eq(40)); + verify(dashboardService).getActivity(any(UUID.class), org.mockito.ArgumentMatchers.eq(40), any(Set.class)); + } + + // ─── GET /api/dashboard/activity — kinds param ─────────────────────────── + + @Test + @WithMockUser(authorities = "READ_ALL") + void activity_parsesKinds_fromCsvQueryParam() throws Exception { + UUID userId = UUID.randomUUID(); + when(userService.findByEmail(any())).thenReturn( + AppUser.builder().id(userId).email("u@test.com").password("pw").build()); + when(dashboardService.getActivity(any(UUID.class), anyInt(), any(Set.class))).thenReturn(List.of()); + + mockMvc.perform(get("/api/dashboard/activity") + .param("kinds", "FILE_UPLOADED", "TEXT_SAVED")) + .andExpect(status().isOk()); + + verify(dashboardService).getActivity(any(UUID.class), anyInt(), + org.mockito.ArgumentMatchers.eq(Set.of(AuditKind.FILE_UPLOADED, AuditKind.TEXT_SAVED))); + } + + @Test + @WithMockUser(authorities = "READ_ALL") + void activity_returns400_forUnknownKindValue() throws Exception { + mockMvc.perform(get("/api/dashboard/activity").param("kinds", "INVALID_KIND")) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(authorities = "READ_ALL") + void activity_defaults_to_rollupEligible_whenKindsAbsent() throws Exception { + UUID userId = UUID.randomUUID(); + when(userService.findByEmail(any())).thenReturn( + AppUser.builder().id(userId).email("u@test.com").password("pw").build()); + when(dashboardService.getActivity(any(UUID.class), anyInt(), any(Set.class))).thenReturn(List.of()); + + mockMvc.perform(get("/api/dashboard/activity")) + .andExpect(status().isOk()); + + verify(dashboardService).getActivity(any(UUID.class), anyInt(), + org.mockito.ArgumentMatchers.eq(AuditKind.ROLLUP_ELIGIBLE)); + } + + @Test + @WithMockUser(authorities = "READ_ALL") + void activity_treats_single_valid_kind_as_filter() throws Exception { + UUID userId = UUID.randomUUID(); + when(userService.findByEmail(any())).thenReturn( + AppUser.builder().id(userId).email("u@test.com").password("pw").build()); + when(dashboardService.getActivity(any(UUID.class), anyInt(), any(Set.class))).thenReturn(List.of()); + + mockMvc.perform(get("/api/dashboard/activity").param("kinds", "COMMENT_ADDED")) + .andExpect(status().isOk()); + + verify(dashboardService).getActivity(any(UUID.class), anyInt(), + org.mockito.ArgumentMatchers.eq(Set.of(AuditKind.COMMENT_ADDED))); } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardServiceTest.java index e7ceb6bd..19a20e51 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/dashboard/DashboardServiceTest.java @@ -88,7 +88,7 @@ class DashboardServiceTest { UUID docId = UUID.randomUUID(); ActivityFeedRow row = mockFeedRow(docId, "ANNOTATION_CREATED"); - when(auditLogQueryService.findActivityFeed(userId, 5)).thenReturn(List.of(row, row)); + when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row, row)); Document doc = Document.builder() .id(docId).title("Familienbrief").originalFilename("f.pdf") @@ -96,7 +96,7 @@ class DashboardServiceTest { .build(); when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(doc)); - List items = dashboardService.getActivity(userId, 5); + List items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE); assertThat(items).hasSize(2); assertThat(items.get(0).documentTitle()).isEqualTo("Familienbrief"); @@ -112,13 +112,13 @@ class DashboardServiceTest { UUID commentId = UUID.randomUUID(); ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", commentId); - when(auditLogQueryService.findActivityFeed(userId, 5)).thenReturn(List.of(row)); + when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row)); when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of( Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build() )); when(commentService.findAnnotationIdsByIds(List.of(commentId))).thenReturn(Map.of()); - List items = dashboardService.getActivity(userId, 5); + List items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE); assertThat(items).hasSize(1); assertThat(items.get(0).commentId()).isEqualTo(commentId); @@ -132,14 +132,14 @@ class DashboardServiceTest { UUID annotationId = UUID.randomUUID(); ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", commentId); - when(auditLogQueryService.findActivityFeed(userId, 5)).thenReturn(List.of(row)); + when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row)); when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of( Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build() )); when(commentService.findAnnotationIdsByIds(List.of(commentId))) .thenReturn(Map.of(commentId, annotationId)); - List items = dashboardService.getActivity(userId, 5); + List items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE); assertThat(items).hasSize(1); assertThat(items.get(0).annotationId()).isEqualTo(annotationId); @@ -151,12 +151,12 @@ class DashboardServiceTest { UUID docId = UUID.randomUUID(); ActivityFeedRow row = mockFeedRow(docId, "TEXT_SAVED", null); - when(auditLogQueryService.findActivityFeed(userId, 5)).thenReturn(List.of(row)); + when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row)); when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of( Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build() )); - List items = dashboardService.getActivity(userId, 5); + List items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE); assertThat(items).hasSize(1); assertThat(items.get(0).commentId()).isNull();