feat(audit): add V49 rollup covering index + raise /api/dashboard/activity cap to 40

- V49__add_audit_log_rollup_index.sql: partial covering index on
  (actor_id, document_id, kind, happened_at DESC) filtered by the 6 rollup
  kinds. Matches the WHERE clause of findRolledUpActivityFeed exactly so the
  session-grouping window scan is index-backed.
- DashboardController: clamp limit to 40 (was 20). Chronik requests up to 40
  activity items per page; dashboard side-rail still passes 7.

Part of #285.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-20 16:08:35 +02:00
committed by marcel
parent eda30f53fa
commit 101f5b2a6a
3 changed files with 22 additions and 1 deletions

View File

@@ -37,6 +37,6 @@ public class DashboardController {
Authentication authentication,
@RequestParam(defaultValue = "7") int limit) {
UUID userId = SecurityUtils.requireUserId(authentication, userService);
return dashboardService.getActivity(userId, Math.min(limit, 20));
return dashboardService.getActivity(userId, Math.min(limit, 40));
}
}

View File

@@ -0,0 +1,7 @@
-- Partial covering index for the session-style activity feed rollup (#285).
-- Matches the WHERE clause of AuditLogQueryRepository.findRolledUpActivityFeed
-- exactly. DESC on happened_at supports the outer ORDER BY without a sort step.
CREATE INDEX idx_audit_log_rollup
ON audit_log (actor_id, document_id, kind, happened_at DESC)
WHERE kind IN ('TEXT_SAVED','FILE_UPLOADED','ANNOTATION_CREATED',
'BLOCK_REVIEWED','COMMENT_ADDED','MENTION_CREATED');

View File

@@ -140,4 +140,18 @@ class DashboardControllerTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray());
}
@Test
@WithMockUser(authorities = "READ_ALL")
void activity_clamps_limit_to_40() 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())).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));
}
}