12 — API Mapping (Timetable Module)
- 0. Module-wide request envelope & client policy
- 1. Create — the only write
- 2. Reads — two scopes, no pagination
- 3. Supporting catalogs (read-only for this module)
- 4. Loading / streaming / realtime
- 5. Client error mapping table (module)
- 6. Pagination summary
- 7. Optimistic / undo
Exact wire contract per 00-shared/07. Base
/api/v1(main.ts:44-48); envelope{success,message,data,meta?,timestamp,requestId}. Global guards:RateLimitGuard → JwtAuthGuard → RbacGuard(app.module.ts:129-131); no timetable endpoint carries RBAC metadata (rbac.guard.ts:29→ JWT-only, 01 §5).BearerJWT;tenantIdfrom token only (base.repository.ts:33-35).
0. Module-wide request envelope & client policy
| Aspect | Contract |
|---|---|
| Headers | Authorization: Bearer; x-request-id client UUID; Content-Type: application/json |
| Tenancy | never in body; server injects tenantId + isDeleted:false scope (base.repository.ts:20-30) |
| Caching | catalogs (classes, subjects, teachers, rooms, years) 24 h TTL; timetable grids 5 min TTL (00-shared/06 §3.3) |
| Offline | reads last-good cache + banner; writes blocked (no offline queue) |
| Retry | backoff on 5xx/network; no auto-retry on 429 |
| Idempotency | create is not idempotent — no duplicate guard on identical slots (class-level dupes legal, OQ-1); Idempotency-Key optional (B6 shared ledger) |
1. Create — the only write
POST /timetable — create entry (S4)
- Body:
CreateTimetableEntryDto(create-timetable-entry.dto.ts:5-38, F1): exactlyclassId,subjectId,teacherId,roomId?,dayOfWeek(enum Mon–Sat,timetable.schema.ts:7-14),startTime,endTime,academicYearId. Unknown keys rejected (whitelist+forbidNonWhitelisted,main.ts:50-57). - 200
data: TimetableEntry(raw doc, refs as ObjectIds; timestamps auto,timetable.schema.ts:16). - 409
ConflictException"Schedule conflict detected" (timetable.service.ts:28): same-day existing entry sharesteacherIdorroomIdwith overlapping[startTime,endTime)(timetable.service.ts:17-22,62-69). No structured error code — client matches status. - 400 validation (invalid ids,
sundaydayOfWeek, unknown keys). - Side-effects:
TimetableEntryCreatedemitted (timetable.service.ts:33-44, payload{entryId, classId, teacherId}) — no queue routing found (01 §6).
2. Reads — two scopes, no pagination
GET /timetable?classId=<id> — class grid (S1)
classIdtakes precedence when both params present (timetable.controller.ts:26-27).- Success:
data: TimetableEntry[]— non-paginated array, nometa. - Sort: server-side
{dayOfWeek:1, startTime:1}(timetable.service.ts:48-53).
GET /timetable?teacherId=<id> — teacher grid (S2)
- Same shape; sort
{dayOfWeek:1, startTime:1}(timetable.service.ts:55-60).
GET /timetable — bare (no params)
- Returns
[](timetable.controller.ts:28) — no "all entries" fetch; client always scopes (OQ-4).
Missing surfaces (derived)
- No
?roomId=filter (timetable.controller.ts:22-25) — room view is a client composition (03 §J5); native filter(planned). - No
:idroutes, noPATCH, noDELETE(timetable.controller.ts:10) — update/delete(planned)(IMPLEMENTATION_PLAN.md:226). - No pagination, filters, or date params — grid pages are the whole week by model
(weekday + time only,
timetable.schema.ts:30-37).
3. Supporting catalogs (read-only for this module)
| Endpoint | Used by | Source |
|---|---|---|
GET /classes (+ by-year/:academicYearId) | S1 class picker, F1 classId | class.controller.ts:27-33 |
GET /subjects (paginated) | F1 subject picker, slot labels | subject.controller.ts:27-29 |
GET /teachers (paginated) | F1 teacher picker, slot labels | teacher.controller.ts:27-29 |
GET /rooms (paginated, ?page=&limit=) | F1 room picker, room labels | rooms.controller.ts:30-34 |
GET /academic-years | F1 year picker (isCurrent) | academic-year.controller.ts:27-30 |
GET /dashboard/overview | timetable-related KPIs (proposed) | dashboard module |
4. Loading / streaming / realtime
| Screen | Loading | Realtime |
|---|---|---|
| S1/S2/S3 grid | skeleton grid | — (timetable WS events (planned); TimetableEntryCreated unrouted today, 01 §6) |
| S4 editor | submit spinner only | — |
| S5 banner | — | appears on 409 / pre-flight |
5. Client error mapping table (module)
| Screen | Code | UI |
|---|---|---|
| all | 401 | silent refresh; fail → sessionExpired |
| all | 403 | 403 screen (future server enforcement; client-side perm gate today) |
| grid | 404 (catalog ref gone) | slot shows "—" |
| S4 | 409 | ConflictBanner, keep form |
| S4 | 400 | per-field errors |
| all | 429 | "Try again in a moment", no auto-retry |
| all | 5xx | generic + requestId + retry |
6. Pagination summary
GET /timetable: non-paginated array — one request = whole weekly grid per scope. Large-school caveat: verify payload size; server pagination(planned)(IMPLEMENTATION_PLAN.md:226).- Catalogs paginate via
PaginationQueryDto— client loads withlimit=100and caches 24 h.
7. Optimistic / undo
- Grid refresh + editor create: server-first (read-after-write; insert slot only after 200).
- No destructive op exists today; delete
(planned)will follow 00-shared/06 §3.5 (confirm dialog; soft-delete viaBaseRepositorypattern —base.repository.ts:68-74). - Drag & drop opens the editor (create semantics) — never an optimistic move (09 §5).