Pagination on audit log

This commit is contained in:
2026-01-23 13:59:32 +01:00
parent 5ac181fdf2
commit 2da8b6b81e
7 changed files with 280 additions and 6 deletions

View File

@@ -1,18 +1,20 @@
package dev.kruhlmann.imgfloat.controller;
import dev.kruhlmann.imgfloat.model.AuditLogEntryView;
import dev.kruhlmann.imgfloat.model.AuditLogPageView;
import dev.kruhlmann.imgfloat.model.OauthSessionUser;
import dev.kruhlmann.imgfloat.service.AuditLogService;
import dev.kruhlmann.imgfloat.service.AuthorizationService;
import dev.kruhlmann.imgfloat.util.LogSanitizer;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import java.util.List;
import org.springframework.data.domain.Page;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -30,8 +32,13 @@ public class AuditLogApiController {
}
@GetMapping
public List<AuditLogEntryView> listAuditEntries(
public AuditLogPageView listAuditEntries(
@PathVariable("broadcaster") String broadcaster,
@RequestParam(name = "search", required = false) String search,
@RequestParam(name = "actor", required = false) String actor,
@RequestParam(name = "action", required = false) String action,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "25") int size,
OAuth2AuthenticationToken oauthToken
) {
String sessionUsername = OauthSessionUser.from(oauthToken).login();
@@ -41,6 +48,15 @@ public class AuditLogApiController {
LogSanitizer.sanitize(broadcaster),
LogSanitizer.sanitize(sessionUsername)
);
return auditLogService.listEntries(broadcaster);
Page<AuditLogEntryView> auditPage = auditLogService
.listEntries(broadcaster, actor, action, search, page, size)
.map(AuditLogEntryView::fromEntry);
return new AuditLogPageView(
auditPage.getContent(),
auditPage.getNumber(),
auditPage.getSize(),
auditPage.getTotalElements(),
auditPage.getTotalPages()
);
}
}

View File

@@ -0,0 +1,11 @@
package dev.kruhlmann.imgfloat.model;
import java.util.List;
public record AuditLogPageView(
List<AuditLogEntryView> entries,
int page,
int size,
long totalElements,
int totalPages
) {}

View File

@@ -2,12 +2,39 @@ package dev.kruhlmann.imgfloat.repository;
import dev.kruhlmann.imgfloat.model.AuditLogEntry;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface AuditLogRepository extends JpaRepository<AuditLogEntry, String> {
List<AuditLogEntry> findTop200ByBroadcasterOrderByCreatedAtDesc(String broadcaster);
@Query(
"""
SELECT entry
FROM AuditLogEntry entry
WHERE entry.broadcaster = :broadcaster
AND (:actor IS NULL OR LOWER(entry.actor) = :actor)
AND (:action IS NULL OR LOWER(entry.action) LIKE CONCAT('%', :action, '%'))
AND (
:search IS NULL
OR LOWER(entry.actor) LIKE CONCAT('%', :search, '%')
OR LOWER(entry.action) LIKE CONCAT('%', :search, '%')
OR LOWER(entry.details) LIKE CONCAT('%', :search, '%')
)
"""
)
Page<AuditLogEntry> searchEntries(
@Param("broadcaster") String broadcaster,
@Param("actor") String actor,
@Param("action") String action,
@Param("search") String search,
Pageable pageable
);
void deleteByBroadcaster(String broadcaster);
}

View File

@@ -6,6 +6,9 @@ import dev.kruhlmann.imgfloat.repository.AuditLogRepository;
import dev.kruhlmann.imgfloat.util.LogSanitizer;
import java.util.List;
import java.util.Locale;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
@@ -63,6 +66,33 @@ public class AuditLogService {
.toList();
}
public Page<AuditLogEntry> listEntries(
String broadcaster,
String actor,
String action,
String search,
int page,
int size
) {
String normalizedBroadcaster = normalize(broadcaster);
if (normalizedBroadcaster == null || normalizedBroadcaster.isBlank()) {
return Page.empty();
}
String normalizedActor = normalizeFilter(actor);
String normalizedAction = normalizeFilter(action);
String normalizedSearch = normalizeFilter(search);
int safePage = Math.max(page, 0);
int safeSize = Math.min(Math.max(size, 1), 200);
PageRequest pageRequest = PageRequest.of(safePage, safeSize, Sort.by(Sort.Direction.DESC, "createdAt"));
return auditLogRepository.searchEntries(
normalizedBroadcaster,
normalizedActor,
normalizedAction,
normalizedSearch,
pageRequest
);
}
public void deleteEntriesForBroadcaster(String broadcaster) {
String normalizedBroadcaster = normalize(broadcaster);
if (normalizedBroadcaster == null || normalizedBroadcaster.isBlank()) {
@@ -74,4 +104,9 @@ public class AuditLogService {
private String normalize(String value) {
return value == null ? null : value.toLowerCase(Locale.ROOT);
}
private String normalizeFilter(String value) {
String normalized = normalize(value);
return normalized == null || normalized.isBlank() ? null : normalized;
}
}