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 (Reports Module)

Exact wire contract for every screen → endpoint. Base /api/v1; envelope per 00-shared/07 and response-envelope.interceptor.ts / http-exception.filter.ts. Endpoints from reports.controller.ts; business rules from reports.service.ts, report-job.repository.ts, report-job.schema.ts. Controller guard chain: JwtAuthGuard only (reports.controller.ts:9).


0. Module-wide request envelope & client policy

AspectContract
Basehttps://api.<domain>/api/v1
HeadersAuthorization: Bearer <accessToken>; x-request-id; Content-Type: application/json
Success{success:true, message:"OK", data, timestamp, requestId} (response-envelope.interceptor.ts:48-53)
Error{success:false, message, error:{code,details?}, timestamp, requestId} (http-exception.filter.ts:27-35)
TenancytenantId never in body — from JWT via TenantContextService; repository tenant-scoped (base.repository.ts:21-23); worker restores tenant context (report.worker.ts:20-29)
RBACNot enforced on reports endpoints. report.generate / report.read exist (permissions.constants.ts:39-40) but the controller carries only @UseGuards(JwtAuthGuard) (reports.controller.ts:9) and no @Permissions decorator. Client must hide/disable by permission list until server enforces (OQ — see 14 §9).
PollingNo server push; client polls GET /reports/:jobId per 10 §1

E1 — Generate a report

EndpointPOST /reports/generate (reports.controller.ts:14-18)
RequestGenerateReportDto (generate-report.dto.ts:5-34): type (req, enum report_card|attendance_summary|fee_summaryreport-job.schema.ts:7-11), studentId?/classId?/examId? (@IsMongoId), startDate?/endDate? (@IsString)
Success201 (Nest default — blueprint says 202, 04-Modules/Reports.md:48; code returns the default, gap) data: {jobId, status:"queued"} (reports.service.ts:43)
SemanticsCreates report_jobs doc (:31-34), enqueues BullMQ job generate on report-generate queue with {jobId, tenantId, type, params} (:36-41). No idempotency key — each POST = new job
Errors400 (enum/mongo-id validation); 401; 429; 5xx (queue down)
ScreenS2 (06 §S2)

E2 — Get job status and result (polling contract)

EndpointGET /reports/:jobId (reports.controller.ts:20-24)
Success200 data: ReportJobDocument — full doc (report-job.schema.ts:21-43) incl. type, status, params, result?, error?, completedAt?, createdAt
404RESOURCE_NOT_FOUND, Report job not found. (reports.service.ts:48) — tenant-scoped; cross-tenant id also 404s
PollingClient polls every 2 s while status ∈ {queued, processing}; terminal statuses completed/failed stop the cycle (10 §1); 404 → stop
ScreenS4/S5 (06 §S4-S5)

E3 — Download result (planned)

EndpointGET /reports/:jobId/download — blueprint only (04-Modules/Reports.md:26)
StatusNOT in code. No controller route, no PDF/CSV serializer, no file write. Blueprint intent: streamed downloads (:49), files under sl/{tenantId}/reports/{uuid} with TTL cleanup (:50)
InterimUntil it lands: results are JSON on the job doc (E2); export CTAs hidden (06 §S5)

E4 — List jobs (needed by S3)

EndpointNone. Index {tenantId, createdAt:-1} exists (report-job.schema.ts:48) but no GET /reports route
Clientlocal persisted history of submitted jobIds; (forward-looking) server GET /reports?status=

E5 — Files (delivery path for future exports) (planned)

UploadPOST /files/upload (multipart, file.uploadfiles.controller.ts:29-41)
Read/downloadGET /files/:id (file.read, :49-53); GET /files/:id/download (file.read, :55-64) → Content-Type + Content-Disposition: attachment; filename="…" streamed buffer (files.service.ts:58-64)
DeleteDELETE /files/:id (file.delete, :66-71) — soft delete
NoteReport generation does NOT write files today; E3 will need this path

E6 — Scheduler (scheduled reports)

Endpointnone (admin API (planned), perms scheduler.read/create/delete, permissions.constants.ts:94-96)
BehaviorAttendanceReportJob.execute enqueues generate-attendance-report on the report-generate queue (attendance-report.job.ts:13-24); scheduler service wires queue QUEUE.REPORT_GENERATE (scheduler.service.ts:32,102,163,219)
Payload{eventType:"attendance-report", tenantId, correlationId, actorId:"scheduler", payload:{reportType:"daily"|"weekly"}} (attendance-report.job.ts:16-21)
NoteEnqueued jobs use jobName generate-attendance-report; ReportsService.executeJob is reached via the worker (report.worker.ts:34) and switches on job doc type (reports.service.ts:64-76) — scheduled jobs must land as attendance_summary jobs to be processed

Worker contract (server-side context)

  • Queue: QUEUE.REPORT_GENERATE = 'report-generate' (queue.constants.ts:10).
  • Processor: @Processor('report-generate') (report.worker.ts:7); process() restores tenant context then reportsService.executeJob(jobId) (:18-35).
  • executeJob: markProcessing → type switch (report_card/attendance_summary/ fee_summaryreports.service.ts:64-76) → markCompleted w/ result, or markFailed w/ error (:78-82; repository :21-47).
  • eventQueueMap has NO report route — ReportGenerated notifications (planned) (event-queue-map.ts).

Client-side error mapping table

ScreencodeUI
S2 submit400inline field error
S2 submit401silent refresh → resubmit
S2 submit429countdown, no auto-retry
S2 submit5xxsnackbar + form kept (re-POST safe — new job per POST)
S4 poll404not-found state, back
S4 poll429pause poll 30 s
S4 poll5xx/networkbanner + manual retry (10 §1)
any (future)403shared 403 screen — server emits none today (no @Permissions)

Optimistic / undo

  • Generate: no optimistic result (async); navigate on 201 only.
  • Retry: new job; previous failed job remains in history (audit trail).
  • No undo anywhere — jobs are immutable once created.