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

03 — User Journeys (Files Module)

Journeys map to the real endpoint surface (files.controller.ts:29-71). Multi-file, sharing, and signed-URL steps are (planned) / (forward-looking).


1. Journey: Upload a document (teacher, office)

Context screen (fees/circular/record)
  → tap "Attach file"            → POST /api/v1/files/upload  (multipart, field `file`, files.controller.ts:29-41)
  → multipart with `file` field  → FilesService.upload: tenant scoped (files.service.ts:28),
                                     storage key `<uuid>--<originalName>` (files.service.ts:31),
                                     provider upload → FileRecord persisted (files.service.ts:36-45)
  → 201 → record JSON {id, originalName, mimeType, size, ...}
  → client shows file tile in context list (GET /files refreshed)
  • Error paths: 401/403 (guard layer, (planned) — guards not yet implemented), 500 provider outage; offline → client-side retry queue ((forward-looking)).
  • Large-file reality: no size cap is enforced server-side today — the entire buffer round-trips through memory (FileInterceptor memory storage → upload(input.buffer)res.send(buffer)). QA and client must assume ~tens of MB max for safety.

2. Journey: Download and open (student, parent)

File tile → tap download
  → GET /api/v1/files/:id/download   (files.controller.ts:55-64)
  → service: findById → storage.download(storageFileId) → Buffer (files.service.ts:58-64)
  → headers: Content-Type = stored mimeType, Content-Disposition: attachment; filename="<originalName>"
  → body: whole file → OS open/save
  • Not a redirect, not a signed URL — a proxied buffered stream. Fine for <25 MB; for large exports the blueprint wants streaming (STORAGE_ARCHITECTURE.md:67) (planned).
  • No Range support → no resume for interrupted downloads; client should retry from scratch.

3. Journey: Delete a misplaced file (admin)

File tile → overflow menu → Delete → confirm dialog
  → DELETE /api/v1/files/:id        (files.controller.ts:66-71)
  → service: findById (404 if gone) → storage.delete(storageFileId) → repo.softDelete (files.service.ts:66-70)
  → { message: 'File deleted' } → tile removed optimistically; audit note (proposed)
  • Semantics: object destroyed at provider, metadata soft-deleted (still visible to admin-only future audit queries; excluded from normal lists via BaseRepository).

4. Journey: Verify a batch of uploads (office manager)

GET /api/v1/files → array sorted createdAt desc (files.service.ts:48-50)
  → client groups/filters by its own context metadata (no server filter exists)
  → tap each → detail sheet (GET /files/:id) → download spot-check
  • (proposed) enhancement: pagination ?page&limit, ?mimeType= filter — no server support.

5. Journey: Signed-URL download (planned)

POST (planned) or GET /files/:id/signed-url?expiresIn=3600
  → provider.getSignedUrl(storageFileId, ttl)   (storage-provider.ts:25)
  → R2 presigned GET (r2.provider.ts:77-86); Appwrite view URL (appwrite-storage.provider.ts:67-74,
     NOTE: ignores expiresInSeconds); local returns fake path `/api/v1/files/<id>`
     (local-storage.provider.ts:47-51, ponytail shortcut)
  → client downloads direct from provider; expiry → 403 → re-request

Journey map (mermaid)

flowchart LR
    A[Context screen] --> B[Upload sheet]
    B -->|POST /files/upload| C[(Storage provider)]
    C -->|StoredFile| D[FileRecord in Mongo]
    D --> E[File tile in list]
    E -->|GET /files/:id/download| F[OS open/save]
    E -->|DELETE /files/:id| G[(object deleted + soft-delete)]