Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

12 — API Mapping (Audit Module)

Exact wire contract for the Audit screens → the single read endpoint. Base /api/v1; envelope per 00-shared/07. All endpoints from src/modules/audit/audit.controller.ts; business rules from audit.service.ts + audit.handler.ts + audit-log.repository.ts. Global guards: RateLimitGuardJwtAuthGuardRbacGuard (app.module.ts:129-133). Append-only module: no create/update/delete/export endpoints exist.


0. Module-wide request envelope & client policy

AspectContract
Basehttps://api.<domain>/api/v1
HeadersAuthorization: Bearer <accessToken>; x-request-id client-generated
Success{success:true, message:"OK", data, meta?, timestamp, requestId} (response-envelope.interceptor.ts:11-18)
Error{success:false, message, error:{code,details?}, timestamp, requestId} (http-exception.filter.ts:16-25)
TenancytenantId from JWT claim → TenantContextService.requireTenantId() (audit.controller.ts:30, jwt-auth.guard.ts:44-55); never in query/body
Rate limitdefault api tier 100/min (rate-limit.constants.ts:6) — no @RateLimit override on the controller
Permissionserver: JwtAuthGuard only (audit.controller.ts:9); audit.read (permissions.constants.ts:54) unenforced → Phase-5 (planned) (docs/IMPLEMENTATION_PLAN.md:241)
Cacheserver none; client last-good cache 5 min (volatile list, 00-shared/06 §3.3)
Offlinereads from cache + AppOfflineBanner; writes n/a (no writes exist)

Screen: Audit log list (and filter bar)

EndpointGET /audit-logs (audit.controller.ts:17)
Query paramspage (default 1), limit (default 50), action?, entityType?, actorId? (audit.controller.ts:20-24)
FiltersAND-combined: {action, entityType, actorId} → Mongo filter + tenant scope (audit.controller.ts:26-29, audit.service.ts:45-48)
Sortfixed occurredAt desc (audit-log.repository.ts:23); no sort/q support
AuthBearer JWT (valid = allowed today; audit.read recommended gate client-side, OQ-5)
Errors400 VALIDATION_ERROR (non-numeric page/limit → CastError, http-exception.filter.ts:47-55); 401 UNAUTHENTICATED; 429 RATE_LIMITED; 5xx INTERNAL_SERVER_ERROR

Actual response (exact — derived from code):

{
  "success": true,
  "message": "OK",
  "data": {
    "data": [
      {
        "_id": "66f0…",
        "tenantId": "t_springfield",
        "actorId": "u_8f2a…",
        "actorType": "user",
        "action": "StudentUpdated",
        "entityType": null,
        "entityId": null,
        "occurredAt": "2026-08-02T05:02:14.000Z",
        "after": { "studentId": "s_9f1…", "changes": ["guardianPhone"] },
        "correlationId": "9a1c…",
        "before": null,
        "metadata": null,
        "isDeleted": false,
        "version": 0
      }
    ],
    "total": 1234
  },
  "timestamp": "2026-08-02T05:02:15.000Z",
  "requestId": "req_…"
}

Pagination shape deviation (critical, OQ-2): AuditService.query() returns {data, total} without meta (audit.service.ts:41-50). The envelope interceptor treats a payload as paginated only when it has both data and meta (response-envelope.interceptor.ts:25-32), so this response is wrapped as a plain object: client reads envelope.data.data[] and envelope.data.total. It does not receive meta:{page,limit,totalItems,totalPages,hasNext,hasPrevious} per the shared paginated contract (00-shared/07 §2). Client computes paging from total + requested page/limit.

Entry document fields (exact, audit-log.schema.ts:13-56 + inherited base.schema.ts:8-34):

FieldTypeNotes
_idstringrow key, dedupe key
tenantIdstringrequired, index (base.schema.ts:10-11)
actorIdstringrequired (audit-log.schema.ts:15-16)
actorTypeuser|system|api_keydefault user; handler always writes user (audit-log.schema.ts:18-19, audit.handler.ts:35)
actionstringrequired = eventType (PascalCase, e.g. UserCreated) (audit-log.schema.ts:21-22, audit.handler.ts:36)
entityType / entityIdstring?rarely populated today (OQ-1) (audit-log.schema.ts:24-28, audit.handler.ts:37-40)
ipAddress / device / browser / userAgentstring?schema fields, never written by current handler (OQ) (audit-log.schema.ts:30-40)
occurredAtISO-8601 UTCrequired; the display timestamp (audit-log.schema.ts:42-43); schema timestamps:false → no createdAt
before / afterobject?masked at write (audit.service.ts:20); before set only when emitter supplies it; after falls back to whole payload (audit.handler.ts:42-43)
correlationIdstring?trace link (audit-log.schema.ts:51-52)
metadataobject?payload.metadata passthrough (audit-log.schema.ts:54-55, audit.handler.ts:44)
isDeleted/version/createdBy/…inheritedalways false/0/absent — never displayed (base.schema.ts:13-34)

Client flows: load(page, filters) → map → model; loadMore()page+1 while items.length < total; pullToRefresh()page=1 bypassing cache; changeFilter()page=1.

Endpoint source: audit.controller.ts:17-34, audit.service.ts:37-51, audit-log.repository.ts:19-28


Non-existent surfaces (append-only — do not build against them)

SurfaceStatusEvidence
GET /audit-logs/:id (entry detail)absent — detail renders in-memory (OQ-4)controller has one route (audit.controller.ts:17)
POST/PATCH/DELETE /audit-logs*absent — append-onlyaudit-log.repository.ts:6, 15-28; PLAN 19.3 (PLAN.md:191)
Export CSV/PDF(planned) — blueprint onlyAUDITING.md:87; no endpoint
Date-range / q / sort filters(planned)controller params only page/limit/action/entityType/actorId (audit.controller.ts:19-25)
Platform (tenantId:null) entries(planned) — not implementedAUDITING.md:70-71 vs audit-log.schema.ts:7-11

Loading / streaming / realtime

ScreenLoadingStreamingRealtime
audit listAppSkeleton rowsLoad-more pagesWS append (ws-bridge.service.ts:16-22) — topics = eventType strings, joined room tenant:{tenantId} (ws.gateway.ts:50)
entry detailn/a (in-memory)
export(planned)(planned) streamed per blueprint

Client-side error mapping table (module)

ScreencodeUI
list/detail401silent refresh → fail → session-expiry (00-shared/06 §3.6)
list429countdown chip; no auto-retry
list400 (bad page/limit)reset to page 1, snackbar
list5xxAppErrorState generic + requestId
list403 (future Phase-5)403 screen; route hidden pre-emptively

Optimistic / undo

None — read-only module; no mutations, no optimistic updates, no undo (09 §1).