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

Exact wire contract for every screen → endpoint. Base /api/v1; envelope per 00-shared/07 §2-§3. All endpoints from src/modules/academics/controllers/*; business rules from services/*; field contracts from dto/*; defaults/enums from schemas/*. Guard today: JwtAuthGuard only (academics.module.ts:46, every controller @UseGuards(JwtAuthGuard)); RBAC (planned) — OQ-1.


0. Module-wide request envelope & client policy

AspectContract
Basehttps://api.<domain>/api/v1
HeadersAuthorization: Bearer <accessToken>; x-request-id client-generated; Content-Type: application/json
Success{success:true, message:"OK", data, meta?, timestamp, requestId}
Error{success:false, message, error:{code, details?}, timestamp, requestId}
TenancytenantId from JWT claim only — never in body/query (base.repository.ts:20-30)
IDs_id, createdAt, updatedAt, __v returned raw (Mongoose docs, no whitelisting) — client maps explicitly (base.schema.ts:7-35)
Paginationpage (≥1, default 1), limit (1–100, default 20), sort (-field = desc), q (parsed, unused by services — OQ-7); meta {page,limit,totalItems,totalPages,hasNext,hasPrevious} (pagination-query.dto.ts:5-30,32-55)
Retrybackoff on 5xx/network; no auto-retry on 429 (00-shared/07 §4)
Offlinewrites blocked + banner; reads from 24 h cache (13 §3)

1. Academic Years — academic-year.controller.ts

Method & PathBody / ParamsSuccess dataRules & Errors
POST /academic-years (:24){name*, startDate*, endDate*, status?}year doc409 DUPLICATE_RESOURCE "Academic year "X" already exists." (academic-year.service.ts:25-29); status omitted → upcoming (:30-33)
GET /academic-years (:27)page,limit,sort,qarray + metadefault sort=createdAt:-1 (:45-47); sort maps - prefix → desc (:45-47)
GET /academic-years/:id (:30)year doc404 "Academic year not found." (:36-40)
PATCH /academic-years/:id (:33)any of {name?, startDate?, endDate?, status?, isCurrent?}year docpartial $set (:59-67); 404 if missing; no duplicate-name check on update (OQ-2)
PATCH /academic-years/:id/set-current (:39)year doc (isCurrent:true, status:"active")un-sets isCurrent on all others (:69-83); 404 if missing
DELETE /academic-years/:id (:42){message:"OK"}soft delete (:85-88); 404 if missing; no cascade (OQ-3)

DTO sources: create-academic-year.dto.ts:4-21 (name IsString, startDate/endDate IsDateString ISO YYYY-MM-DD, status IsString optional); update-academic-year.dto.ts:4-28 (all optional incl. isCurrent bool).

Client notes: isCurrent editable via PATCH body but the UI uses set-current (which also forces status:"active", academic-year.service.ts:78-80); treat isCurrent:true as a tenant-wide singleton (blueprint rule, COLLECTIONS.md:1023).


2. Grades — grade.controller.ts

Method & PathBody / ParamsSuccess dataRules & Errors
POST /grades (:24){name*, academicYearId?, code?, displayOrder?}grade doc409 DUPLICATE_RESOURCE "Grade "X" already exists." (grade.service.ts:21-23); displayOrder default 0
GET /grades (:27)page,limit,sort,qarray + metadefault sort=displayOrder:1 (:37-39)
GET /grades/:id (:30)grade doc404 "Grade not found." (:28-32)
PATCH /grades/:id (:33)any of {academicYearId?, name?, code?, displayOrder?, status?}grade docpartial $set (:51-56); no rename duplicate check (OQ-2)
DELETE /grades/:id (:36){message:"OK"}soft delete (:58-61); no cascade — sections/classes orphaned (OQ-3)

DTO sources: create-grade.dto.ts:4-24 (name IsString; academicYearId IsMongoId optional — schema optional grade.schema.ts:9-10; code IsString; displayOrder IsInt Min(0) default 0); update-grade.dto.ts:4-30 (adds status IsString).


3. Sections — section.controller.ts

Method & PathBody / ParamsSuccess dataRules & Errors
POST /sections (:24){gradeId*, name*, capacity?, classTeacherId?, roomId?}section docno duplicate guard (OQ-4) (section.service.ts:16-18); capacity default 40
GET /sections (:27)page,limit,sort,qarray + metainsertion order (:41-48)
GET /sections/by-grade/:gradeId (:30)page,limitarray + metascoped {gradeId} (:26-39)
GET /sections/:id (:36)section doc404 "Section not found." (:20-24)
PATCH /sections/:id (:39)any of {gradeId?, name?, capacity?, classTeacherId?, roomId?, status?}section docpartial $set; reparenting orphans classes (class.schema.ts:18-19 required) — client confirm (OQ-3)
DELETE /sections/:id (:42){message:"OK"}soft delete (:62-65); no cascade

DTO sources: create-section.dto.ts:4-28 (gradeId IsMongoId; name IsString e.g. "A"; capacity IsInt Min(1) default 40; classTeacherId IsMongoId optional — user ID; roomId IsString optional); update-section.dto.ts:4-35 (adds status).


4. Classes — class.controller.ts

Method & PathBody / ParamsSuccess dataRules & Errors
POST /classes (:24){academicYearId*, gradeId*, sectionId*, name*, capacity?, campusId?, classTeacherId?, roomId?}class docno duplicate guard (OQ-4) (class.service.ts:16-18); capacity default 40
GET /classes (:27)page,limit,sort,qarray + metainsertion order (:41-53)
GET /classes/by-year/:academicYearId (:30)page,limitarray + metascoped {academicYearId} (:26-39)
GET /classes/:id (:36)class doc404 "Class not found." (:20-24)
PATCH /classes/:id (:39)any of {academicYearId?, campusId?, gradeId?, sectionId?, name?, capacity?, classTeacherId?, roomId?, status?}class docpartial $set (:55-60)
DELETE /classes/:id (:42){message:"OK"}soft delete (:62-65); downstream refs (timetable/attendance/assignments) remain (OQ-3)

DTO sources: create-class.dto.ts:4-41 (academicYearId,gradeId,sectionId IsMongoId required; name IsString e.g. "Grade 10 - A"; campusId IsMongoId optional — ref points at Class schema, likely Campus typo, class.schema.ts:12-13, OQ-6; classTeacherId IsMongoId optional user ID; roomId IsString optional free text); update-class.dto.ts:4-50 (adds status).


5. Subjects — subject.controller.ts

Method & PathBody / ParamsSuccess dataRules & Errors
POST /subjects (:24){code*, name*, shortName?, credits?, maximumMarks?, passingMarks?, theoryMarks?, practicalMarks?}subject doc409 "Subject with code "X" already exists." (subject.service.ts:21-25) + DB unique index fallback (subject.schema.ts:38); defaults 0/100/33/80/20
GET /subjects (:27)page,limit,sort,qarray + metainsertion order (:35-47)
GET /subjects/:id (:30)subject doc404 "Subject not found." (:29-33)
PATCH /subjects/:id (:33)any of {code?, name?, shortName?, credits?, maximumMarks?, passingMarks?, status?}subject docpartial $set (:49-54); theoryMarks/practicalMarks NOT updatable (update-subject.dto.ts:4-42, OQ-5); duplicate code on update → unique index error → 409-mapped by filter (http-exception.filter.ts:32, OQ-2)
DELETE /subjects/:id (:36){message:"OK"}soft delete (:56-59); assignments referencing it stay (OQ-3)

DTO sources: create-subject.dto.ts:4-47 (code,name IsString; shortName IsString; credits IsInt Min(0) default 0; maximumMarks IsInt Min(1) default 100; passingMarks IsInt Min(1) default 33; theoryMarks IsInt Min(0) default 80; practicalMarks IsInt Min(0) default 20); update-subject.dto.ts:4-42 as above.

No cross-field validation (theory+practical ≤ max, passing ≤ max) server-side (OQ-5).


6. Subject Assignments — subject-assignment.controller.ts

Method & PathBody / ParamsSuccess dataRules & Errors
POST /subject-assignments (:21){teacherId*, subjectId*, classId*, academicYearId*} (all IsMongoId)assignment docno duplicate/conflict check (OQ-4) (subject-assignment.service.ts:13-17)
GET /subject-assignments/by-class/:classId (:24)?academicYearId=bare array (no meta){classId, academicYearId} (:19-24)
GET /subject-assignments/by-teacher/:teacherId (:30)?academicYearId=bare array (no meta){teacherId, academicYearId} (:26-31)
DELETE /subject-assignments/:id (:36){message:"OK"}soft delete; 404 "Assignment not found." (:33-36)

No GET /:id and no update endpoint — remove + recreate is the only correction path (see 08 §11 "Replace" flow). Assignment tuples are not unique per (classId, subjectId) (subject-assignment.schema.ts:22-25 indexes are non-unique).


7. Loading / streaming / realtime

ScreenLoadingStreamingRealtime
All listsAppSkeleton rows ≤ 200 ms, infinite-scroll footer
Detailheader skeleton + section skeletons
Explorerper-level skeletons as children load
Any(planned) WS push on set-current/structure change (00-shared/07 §8) — until then refresh-on-focus

8. Client-side error mapping table (module)

ScreencodeUI
Any create/update400 VALIDATION_ERRORper-field errorText from error.details[] (keys = DTO field names); focus first invalid
Any401 UNAUTHENTICATEDsilent refresh → replay once → sessionExpired
Any write403 PERMISSION_DENIEDhidden CTAs + guarded routes (planned) — OQ-1
Year/Grade create409 DUPLICATE_RESOURCEinline AppBanner(error) with server message verbatim
Subject create/update code409"Code already in use" + open existing row
Class/Section/Assignment(server never 409s)client duplicate guard only (OQ-4)
Delete / set-current404 RESOURCE_NOT_FOUNDtreat as already-removed → refresh; snackbar
Any429 RATE_LIMITEDcountdown banner; no auto-retry
Any422 BUSINESS_RULE_VIOLATION(planned) — no service throws it today; map as generic business error
Any5xx INTERNAL_SERVER_ERRORgeneric + requestId; never server internals

9. Pagination & sort (per list)

ListDefault sortServer filter supportq
YearscreatedAt:-1 (academic-year.service.ts:45-47)unused (OQ-7)
GradesdisplayOrder:1 (grade.service.ts:37-39)unused
Sectionsinsertion (section.service.ts:41-48)by-grade/:gradeIdunused
Classesinsertion (class.service.ts:41-53)by-year/:academicYearIdunused
Subjectsinsertion (subject.service.ts:35-47)unused
Assignmentsinsertion (bare array, subject-assignment.service.ts:19-31)by-class, by-teacher (+ academicYearId)n/a

Client policy: preserve server order always; pagination via meta{hasNext} infinite scroll; by-class/by-teacher render all rows (no meta).

10. Optimistic / undo

  • No optimistic writes anywhere: creates/updates/deletes/set-current are server-first; response doc is the canonical state (00-shared/07 §9).
  • Exceptions: filter chips + year switcher are local UI state (optimistic by nature, re-scope queries); client duplicate pre-checks are advisory only.
  • Delete/remove confirmations are destructive and never undoable (soft delete — restoration (planned)).