feat(dashboard): add kinds param to GET /api/dashboard/activity
Spring auto-converts ?kinds=FILE_UPLOADED,TEXT_SAVED to Set<AuditKind>. Absent or empty kinds defaults to ROLLUP_ELIGIBLE. Unknown value → 400. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<ActivityFeedItemDTO> getActivity(
|
||||
Authentication authentication,
|
||||
@RequestParam(defaultValue = "7") int limit) {
|
||||
@RequestParam(defaultValue = "7") int limit,
|
||||
@RequestParam(required = false) Set<AuditKind> kinds) {
|
||||
UUID userId = SecurityUtils.requireUserId(authentication, userService);
|
||||
return dashboardService.getActivity(userId, Math.min(limit, 40));
|
||||
Set<AuditKind> effectiveKinds = (kinds == null || kinds.isEmpty()) ? AuditKind.ROLLUP_ELIGIBLE : kinds;
|
||||
return dashboardService.getActivity(userId, Math.min(limit, 40), effectiveKinds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ActivityFeedItemDTO> getActivity(UUID currentUserId, int limit) {
|
||||
List<ActivityFeedRow> rows = auditLogQueryService.findActivityFeed(currentUserId, limit);
|
||||
public List<ActivityFeedItemDTO> getActivity(UUID currentUserId, int limit, Set<AuditKind> kinds) {
|
||||
List<ActivityFeedRow> rows = auditLogQueryService.findActivityFeed(currentUserId, limit, kinds);
|
||||
|
||||
List<UUID> docIds = rows.stream()
|
||||
.map(ActivityFeedRow::getDocumentId)
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5);
|
||||
List<ActivityFeedItemDTO> 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<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5);
|
||||
List<ActivityFeedItemDTO> 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<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5);
|
||||
List<ActivityFeedItemDTO> 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<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5);
|
||||
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
||||
|
||||
assertThat(items).hasSize(1);
|
||||
assertThat(items.get(0).commentId()).isNull();
|
||||
|
||||
Reference in New Issue
Block a user