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

Exact wire contract for every screen → endpoint. Base /api/v1; envelope per 00-shared/07. All endpoints from src/modules/library/library.controller.ts (@Controller('books'), :23); business rules from library.service.ts. Guards: @UseGuards(JwtAuthGuard) at controller level (library.controller.ts:22) — no RBAC metadata (OQ-1). Tenant from JWT only; never in body (base.repository.ts scoped filters).


0. Module-wide request envelope & client policy

AspectContract
Basehttps://api.<domain>/api/v1
HeadersAuthorization: Bearer <accessToken>; x-request-id; Content-Type: application/json
Response{success, message:"OK", data, meta?, timestamp, requestId} (response-envelope.interceptor.ts:44-52)
Error{success:false, message, error:{code, details?}, timestamp, requestId} (http-exception.filter.ts:17-24,74-78); codes include VALIDATION_ERROR (400), DUPLICATE_RESOURCE (409), RATE_LIMITED (429), RESOURCE_NOT_FOUND (404) (:28-34)
PaginationGET /books only: page (≥1, default 1), limit (default 20) (library.controller.ts:36-38); meta = {page, limit, totalItems, totalPages, hasNext, hasPrevious} via buildPaginationMeta (library.service.ts:75); borrow endpoints are not paginated (library.service.ts:195-201)
Cachingreads cached client-side (catalog 5 min, borrows 1 min, detail 1 min); no cache on write responses
Offlinereads from cache; writes blocked (no offline queue)
Retrybackoff on 5xx/network; no auto-retry on 409 or 429
Idempotencynone server-side (issue/return are not idempotent; double-tap guarded client-side — QA-2)

Screen: Catalog list / search — GET /books

EndpointGET /api/v1/books?page&limit&q (library.controller.ts:33-41)
Querypage (default 1), limit (default 20) — Number() cast (:40); q optional
Success200 data: [BookDoc…] + meta; with q → regex title/author/ISBN case-insensitive (book.repository.ts:26-30); without → all, sort: {title: 1} (:36); skip (page−1)*limit (library.service.ts:59,71)
Countmatched via $or regex or {} (library.service.ts:60-67,73)
Errors400 invalid ints; 5xx
Filterscategory/status/availability not supported server-side — client-side only (OQ-5)

BookDoc (book.schema.ts:15-48, base.schema.ts): _id, tenantId, title, author, isbn, publisher?, category?, edition?, totalCopies, availableCopies, status(available|borrowed|damaged|lost), shelfLocation?, description?, createdAt, updatedAt, version. Indexes: {tenantId,isbn} unique (:51), {tenantId,title} (:52), {tenantId,author} (:53).

Screen: Book detail — GET /books/:id

EndpointGET /api/v1/books/:id (library.controller.ts:43-47)
Success200 data: BookDoc
Errors404 RESOURCE_NOT_FOUND "Book not found." (library.service.ts:80); 400 invalid id (CastError → VALIDATION_ERROR)

Screen: Create book — POST /books

EndpointPOST /api/v1/books (library.controller.ts:27-31)
BodyCreateBookDto (create-book.dto.ts:4-47): title*, author*, isbn*, publisher?, category?, edition?, totalCopies?(@Min 1), shelfLocation?, description?
Success201 data: BookDoctotalCopies = dto.totalCopies ?? 1, availableCopies = totalCopies (library.service.ts:41-46)
Errors409 DUPLICATE_RESOURCE "Book with ISBN "…" already exists." (library.service.ts:37-39) + unique index {tenantId,isbn} (book.schema.ts:51); 400 validation

Screen: Edit book — PATCH /books/:id

EndpointPATCH /api/v1/books/:id (library.controller.ts:49-53)
Bodyany subset of CreateBookDto fields (update-book.dto.ts:4)
Success200 data: BookDoc; if totalCopies changed → availableCopies = max(0, available + Δ) (library.service.ts:87-90)
Errors404 (library.service.ts:92); 400 validation; 409 duplicate ISBN if ISBN changed to an existing one

Screen: Delete book — DELETE /books/:id

EndpointDELETE /api/v1/books/:id (library.controller.ts:55-59)
Success200 — soft delete (library.service.ts:103 via BaseRepository.softDelete)
Errors409 "Cannot delete book with active borrow records." (library.service.ts:99-101); 404 (:104)

Screen: Issue form — POST /books/issue

