EduShade
Audit Module

Filters & Search

All supported filter dimensions, sorting, and pagination across audit and activity log endpoints

Filters & Search

Both audit_logs and activity_logs endpoints accept a strict, well-defined set of query-string filters. The UI surfaces a deliberately small subset; everything below is callable directly against the API.

Audit Log Filters

GET /v1/admin/audit/audit-logs?…

ParamTypeBound to column
actor_idUUIDactor_id
actor_typestring (≤50 chars)actor_type
actionstring (≤100 chars)action
resource_typestring (≤100 chars)resource_type
resource_idstringresource_id
modulestring (≤100 chars)module
start_dateRFC3339 timestampcreated_at >= ?
end_dateRFC3339 timestampcreated_at <= ?

Source: audit-service/dto/audit_log.go::AuditLogFilter. All filters are AND-combined. Tenant scope is always applied automatically from the JWT — there is no way to query across tenants.

What the Admin UI Exposes

The audit-logs-table.tsx toolbar shows only Action, Resource Type, and Module as select dropdowns. Date range, actor ID, resource ID, and actor type need to be sent as query params directly.

Activity Log Filters

GET /v1/admin/audit/activity-logs?… and GET /v1/user/audit/activity-logs?…

ParamTypeBound to column
user_idUUIDuser_id (admin endpoint only — user endpoint forces caller's ID)
actionstring (≤100 chars)action
modulestring (≤100 chars)module
methodstring (≤10 chars)method
status_codeint (100–599)status_code
start_dateRFC3339 timestampcreated_at >= ?
end_dateRFC3339 timestampcreated_at <= ?

Source: audit-service/dto/activity_log.go::ActivityLogFilter. status_code outside 100..599 is rejected at validation; the others are length-checked.

What the Admin UI Exposes

The activity-logs-table.tsx toolbar shows only Method and Module as select dropdowns. The user view (user-activity-logs.tsx) exposes no filter chips at all.

Sorting

Both endpoints accept the same pagination contract (base_dto.PaginationRequest):

ParamDefaultNotes
sort_bycreated_atAny column name; if it doesn't already contain a ., the table alias is automatically prepended (al. for audit, acl. for activity)
sort_dirdescasc or desc

Because the alias is auto-prepended, you can pass sort_by=action and the SQL becomes ORDER BY al.action. If you need to sort by a joined column (currently none are exposed), pass it fully qualified.

Pagination

ParamDefaultNotes
page11-indexed
per_page50No hard cap is enforced server-side, but the UI sticks to 50

The response always includes a pagination block with total, page, per_page, and a has_next/has_previous pair.

Tenant Scoping

Tenant scoping is enforced by the service layer, not by user-supplied params:

  • GetAuditLogs(ctx, tenantID, ...) always adds WHERE al.tenant_id = ?.
  • GetActivityLogs(ctx, tenantID, ...) does the same with acl.tenant_id.
  • The tenantID argument comes from the resolved tenant context middleware — never from the query string.

There is no API surface that lets a caller view another tenant's rows.

Common Recipes

GoalQuery
Last 24 hours of admin deletes in the auth module?action=delete&module=auth&start_date=2026-04-20T00:00:00Z
Everything user X did this week?actor_id=<uuid>&start_date=2026-04-14T00:00:00Z
All 5xx activity rows for triage?status_code=500 (loop status codes if you need a band)
All deletes on a specific course?action=delete&resource_type=course&resource_id=<uuid>
One admin's mutations on courses, newest first?actor_id=<uuid>&resource_type=course&sort_by=created_at&sort_dir=desc

Limitations

  • Single value per filter. Every filter accepts exactly one value. To OR across actions or modules, page through twice and merge client-side.
  • No full-text search. There is no q / search parameter. Filtering happens on indexed columns only.
  • Status code is exact-match. There's no status_code_min / status_code_max — to scan all 5xx, query each code or pre-aggregate.
  • description, metadata, before_value, after_value are not searchable. Indexes exist on the dimensions in the table above; JSONB content is opaque to the API.

For free-form historical analysis beyond these dimensions, use the Export endpoint and process the JSON offline.

On this page