diff --git a/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepository.java b/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepository.java index fedd587..7c7db9c 100644 --- a/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepository.java +++ b/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepository.java @@ -1,10 +1,6 @@ package net.siegeln.cameleer.saas.audit; -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; import java.time.Instant; @@ -12,31 +8,9 @@ import java.util.List; import java.util.UUID; @Repository -public interface AuditRepository extends JpaRepository { +public interface AuditRepository extends JpaRepository, AuditRepositoryCustom { List findByTenantIdAndCreatedAtBetween(UUID tenantId, Instant from, Instant to); List findByActorId(UUID actorId); - - @Query(""" - SELECT a FROM AuditEntity a - WHERE (:tenantId IS NULL OR a.tenantId = :tenantId) - AND (:action IS NULL OR a.action = :action) - AND (:result IS NULL OR a.result = :result) - AND (:from IS NULL OR a.createdAt >= :from) - AND (:to IS NULL OR a.createdAt <= :to) - AND (:search = '' - OR LOWER(a.actorEmail) LIKE LOWER(CONCAT('%', :search, '%')) - OR LOWER(a.resource) LIKE LOWER(CONCAT('%', :search, '%'))) - ORDER BY a.createdAt DESC - """) - Page findFiltered( - @Param("tenantId") UUID tenantId, - @Param("action") String action, - @Param("result") String result, - @Param("from") Instant from, - @Param("to") Instant to, - @Param("search") String search, - Pageable pageable - ); } diff --git a/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepositoryCustom.java b/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepositoryCustom.java new file mode 100644 index 0000000..a25d815 --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepositoryCustom.java @@ -0,0 +1,14 @@ +package net.siegeln.cameleer.saas.audit; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.time.Instant; +import java.util.UUID; + +public interface AuditRepositoryCustom { + + Page findFiltered(UUID tenantId, String action, String result, + Instant from, Instant to, String search, + Pageable pageable); +} diff --git a/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepositoryImpl.java b/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepositoryImpl.java new file mode 100644 index 0000000..c459961 --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/audit/AuditRepositoryImpl.java @@ -0,0 +1,88 @@ +package net.siegeln.cameleer.saas.audit; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +public class AuditRepositoryImpl implements AuditRepositoryCustom { + + private final JdbcTemplate jdbc; + + public AuditRepositoryImpl(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + @Override + public Page findFiltered(UUID tenantId, String action, String result, + Instant from, Instant to, String search, + Pageable pageable) { + StringBuilder where = new StringBuilder("WHERE 1=1"); + List params = new ArrayList<>(); + + if (tenantId != null) { + where.append(" AND tenant_id = ?"); + params.add(tenantId); + } + if (action != null && !action.isBlank()) { + where.append(" AND action = ?"); + params.add(action); + } + if (result != null && !result.isBlank()) { + where.append(" AND result = ?"); + params.add(result); + } + if (from != null) { + where.append(" AND created_at >= ?"); + params.add(Timestamp.from(from)); + } + if (to != null) { + where.append(" AND created_at <= ?"); + params.add(Timestamp.from(to)); + } + if (search != null && !search.isBlank()) { + where.append(" AND (actor_email ILIKE ? OR resource ILIKE ?)"); + String like = "%" + search + "%"; + params.add(like); + params.add(like); + } + + String countSql = "SELECT COUNT(*) FROM audit_log " + where; + Long total = jdbc.queryForObject(countSql, Long.class, params.toArray()); + long totalCount = total != null ? total : 0; + + String dataSql = "SELECT * FROM audit_log " + where + + " ORDER BY created_at DESC LIMIT ? OFFSET ?"; + List dataParams = new ArrayList<>(params); + dataParams.add(pageable.getPageSize()); + dataParams.add(pageable.getOffset()); + + List items = jdbc.query(dataSql, (rs, rowNum) -> mapRow(rs), dataParams.toArray()); + return new PageImpl<>(items, pageable, totalCount); + } + + private AuditDto.AuditLogEntry mapRow(ResultSet rs) throws SQLException { + Timestamp ts = rs.getTimestamp("created_at"); + return new AuditDto.AuditLogEntry( + rs.getObject("id", UUID.class), + rs.getString("actor_email"), + rs.getObject("tenant_id", UUID.class), + rs.getString("action"), + rs.getString("resource"), + rs.getString("environment"), + rs.getString("result"), + rs.getString("source_ip"), + ts != null ? ts.toInstant() : null + ); + } +} diff --git a/src/main/java/net/siegeln/cameleer/saas/audit/AuditService.java b/src/main/java/net/siegeln/cameleer/saas/audit/AuditService.java index 87bea93..d5cf1e7 100644 --- a/src/main/java/net/siegeln/cameleer/saas/audit/AuditService.java +++ b/src/main/java/net/siegeln/cameleer/saas/audit/AuditService.java @@ -34,10 +34,9 @@ public class AuditService { auditRepository.save(entry); } - public Page search(UUID tenantId, String action, String result, - Instant from, Instant to, String search, - Pageable pageable) { - String safeSearch = (search != null && !search.isBlank()) ? search.trim() : ""; - return auditRepository.findFiltered(tenantId, action, result, from, to, safeSearch, pageable); + public Page search(UUID tenantId, String action, String result, + Instant from, Instant to, String search, + Pageable pageable) { + return auditRepository.findFiltered(tenantId, action, result, from, to, search, pageable); } } diff --git a/src/main/java/net/siegeln/cameleer/saas/portal/TenantAuditController.java b/src/main/java/net/siegeln/cameleer/saas/portal/TenantAuditController.java index e8e409e..64bab13 100644 --- a/src/main/java/net/siegeln/cameleer/saas/portal/TenantAuditController.java +++ b/src/main/java/net/siegeln/cameleer/saas/portal/TenantAuditController.java @@ -1,6 +1,5 @@ package net.siegeln.cameleer.saas.portal; -import net.siegeln.cameleer.saas.audit.AuditDto.AuditLogEntry; import net.siegeln.cameleer.saas.audit.AuditDto.AuditLogPage; import net.siegeln.cameleer.saas.audit.AuditService; import net.siegeln.cameleer.saas.config.TenantContext; @@ -39,12 +38,8 @@ public class TenantAuditController { var pageResult = auditService.search(tenantId, action, result, from, to, search, PageRequest.of(page, size)); - var entries = pageResult.getContent().stream() - .map(AuditLogEntry::from) - .toList(); - return ResponseEntity.ok(new AuditLogPage( - entries, pageResult.getNumber(), pageResult.getSize(), + pageResult.getContent(), pageResult.getNumber(), pageResult.getSize(), pageResult.getTotalElements(), pageResult.getTotalPages())); } } diff --git a/src/main/java/net/siegeln/cameleer/saas/vendor/VendorAuditController.java b/src/main/java/net/siegeln/cameleer/saas/vendor/VendorAuditController.java index 7725894..306e5f6 100644 --- a/src/main/java/net/siegeln/cameleer/saas/vendor/VendorAuditController.java +++ b/src/main/java/net/siegeln/cameleer/saas/vendor/VendorAuditController.java @@ -1,6 +1,5 @@ package net.siegeln.cameleer.saas.vendor; -import net.siegeln.cameleer.saas.audit.AuditDto.AuditLogEntry; import net.siegeln.cameleer.saas.audit.AuditDto.AuditLogPage; import net.siegeln.cameleer.saas.audit.AuditService; import org.springframework.data.domain.PageRequest; @@ -40,12 +39,8 @@ public class VendorAuditController { var pageResult = auditService.search(tenantId, action, result, from, to, search, PageRequest.of(page, size)); - var entries = pageResult.getContent().stream() - .map(AuditLogEntry::from) - .toList(); - return ResponseEntity.ok(new AuditLogPage( - entries, pageResult.getNumber(), pageResult.getSize(), + pageResult.getContent(), pageResult.getNumber(), pageResult.getSize(), pageResult.getTotalElements(), pageResult.getTotalPages())); } }