Skip to content

feat(audit-logs): paging + filters + outcome/ip/userAgent + field rename#15

Merged
jlc488 merged 1 commit into
mainfrom
feat/auditlogs-paging-filters
May 27, 2026
Merged

feat(audit-logs): paging + filters + outcome/ip/userAgent + field rename#15
jlc488 merged 1 commit into
mainfrom
feat/auditlogs-paging-filters

Conversation

@jlc488

@jlc488 jlc488 commented May 27, 2026

Copy link
Copy Markdown
Contributor

Second PR in the admin-ui ↔ backend contract-gap series (after #14). The admin UI's Audit Logs page sends rich query parameters and expects a Spring Data Page shape; this controller only had a flat tenantId + since lookup.

Summary

Schema (V9)

  • Three nullable columns on platform_audit_log:
    • outcome VARCHAR(16)SUCCESS / FAILURE. NULL on legacy rows (the response layer treats NULL as SUCCESS).
    • ip_address VARCHAR(64) — actor's network address.
    • user_agent VARCHAR(512) — actor's browser / client.
  • Index on outcome (cheap, helps the most common UI filter).
  • Existing rows unaffected.

Entity / write path

  • PlatformAuditLogEntity gains the three fields and an extended all-args constructor.
  • AuditLogService.record reads outcome / ip / userAgent from the AuditEvent metadata map if the publisher stuffs them in. The AuditEvent record itself doesn't carry them as first-class fields yet — that lives in a follow-up so this PR stays focused on the wire shape.

Search

  • JpaPlatformAuditLogRepository now extends JpaSpecificationExecutor.
  • AuditLogQueryService.search(AuditLogSearchCriteria, Pageable) builds a Specification covering: tenantId, actorLogin (case-insensitive contains on actor_display_name), action, targetType, outcome, from, to. Every filter is optional — null means "any". Default sort: occurredAt DESC.

Wire shape

  • AuditLogResponse rewritten to match the admin UI's AuditLog interface: id, tenantId, actorId, actorLogin, action, targetType, targetId, outcome, ip, userAgent, payload, occurredAt. Payload parsed from metadataJson via ObjectMapper; a corrupt blob is surfaced under {"raw": <text>} so a single bad row doesn't sink the page.

Controller

  • GET /admin/api/v1/audit-logs is now paginated + filterable. Query params: tenantId, actorLogin, action, targetType, outcome, from, to, page, size. Size capped at 200, default 25, page is 0-based. Response is the standard Spring Page<AuditLogResponse> shape the UI already expects (content, totalElements, totalPages, number, size).
  • /audit-logs/user/{userId} preserved as a flat convenience read for the diagnostics drill-down.

Test plan

  • ./gradlew build green (55 tasks, all sample-app + audit-core tests pass)
  • Wire the admin-ui Audit Logs page against this and exercise date-range filter, outcome filter, paging
  • Follow-up: promote outcome / ip / userAgent to first-class fields on AuditEvent so publishers don't have to use the metadata map

What's next in this series

Remaining gaps: Policies (PolicyDescriptor list + nested test body), Tenants (status enum + createdAt + status endpoint), Diagnostics + Settings field rename.

Closes the second contract gap with the admin UI. The Audit Logs
page sends rich query parameters and expects a Spring Data Page
shape; this controller only had a flat `tenantId + since` lookup.

Schema (V9)
-----------
- Add three nullable columns to platform_audit_log:
    outcome      VARCHAR(16)   -- SUCCESS / FAILURE, NULL on legacy rows
    ip_address   VARCHAR(64)   -- network address of the actor
    user_agent   VARCHAR(512)  -- browser / client identifier
- Index on outcome (cheap, helps the most common filter).
- Existing rows unaffected; NULL outcome is rendered as SUCCESS by
  the response layer.

Entity
------
- PlatformAuditLogEntity gains outcome, ipAddress, userAgent fields
  and an extended all-args constructor.
- AuditLogService.record now reads `outcome` / `ip` / `userAgent`
  from the AuditEvent metadata map (if the publisher stuffs them in
  there) and persists them into the new columns. The AuditEvent
  record itself doesn't carry these as first-class fields yet —
  that lives in a follow-up.

Search
------
- JpaPlatformAuditLogRepository now extends JpaSpecificationExecutor.
- AuditLogQueryService.search(AuditLogSearchCriteria, Pageable)
  builds a Specification covering tenantId, actorLogin (case-
  insensitive contains on actor_display_name), action, targetType,
  outcome, from, to. Every filter is optional ("null means any").
  Default sort is occurredAt DESC.

DTO + Controller
----------------
- AuditLogResponse rewritten to match the admin UI's `AuditLog`
  interface: id, tenantId, actorId, actorLogin, action, targetType,
  targetId, outcome, ip, userAgent, payload, occurredAt. Payload
  parsed from metadataJson via ObjectMapper; a corrupt blob is
  surfaced under {"raw": <text>} so a single bad row doesn't sink
  the page.
- GET /admin/api/v1/audit-logs is now paginated + filterable.
  Query params: tenantId, actorLogin, action, targetType, outcome,
  from, to, page, size. Size capped at 200 with a default of 25.
  Response is the standard Spring `Page<AuditLogResponse>` shape
  the UI already expects (content, totalElements, totalPages,
  number, size).
- /audit-logs/user/{userId} preserved as a flat convenience read
  for the diagnostics drill-down.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant