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

Exact wire contract for every screen → endpoint. Base /api/v1. Envelope per 00-shared/07 §2-3. Endpoints from student.controller.ts, parent.controller.ts, bulk.controller.ts, users.controller.ts; business rules from the services cited. All endpoints behind JwtAuthGuard (student.controller.ts:32-35); tenantId from JWT only — never in the body. RBAC enforcement (planned) (OQ-4); client still gates UI on student.*.


0. Module-wide contract

AspectContract
Basehttps://api.<domain>/api/v1
HeadersAuthorization: Bearer <accessToken>; x-request-id (client UUID); Content-Type: application/json (multipart/form-data for uploads)
Success{success:true, message:"OK", data, meta?, timestamp, requestId} — paginated: data array + meta{page,limit,totalItems,totalPages,hasNext,hasPrevious} (pagination-query.dto.ts:32-39)
Error{success:false, message, error:{code, details?}, timestamp, requestId}
Codes400 VALIDATION_ERROR · 401 UNAUTHENTICATED · 403 PERMISSION_DENIED · 404 RESOURCE_NOT_FOUND · 409 DUPLICATE_RESOURCE · 422 BUSINESS_RULE_VIOLATION · 429 RATE_LIMITED · 5xx INTERNAL_SERVER_ERROR
Paginationpage ≥ 1 (default 1), limit 1–100 (default 20), sort (- = desc), qstudents find ignores sort/q (OQ-2)
Cachingclient last-good + stale-while-revalidate (00-shared/06 §3.3); TTLs: roster 5 min (proposed), detail no client cache
Offlinereads cached; all writes blocked (no offline queue defined for students)
Retrybackoff on 5xx/network; no auto-retry on 429

1. List students — Roster

EndpointGET /students (student.controller.ts:41-43)
Querypage, limit, sort?, q? (parsed; only page/limit applied — student.service.ts:104-108)
Response200 data:[StudentDoc…], meta (page/limit/totalItems/totalPages/hasNext/hasPrevious)
StudentDoc shape_id, tenantId, userId, admissionNumber, rollNumber?, academicYearId, campusId?, gradeId, sectionId, classId, houseId?, admissionDate?, admissionType?, status, transportRequired, hostelRequired, medicalNotes?, metadata?, createdAt, updatedAt, version, isDeleted:false (student.schema.ts:15-64 + base.schema.ts)
Errors401; 429; 5xx
Clientinfinite scroll on meta.hasNext; client-side filters/sort (server ignores q/sort — OQ-2)

2. Get student — Detail

GET /students/:id (student.controller.ts:44-46) → 200 data:StudentDoc; 404 RESOURCE_NOT_FOUND "Student not found." (student.service.ts:95-99).

3. Create student — Create wizard

EndpointPOST /students (student.controller.ts:38-40)
BodyCreateStudentDto — see 08 §1 (userId*, admissionNumber*, academicYearId*, gradeId*, sectionId*, classId*; rest optional)
Response200 data:StudentDoc with status:"active" (student.service.ts:65) + auto-enrolled ACTIVE class_enrollments row (student.service.ts:71-78)
Errors400 validation; 409 DUPLICATE_RESOURCE "Admission number "x" already exists." (student.service.ts:56-62); 429; 5xx
EventsStudentCreated {studentId, admissionNumber, classId}in-app/student-enrolled (event-queue-map.ts:28); audit via event bus

4. Update student — Edit form

PATCH /students/:id (student.controller.ts:56-58) — body UpdateStudentDto (all optional; status is unvalidated string — UI sends enum values only). 200 data:StudentDoc; 404; 409 n/a; emits StudentUpdated {changes:[field…]}audit-write/log-student-updated (event-queue-map.ts:29). Note version increments on every write (base.repository.ts:57-66).

5. Delete student (soft) — Delete dialog

DELETE /students/:id (student.controller.ts:59-61) → 200 (data empty); 404; emits StudentDeletedaudit-write/log-student-deleted (event-queue-map.ts:30). Soft delete: isDeleted:true, deletedAt, deletedBy (base.repository.ts:68-74) — excluded from all queries. No cascade to documents/enrollments/links (OQ-7). No restore endpoint for soft-deleted.

6. Enroll — Enroll sheet

EndpointPOST /students/:id/enroll (student.controller.ts:50-55)
BodyEnrollStudentDto {classId*, academicYearId*, rollNumber?}
Behaviourdeactivates all ACTIVE enrollments (status:"transferred", leftAt:now) then creates new ACTIVE (student.service.ts:125-140)
Response200 data:ClassEnrollmentDoc {studentId, classId, academicYearId, rollNumber?, joinedAt, leftAt?, status:"active"} (class-enrollment.schema.ts:14-39)
Errors404 (student); 400; 429; 5xx

7. Transfer — Transfer form

EndpointPOST /students/:id/transfer (student.controller.ts:62-65)
BodyTransferStudentDto {classId*, academicYearId*, gradeId?, sectionId?, rollNumber?}
Behaviourguard status === "active" else 409 Cannot transfer a student with status "x". (student.service.ts:175-179) → enroll (same deactivation) → $set {classId, academicYearId, gradeId?, sectionId?} (student.service.ts:180-192)
Response200 data:StudentDoc (updated)
Errors404; 409 (wrong status); 400; 429; 5xx
EventsStudentUpdated {changes:["transfer"]} → audit