EndpointPOST /api/v1/books/issue (library.controller.ts:61-65)
Body{bookId*, studentId*, dueDate* (ISO), notes?} (issue-book.dto.ts:4-21)
Success201 data: BorrowRecordDocstatus: ACTIVE, borrowedAt: now, dueDate as sent (library.service.ts:132-139); book availableCopies − 1, status → borrowed if 0 remain (:122-130)
Side effectBookIssued {bookId, studentId} domain event (library.service.ts:141-148)
Errors404 book (:108); 409 "No copies available for borrowing." (:109-111); 409 "Student already has maximum number of borrowed books." (cap 5, :116-120); 400 validation
Noteno server min-date on dueDate — client enforces future; no idempotency key

Screen: Return form — POST /books/return

EndpointPOST /api/v1/books/return (library.controller.ts:67-71)
Body{borrowRecordId*, fineAmount?(@Min 0), notes?} (return-book.dto.ts:4-19)
Success200 data: BorrowRecordDocstatus: RETURNED, returnedAt: now, fineAmount = dto.fineAmount ?? calculateFine(dueDate), fineStatus = pending iff fineAmount > 0 else unset (library.service.ts:159-169); book availableCopies + 1, status → available (:172-178)
Side effectBookReturned {bookId, studentId, fineAmount} event (library.service.ts:180-191)
Errors404 record (:153-154); 409 "Book was not actively borrowed." (:155-157); 400 validation
NotefineAmount override is trusted server-side — staff-only UI (QA-4)

BorrowRecordDoc (borrow-record.schema.ts:20-58): _id, tenantId, bookId (populated by repos: borrow-record.repository.ts:31,42), studentId, issuedBy? (never written — OQ-3), borrowedAt, dueDate, returnedAt?, status(active|returned|overdue|lost), fineAmount, fineStatus?(pending|paid|waived), notes?, createdAt, updatedAt. Indexes (:61-63): {tenantId,bookId,studentId}, {tenantId,studentId,status}, {tenantId,dueDate,status}.

Screen: Borrowed-by-me — history & active

HistoryGET /api/v1/books/borrows/:studentId (library.controller.ts:73-77) → 200 [BorrowRecordDoc…] sorted borrowedAt desc (library.service.ts:195-197); no pagination, no populate of studentId
ActiveGET /api/v1/books/borrows/:studentId/active (library.controller.ts:79-83) → 200 [BorrowRecordDoc…] where status: ACTIVE (library.service.ts:199-201, borrow-record.repository.ts:24-32, bookId populated :31)
Errors400 invalid id; 5xx
Noteno me variant — client supplies studentId from profile (OQ-9)

Screen: Fine pay — POST /books/fines/:borrowRecordId/pay

EndpointPOST /api/v1/books/fines/:borrowRecordId/pay (library.controller.ts:85-89) — no body
Success200 data: BorrowRecordDoc with fineStatus: PAID (library.service.ts:203-209)
Errors404 record (:207); 400 invalid id
Noteserver does not check fineStatus before paying and does not validate fine > 0 (OQ-6) — client gates the button

Not exposed (gaps the client must work around)

NeedStatus
List all active borrows (overdue sweep)(planned)findOverdue() exists unused (borrow-record.repository.ts:34-43); IMPLEMENTATION_PLAN.md:228
Mark book damaged/lost, record lost(planned) — enums exist (book.schema.ts:7-12)
Renew / reserve(planned) — IMPLEMENTATION_PLAN.md:228
Catalog import/export(planned) — IMPLEMENTATION_PLAN.md:172,228
Waive fine(planned)FineStatus.WAIVED exists (borrow-record.schema.ts:17) but nothing sets it
Book cover / attachments(proposed) — files module exists (files.controller.ts:29-71), Book has no cover field (OQ-8)
QR/barcode scanning(forward-looking)

Loading / streaming / realtime

ScreenLoadingRealtime
Catalog listAppSkeletonre-fetch on focus; WS (planned)
Book detailskeletonre-fetch on focus (issue/return elsewhere)
Borrowed-by-meskeletonre-fetch on focus
Issue/Return/Fine paybutton spinner

Client-side error mapping (module)

ScreencodeUI
issue409 (copies/cap)banner + refresh availability
return409 (not active)banner "Already returned" + refresh
create/update409 (ISBN)inline field error
any404AppEmptyState / close form + snackbar
any400field errors
any401 → refresh → failsessionExpired
any429countdown, no retry
any5xxgeneric + requestId, retry

Optimistic / undo

  • No optimistic mutations — issue/return/fine pay/create/update/delete are all server-confirmed (00-shared/07 §9).
  • Undo: in-form edits only; delete = confirm dialog (soft delete server-side); no undo for issue/return (record lifecycle instead).

Notifications surface

BookIssued / BookReturned events (library.service.ts:141-148,180-191) are on the domain event bus; notification/email consumers (proposed).