feat(audit): filterable audit log page under Settings

The audit trail existed only as a summary card — no table, no filters.
The list endpoint was also self-scoped and never returned user_email
(the field existed on the struct but was never selected).

Backend: GET /audit-logs now joins users for email and accepts
user_id / actor (email ILIKE) / since (RFC3339) filters alongside the
existing resource/action/page/limit. Widening past self-scope matches
the platform's no-roles trust model — every authed user can already
create users. RLS on audit_logs is dormant (superuser connection,
app.current_user_id never set).

Frontend: /settings/audit-logs page with deferred filter inputs, time
range select, paginated table (time, actor, resource, action, id, IP,
details), linked from a Settings card. Spec updated and types
regenerated.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-17 16:36:05 +02:00
1 parent e127c62b0f
commit 2bfa683084
8 files changed
+337 -23

No files matched your search

+12
View File
@@ -254,6 +254,9 @@ export type AuditLogEntity = {
export type ListAuditLogsInput = {
resource?: string;
action?: string;
actor?: string;
userId?: string;
since?: string;
page?: number;
limit?: number;
};
@@ -981,6 +984,15 @@ export async function listAuditLogs(input: ListAuditLogsInput = {}): Promise<Aud
if (input.action) {
searchParams.set('action', input.action);
}
if (input.actor) {
searchParams.set('actor', input.actor);
}
if (input.userId) {
searchParams.set('user_id', input.userId);
}
if (input.since) {
searchParams.set('since', input.since);
}
if (input.page && input.page > 0) {
searchParams.set('page', String(input.page));
}