8. Graduate / Archive / Restore — Lifecycle dialogs

ActionEndpointGuardSuccess → dataSource
GraduatePOST /students/:id/graduate409 "Student is already graduated."status:"graduated"student.controller.ts:66-69, student.service.ts:205-223
ArchivePOST /students/:id/archive409 "Student is already archived."status:"archived"student.controller.ts:70-73
RestorePOST /students/:id/restore409 "Student is already active."status:"active"student.controller.ts:74-77

All: 404; 200 data:StudentDoc; emit StudentUpdated {changes:["graduate"|"archive"|"restore"]}.

9. Active enrollments — Detail → Profile/History

GET /students/:id/enrollments (student.controller.ts:47-49) → 200 data:[ClassEnrollmentDoc…] active only (findActiveByStudentclass-enrollment.repository.ts:20-24); 404.

10. Academic history — Detail → History tab

GET /students/:id/academic-history (student.controller.ts:91-94) → 200 data:[ClassEnrollmentDoc…] all enrollments sorted joinedAt desc (student.service.ts:281-287); 404.

11. Documents

ListGET /students/:id/documents (student.controller.ts:78-81) → 200 data:[StudentDocumentDoc…] sorted createdAt desc (student.service.ts:273-279) — shape {studentId, fileName, mimeType, size, fileId, category?, uploadedBy?, createdAt} (student-document.schema.ts:8-29)
UploadPOST /students/:id/documents (student.controller.ts:82-90) — multipart/form-data, file field file, text field category?; 200 data:StudentDocumentDoc; no server size/type validation (OQ-9)
Downloadnone — only fileId metadata; local provider path /api/v1/files/<tenantId>/<uuid>--<name> (local-storage.provider.ts:47-51) — preview/download (planned)

12. Bulk import / export

ImportPOST /bulk/import/:entity where entity="students" (bulk.controller.ts:35-48; only adapter — bulk-import.service.ts:17-20) — multipart field file; missing → 400 "CSV file is required (multipart field "file")."; 200 data:ImportReport {entity, totalRows, imported, failed, errors:[{rowNumber, errors[]}]} (import-adapter.interface.ts:14-25)
Parse errors400 "Malformed CSV: could not parse file." / "CSV must include a header row and data." (bulk-import.service.ts:31-35)
Unknown entity404 No import adapter for entity "x".
ExportGET /bulk/export/:entity (bulk.controller.ts:50-60) → 200 text/csv, Content-Disposition: attachment; filename="students.csv" — columns admissionNumber, rollNumber, status, admissionDate sorted by admissionNumber (students-import.adapter.ts:87-98)

Per-row errors (exact strings) in 08 §6; row numbers are index+2 (header = row 1) (bulk-import.service.ts:45-46). Synchronous; no job/progress endpoint (OQ-12).

13. Parent linking (via Parents module)

List linksGET /parents/link/student/:studentId (parent.controller.ts:53-57) → 200 data:[StudentParentLinkDoc…] {studentId, parentId, relationship, isPrimaryGuardian, financialResponsibility, pickupAllowed, emergencyPriority, metadata?} (student-parent-link.schema.ts:17-41)
Create parentPOST /parents (parent.controller.ts:29-31) — CreateParentDto; 409 "Parent profile already exists for this user." (parent.service.ts:30-34)
LinkPOST /parents/link/:studentId (parent.controller.ts:47-52) — LinkParentDto (relationship* required); 404 unknown student (student-parent-link.service.ts:27); parent existence not verified (OQ-11)
UnlinkDELETE /parents/link/:linkId (parent.controller.ts:58-60) — soft delete; 404 "Link not found." (student-parent-link.service.ts:38-41)
Parent's childrenGET /parents/:id/students (parent.controller.ts:38-40) → link docs for that parent
Update linknone — edit = unlink + re-link

14. Supporting endpoints (dropdown/identity data)

PurposeEndpointSource
Academic yearsGET /academic-years (page/limit)academics/controllers/academic-year.controller.ts:27-29
GradesGET /gradesgrade.controller.ts:27-29
Sections by gradeGET /sections/by-grade/:gradeIdsection.controller.ts:30-33
ClassesGET /classes ; GET /classes/by-year/:academicYearIdclass.controller.ts:27-33
Create user (identity step)POST /usersusers.controller.ts:36-40
Search users (reuse)GET /users?q= (server-side q handling per users module)users.controller.ts:42-46
AvatarPOST /users/:id/avatar (multipart file)users.controller.ts:95-102

Client error mapping table (module)

ScreenCodeUI
any401silent refresh → sessionExpired (00-shared/06 §3.6)
any action403hide action / 403 screen (server RBAC (planned))
create409 admissioninline field "already exists"
create (users)409 emailstep-1 inline + offer reuse
transfer409 statusread-only status banner
graduate/archive/restore409 same statesnackbar + refresh
documents upload400/5xxtile error + retry
import400 parsewizard error banner with message
import404 entitygeneric (should not happen)
list/detail404empty-state "not found"
any429countdown, CTA disabled
any5xxgeneric + requestId (00-shared/06 §5)

Optimistic / undo policy

  • Never optimistic on: create, enroll, transfer, graduate, archive, restore, delete, link/unlink, document upload, import (all server-truth, side effects).
  • Row removal (unlink, delete) reflects only after server 200.
  • Undo: not offered (no restore endpoint for soft-delete; archive has explicit Restore action instead).