12 — API Mapping (Rooms Module)
- E1 — Create room
- E2 — List rooms
- E3 — Get room by ID
- E4 — Update room
- E5 — Delete room (soft)
- Client contract summary (all screens)
- Endpoint → screen matrix
Exact endpoints per screen. Wire contract per 00-shared/07: base
/api/v1, Bearer JWT, success{success:true,message:"OK",data,meta?,timestamp,requestId}, error envelope with codes. Only shapes in code are used.(planned)/(forward-looking)marked.
E1 — Create room
| Endpoint | POST /api/v1/rooms (rooms.controller.ts:24-28) |
| Guard | JwtAuthGuard only (rooms.controller.ts:19); RBAC rooms.create not enforced server-side (OQ-2, permissions.constants.ts:51); client gates UI |
| Request | CreateRoomDto (create-room.dto.ts:11-38) — name, code required; capacity, type (enum, default classroom), building, facilities optional |
| Response | 201 envelope, data = saved room doc (rooms.service.ts:18-23); tenantId injected from context, never from body (base.repository.ts:32-36) |
| Errors | 400 VALIDATION_ERROR (missing name/code, bad type); 409 ConflictException "Room code "X" already exists." (rooms.service.ts:19-21); 401; 429 RATE_LIMITED; 5xx |
| Client | S3 create; on success → detail + snackbar; 409 → inline under code field |
| Cache | none (write); on success invalidate list cache key |
E2 — List rooms
| Endpoint | GET /api/v1/rooms?page&limit (rooms.controller.ts:30-34) |
| Params | page ≥ 1 default 1; limit default 20 (rooms.controller.ts:32); no sort, q, or filters in code — PaginationQueryDto.sort/q (pagination-query.dto.ts:21-29) unused on this route (OQ-3); order = Mongo natural (_id) order |
| Response | 200 envelope: data: Room[] + meta {page, limit, totalItems, totalPages, hasNext, hasPrevious} (rooms.service.ts:25-34; pagination-query.dto.ts:32-39,41-54) |
| Errors | 401; 429; 5xx |
| Client | S1; infinite scroll on hasNext; pull-to-refresh; client-side filter/search (proposed) until server params (planned) |
| Cache | client paginated cache sl:{tenantId}:rooms:{page}:{limit} TTL 5 min; invalidated on E1/E4/E5 success |
E3 — Get room by ID
| Endpoint | GET /api/v1/rooms/:id (rooms.controller.ts:36-40) |
| Response | 200 envelope, data = room doc (rooms.service.ts:36-40) |
| Errors | 400 VALIDATION_ERROR (invalid ObjectId → CastError mapping, shared filter); 404 RESOURCE_NOT_FOUND "Room not found." (rooms.service.ts:38); cross-tenant id → 404, no existence leak (base.repository.ts:24-29); 401; 429 |
| Client | S2 detail + S3 edit prefill; stale-while-revalidate OK |
E4 — Update room
| Endpoint | PATCH /api/v1/rooms/:id (rooms.controller.ts:42-46) |
| Request | CreateRoomDto (rooms.controller.ts:44) — there is no update-room.dto.ts (OQ-6); name+code remain required on PATCH; partial body → 400 |
| Behaviour | updateById → $set: dto + $inc: {version: 1}, returns updated doc (rooms.service.ts:42-46; base.repository.ts:57-66) |
| Errors | 400 (missing required fields, bad enum); 404 (rooms.service.ts:44); duplicate-code update → uncaught E11000 unique-index error → 500 (OQ-1) (room.schema.ts:38); 401; 429 |
| Client | S3 edit; success → detail reconcile from response; client-side duplicate warning (proposed); server pre-check (planned) OQ-1 |
| Cache | invalidate list + detail keys on success |
E5 — Delete room (soft)
| Endpoint | DELETE /api/v1/rooms/:id (rooms.controller.ts:48-52) |
| Behaviour | soft delete: isDeleted:true, deletedAt, deletedBy + version (rooms.service.ts:48-51; base.repository.ts:68-74); returns 200, no payload (void handler) |
| Errors | 404 (rooms.service.ts:50); 401; 429 |
| Gaps | no in-use guard (OQ-4) — succeeds even if referenced by timetable/bookings; hard purge not scheduled (rooms never hard-deleted in code) |
| Client | S4 typed-confirm dialog; on success remove row locally + snackbar; 404 → "already deleted" |
| Audit | deletedAt/deletedBy recorded (base.schema.ts:23-27) — future audit surface (planned) (IMPLEMENTATION_PLAN.md Phase 5) |
Client contract summary (all screens)
| Concern | Rule |
|---|---|
| Auth | Bearer JWT; 401 → single-flight refresh → replay; fail → session expiry (00-shared/06 §3.6) |
| RBAC | server does not enforce rooms.* today (OQ-2); client gates by permission list from permissions.constants.ts:50-53; when server guard lands, treat 403 as route-hide |
| Optimistic | none for mutations — server result always shown (delete is void; local removal after 200) |
| Idempotency | PATCH/DELETE retry-safe; no Idempotency-Key support confirmed |
| Offline | reads from last-good cache + banner; writes blocked (no module offline queue) |
| Pagination | page/limit + meta exact (pagination-query.dto.ts:5-54); infinite scroll driven by hasNext |
| Filtering | client-side (proposed); server params (planned) OQ-3 |
| Realtime | no WS topics today; (forward-looking) rooms.updated for multi-device sync |
| Error mapping | 00-shared/06 §5: 400 field errors, 403 hide/deny, 404 empty/not-found, 409 inline duplicate, 429 backoff, 5xx generic+requestId |
Endpoint → screen matrix
| Endpoint | S1 List | S2 Detail | S3 Editor | S4 Delete |
|---|---|---|---|---|
POST /rooms | — | — | create submit | — |
GET /rooms?page&limit | load/load-more/refresh | — | duplicate hint (proposed) | — |
GET /rooms/:id | — | load | edit prefill | — |
PATCH /rooms/:id | — | reconcile | edit submit | — |
DELETE /rooms/:id | row remove | menu trigger | — | confirm submit |