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

01 — Product Overview (Settings Module)

StudyLyon — multi-tenant ERP / School Management API. This package designs the Settings module client (Flutter, forward-looking spec) against the implemented NestJS backend. All endpoints, DTO fields, schemas, and wire contracts are derived directly from src/modules/settings/**, src/modules/organizations/**, src/modules/feature-flags/**, src/modules/rbac/permissions.constants.ts, studylyon-blueprint/04-Modules/Organizations.md and 03-Database/COLLECTIONS.md. No feature is invented; gaps are flagged in the Assumptions & Open Questions section.


1. Purpose

Settings is the tenant-scoped key/value configuration store of StudyLyon. It gives each organization a flat, arbitrarily-typed setting registry — key → value — grouped into six fixed buckets, read/written through a thin CRUD API.

ResponsibilitySource
List all tenant settings (sorted by group then key)settings.service.ts:10-12
List settings of one group (?group= filter)settings.controller.ts:26-28, settings.service.ts:14-16
Read a single setting by keysettings.controller.ts:31-35, settings.service.ts:18-22
Create-or-update a setting (upsert per key)settings.controller.ts:37-41, settings.service.ts:24-26
Bulk upsert (sequential per-key)settings.controller.ts:43-47, settings.service.ts:28-34
Delete a setting (soft delete)settings.controller.ts:49-53, settings.service.ts:36-41
Tenant isolation on every query/upsertbase.repository.ts:20-30, setting.repository.ts:21-40

The module is deliberately thin. It is a generic store: there is no per-key schema, no setting registry, no validation of values, and no history. The "rich" org-level configuration lives in a second, separate surface: the settings object embedded in the organizations document (organization.schema.ts:96-112) with its own endpoints (GET/PATCH /organizations/:id/settings). The blueprint separates them deliberately — "Settings separated from organizations to avoid hot-document writes" (Organizations.md:60), and defines a dedicated organization_settings collection (COLLECTIONS.md:767-783). Today only the standalone settings collection is implemented in code; the organization_settings collection is a blueprint concept not yet in code.

2. Business goals

GoalMeasure
Any org-level knob in one placeAll settings CRUD via 5 endpoints; groups cover academic, attendance, grading, notification, theme, general (setting.schema.ts:7-14)
Zero-conflict multi-tenant isolationUnique index {tenantId, key} (setting.schema.ts:38); tenantId injected by repository, never from body (base.repository.ts:33-35)
No hot-writes on org documentStandalone collection + upsert semantics (Organizations.md:60)
Config written by machines and adminsIdempotent PUT upsert; bulk endpoint for batch imports (settings.service.ts:24-34)
Delete ≠ data lossSoft delete with audit markers (base.repository.ts:68-74)

3. User goals

  • Org admin: browse every org setting in one screen, edit any value with the right input type, save one or many, remove stale keys.
  • Setting editor (staff/admin with settings.* perms): find a key quickly (group filter
    • client search — the API has no search param), edit JSON/numbers/booleans without breaking values.
  • Platform admin: cross-tenant visibility for support (platform admin bypasses the tenant scope, base.repository.ts:21-23).
  • Consumers (other modules / future UI): read settings programmatically via GET /settings/:key or GET /settings?group=.

4. Stakeholders

Org admins, delegated setting editors, platform support, module developers (each module reads its own settings at runtime), QA/design/engineering. Note: today no backend consumer reads settings — nothing in src/ imports SettingsService outside the settings module (no cross-module call, per AGENTS.md module-boundary rule); consumption is via API or future events.

5. Why this exists

Schools differ in grading, attendance rules, notification preferences, and theming. A generic per-tenant store lets the platform ship one codebase with per-org behavior without schema migration per feature, and keeps high-frequency config writes off the hot organizations document (COLLECTIONS.md:781).

6. Dependencies

DependencyRoleSource
Auth (JWT)every settings endpoint requires a bearer tokensettings.controller.ts:12,19; global guards app.module.ts:129-131
RBACsettings.read/update/delete permission constants existpermissions.constants.ts:75-77not enforced on this controller (OQ-2)
TenantContextServicetenant scoping of all queriessetting.repository.ts:16,34; base.repository.ts:20-30
Organizations moduleseparate embedded settings surface (attendance/academic/theme)organization.schema.ts:96-112; organizations.controller.ts:56-72
Feature Flags modulesibling boolean config surfacefeature-flags.controller.ts:23-58
BaseRepositorysoft-delete + version + audit fieldsbase.schema.ts:8-35, base.repository.ts:32-74
Mongo collectionsettings (implemented); organization_settings (blueprint only)setting.schema.ts:16; COLLECTIONS.md:767-783

7. Success metrics

  • List load < 300 ms (settings volume per tenant is small — tens to low hundreds of keys).
  • Save round-trip < 500 ms p95; upsert idempotent — re-tap safe.
  • Zero cross-tenant leaks (unique index + scopedFilter; QA-perm).
  • Bulk save of N keys: all-or-nothing perception on UI despite sequential server loop (failure surfaced per item; see OQ-4).

8. Edge cases (derived)

  • GET /settings/:key missing key → 404 RESOURCE_NOT_FOUND "Setting "key" not found." (settings.service.ts:20).
  • PUT /settings missing key or value → class-validator has no decorators on them (update-setting.dto.ts:5-10) → Mongoose required validation error → 500 (OQ-3).
  • DELETE /settings/:key → soft delete; the unique index {tenantId, key} (setting.schema.ts:38) still holds the soft-deleted doc → re-creating the same key after delete hits a duplicate-key error → 500 (OQ-5).
  • label/description are accepted by the DTO (update-setting.dto.ts:17-25) but the service never persists them — only key/value/group reach the repo (settings.service.ts:25; setting.repository.ts:37).
  • isEncrypted exists on the schema (setting.schema.ts:33-34) but nothing sets it and no encryption path exists (OQ-6).
  • Bulk update runs a sequential loop, not a transaction (settings.service.ts:28-34) — a mid-batch failure leaves earlier keys saved.
  • Platform admin GET /settings lists settings of all tenants (scope bypass, base.repository.ts:21-23).
  • Values are untyped: value: unknown (update-setting.dto.ts:10) and @Prop({type: Object}) (setting.schema.ts:21-22) → the client must infer the editor type from the runtime value.

9. Assumptions (module)

  • Mobile client is forward-looking: backend is complete; this package is the UI-side spec.
  • The settings list is NOT paginated: findAll returns a bare array (settings.service.ts:10-12) and the envelope interceptor only adds meta for {data, meta} payloads (response-envelope.interceptor.ts:25-32,55-59) — the settings list is a single fetch with meta omitted.
  • Group set is fixed to the enum: academic, attendance, grading, notification, theme, general (setting.schema.ts:7-14). A COACHING group is (planned)IMPLEMENTATION_PLAN.md:773.
  • No setting definitions exist: the UI is data-driven (render whatever keys exist); it cannot show a canonical "every setting" catalog, defaults, or per-key docs.
  • Settings written via API are immediately consistent for subsequent reads (single Mongo doc, no cache layer in the settings path).

10. Open questions (module-level; global ledger in 00-shared/12)

#ItemImpact
OQ-1Which module(s) consume settings at runtime? No consumer exists in src/.Copy/help text, "who reads this" metadata
OQ-2settings.read/update/delete (permissions.constants.ts:75-77) are not enforced — the controller has JWT guard only (settings.controller.ts:19) and no @Permissions metadata. Any authenticated tenant user can read/edit/delete every setting.Role-based UI gating vs server truth; 403 UX
OQ-3PUT /settings with missing key/value → Mongoose required error → 500 instead of 400 (no @IsString/@IsDefined on the DTO).Client must always send both; error mapping
OQ-4Bulk loop is non-transactional (settings.service.ts:28-34) — partial failures possible. Add a transaction or accept per-item results?Save-all UX, retry semantics
OQ-5Soft delete + unique {tenantId, key} index = a deleted key can never be recreated (E11000 → 500). Fix: include isDeleted in index or hard-delete.Delete → re-create flow
OQ-6isEncrypted is never written; secrets (SMTP creds etc.) are stored plaintext. Encryption pipeline (planned)?Encrypted-badge UI, secret handling
OQ-7No history/audit of setting changes exposed (audit module exists; no settings events). History screen (planned)."Setting detail history" tab
OQ-8Which settings keys are canonical? No seed/registry anywhere. Coaching expansion will add groups/keys (IMPLEMENTATION_PLAN.md:773).Editor type heuristics, defaults

11. Glossary (this module)

TermMeaning
SettingOne settings doc: {tenantId, key, value(any JSON), group, label?, description?, isEncrypted, createdBy/updatedBy, isDeleted, deletedAt?, deletedBy?, version, createdAt, updatedAt} (setting.schema.ts:17-35, base.schema.ts:10-34)
GroupFixed enum bucket: academic, attendance, grading, notification, theme, general
UpsertfindOneAndUpdate(..., {upsert: true}) — create if missing, else update value (+group) (setting.repository.ts:29-40)
Embedded settingsThe settings object on the organizations doc (attendance/academic/theme) — separate surface
Envelope{success,message,data,meta?,timestamp,requestId} (response-envelope.interceptor.ts:48-59)