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 (Feature Flags Module)

Exact wire contract for every screen → endpoint. Base /api/v1; envelope per 00-shared/07. All endpoints from src/modules/feature-flags/feature-flags.controller.ts and the org-overlay from src/modules/organizations/organizations.controller.ts; business rules from feature-flags.service.ts. Global guards: RateLimitGuardJwtAuthGuardRbacGuard (app.module.ts:129-131). No flag endpoint is @Public(), and none declares @Permissions() — effective auth = valid JWT only (OQ-5; perms declared at permissions.constants.ts:78-80).


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} (response-envelope.interceptor.ts:50-59)
Error{success:false, message, error:{code,details?}, timestamp, requestId} (http-exception.filter.ts:73-81)
Codes400 VALIDATION_ERROR · 401 UNAUTHENTICATED · 403 PERMISSION_DENIED · 404 RESOURCE_NOT_FOUND · 409 DUPLICATE_RESOURCE · 422 BUSINESS_RULE_VIOLATION · 429 RATE_LIMITED · 5xx INTERNAL_SERVER_ERROR (http-exception.filter.ts:27-35)
TenancytenantId from JWT only; never in body. Reads auto-scoped (base.repository.ts:20-30); platform-admin reads bypass scope; upsert requires tenant context (feature-flag.repository.ts:37)
Rate limitglobal api tier 100/min default (rate-limit.constants.ts:6, rate-limit.guard.ts:36)
Cachingnone in code; blueprint: Redis sl:global:featureflag:{name} poll+cache 30 s (planned) (CACHE_ARCHITECTURE.md:33,48)
Offlinereads from last-good client cache; writes blocked
Retrybackoff on 5xx/network; no auto-retry on 429; bulk retry = failed subset only

Screen: Flags list (all / module / enabled)

EndpointGET /feature-flags (feature-flags.controller.ts:23-28)
Query?module=<string> optional → findByModule else findAll
Success200 data: [FeatureFlagDoc…] sorted by key asc (feature-flags.service.ts:11) — plain array, no meta (not paginated)
Errors401; 5xx
Enabled tabGET /feature-flags/enabled (feature-flags.controller.ts:30-34) → data: [docs with enabled:true] (feature-flag.repository.ts:28-30) — this is the gating set the client cubit mirrors
Client flowfetch on screen open; RefreshIndicator refetch; cache last-good per sl:cache:featureflags:{query}

Doc shape (FeatureFlag + BaseSchema)

{
  "_id": "…", "tenantId": "…",
  "key": "channels.whatsapp", "enabled": false,
  "label": "WhatsApp", "description": "…", "module": "channels",
  "createdBy": "…", "updatedBy": "…",
  "isDeleted": false, "deletedAt": null, "deletedBy": null,
  "version": 0, "createdAt": "…", "updatedAt": "…"
}

(feature-flag.schema.ts:7-22, base.schema.ts:10-34; timestamps:true)


Screen: Flag detail

EndpointGET /feature-flags/:key (feature-flags.controller.ts:36-40)
Success200 data: doc
Errors404 RESOURCE_NOT_FOUND "Feature flag "" not found." (feature-flags.service.ts:29); 401
Notesoft-deleted flags are invisible (scoped isDeleted:false) → 404

Screen: Flag editor (create/edit) — single upsert

EndpointPUT /feature-flags (feature-flags.controller.ts:42-46) — upsert: create or update
BodyUpdateFeatureFlagDto: {key: string (req), enabled: boolean (req), label?: string, description?: string, module?: string} (update-feature-flag.dto.ts:4-26)
Success200 data: doc{new:true} upsert result (feature-flag.repository.ts:41)
Persistence truth$set: {enabled, label} only (feature-flag.repository.ts:40) — description/module dropped on every write (OQ-3)
Errors400 VALIDATION_ERROR (missing key/enabled, wrong types — details[]); 5xx incl. E11000 duplicate on re-create after soft-delete (OQ-4)
Client flowcreate mode: new key → new doc; edit mode: key read-only, label/enabled editable; never claim description/module saved

Screen: Bulk update

EndpointPUT /feature-flags/bulk (feature-flags.controller.ts:48-52)
BodyJSON array of UpdateFeatureFlagDto (feature-flags.controller.ts:50)
Success200 data: [docs] — applied sequentially, no transaction, no rollback (feature-flags.service.ts:40-44)
Errors400 whole-body validation (array-item errors in details[]); 5xx mid-loop → partial apply
Client flowper-row results by key diff; retry failed subset only

Screen: Delete flag

EndpointDELETE /feature-flags/:key (feature-flags.controller.ts:54-58)
Success200 (envelope, data absent/void) — soft delete by _id (feature-flags.service.ts:47-52, base.repository.ts:68-74)
Errors404 first-guard (findByKey) / second-guard (softDelete returns falsy → 404) (feature-flags.service.ts:48-51); 401
Notekey slot retained by unique {tenantId,key} index (feature-flag.schema.ts:26) → re-create may 500 (OQ-4)

Screen: Org-level flags (organization settings context)

EndpointGET /organizations/:id/feature-flags (organizations.controller.ts:74-79)
Success200 data: {flagKey: boolean…} from org.metadata.featureFlags (organizations.service.ts:139-142); empty object when unset
EndpointPATCH /organizations/:id/feature-flags (organizations.controller.ts:81-89)
Bodyplain Record<string, boolean> (organizations.controller.ts:86) — no DTO, no validation
Semanticsfull replace: $set: {'metadata.featureFlags': flags} (organizations.service.ts:149-151) — omitted keys are deleted
Success200 data: {flagKey: boolean…} (updated map)
Errors404 org missing; 401; (id is org doc id — platform/org-admin context)
Client flowload map → merge locally → PATCH full merged map; diff preview (proposed)
Notemetadata is a free-form object (organization.schema.ts:129-130); this surface is not connected to the feature_flags collection or isEnabled() (OQ-2)

Loading / streaming / realtime

ScreenLoadingStreamingRealtime
listAppSkeleton(list)(planned) WS topic featureflag.changed (not in 00-shared/07 §8 topic list; no domain event in code — OQ-7); client falls back to TTL refresh
detailskeleton
editorsave spinner
bulkprogress bar

Client-side error mapping table (module)

ScreencodeUI
any401silent refresh; fail → sessionExpired
detail404AppEmptyState "Flag not found — deleted?"
editor save400field errors from details[]
editor save5xxsnackbar + requestId; form retained
toggle5xxrollback switch + snackbar
bulk400offending rows highlighted
bulk5xx midpartial results + "retry failed"
delete404treat as already-removed
org overlay404org missing → settings error state

Pagination

None — every flag endpoint returns full arrays (no meta). Client renders all rows (feature-flags.service.ts:10-20); bulk selection is client-side.

Optimistic / undo

  • Toggles (list + detail): optimistic with rollback (00-shared/06 §3.5); undo = flip again.
  • Editor save, bulk apply, delete, org overlay PATCH: server-confirm, never optimistic.
  • Delete has no undo (soft delete; no restore endpoint; re-create caveat OQ-4).