feat(audit-logs): paging + filters + outcome/ip/userAgent + field rename#15
Merged
Conversation
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.
2 tasks
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Pageshape; this controller only had a flattenantId + sincelookup.Summary
Schema (V9)
platform_audit_log:outcome VARCHAR(16)—SUCCESS/FAILURE. NULL on legacy rows (the response layer treats NULL asSUCCESS).ip_address VARCHAR(64)— actor's network address.user_agent VARCHAR(512)— actor's browser / client.outcome(cheap, helps the most common UI filter).Entity / write path
PlatformAuditLogEntitygains the three fields and an extended all-args constructor.AuditLogService.recordreadsoutcome/ip/userAgentfrom theAuditEventmetadata map if the publisher stuffs them in. TheAuditEventrecord 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
JpaPlatformAuditLogRepositorynow extendsJpaSpecificationExecutor.AuditLogQueryService.search(AuditLogSearchCriteria, Pageable)builds a Specification covering:tenantId,actorLogin(case-insensitive contains onactor_display_name),action,targetType,outcome,from,to. Every filter is optional — null means "any". Default sort:occurredAt DESC.Wire shape
AuditLogResponserewritten to match the admin UI'sAuditLoginterface:id, tenantId, actorId, actorLogin, action, targetType, targetId, outcome, ip, userAgent, payload, occurredAt. Payload parsed frommetadataJsonviaObjectMapper; a corrupt blob is surfaced under{"raw": <text>}so a single bad row doesn't sink the page.Controller
GET /admin/api/v1/audit-logsis now paginated + filterable. Query params:tenantId, actorLogin, action, targetType, outcome, from, to, page, size. Size capped at 200, default 25,pageis 0-based. Response is the standard SpringPage<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 buildgreen (55 tasks, all sample-app + audit-core tests pass)outcome/ip/userAgentto first-class fields onAuditEventso publishers don't have to use the metadata mapWhat's next in this series
Remaining gaps: Policies (PolicyDescriptor list + nested test body), Tenants (status enum + createdAt + status endpoint), Diagnostics + Settings field rename.