01 — Product Overview (Settings Module)
- 1. Purpose
- 2. Business goals
- 3. User goals
- 4. Stakeholders
- 5. Why this exists
- 6. Dependencies
- 7. Success metrics
- 8. Edge cases (derived)
- 9. Assumptions (module)
- 10. Open questions (module-level; global ledger in 00-shared/12)
- 11. Glossary (this 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.mdand03-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.
| Responsibility | Source |
|---|---|
| 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 key | settings.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/upsert | base.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
| Goal | Measure |
|---|---|
| Any org-level knob in one place | All settings CRUD via 5 endpoints; groups cover academic, attendance, grading, notification, theme, general (setting.schema.ts:7-14) |
| Zero-conflict multi-tenant isolation | Unique index {tenantId, key} (setting.schema.ts:38); tenantId injected by repository, never from body (base.repository.ts:33-35) |
| No hot-writes on org document | Standalone collection + upsert semantics (Organizations.md:60) |
| Config written by machines and admins | Idempotent PUT upsert; bulk endpoint for batch imports (settings.service.ts:24-34) |
| Delete ≠ data loss | Soft 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/:keyorGET /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
| Dependency | Role | Source |
|---|---|---|
| Auth (JWT) | every settings endpoint requires a bearer token | settings.controller.ts:12,19; global guards app.module.ts:129-131 |
| RBAC | settings.read/update/delete permission constants exist | permissions.constants.ts:75-77 — not enforced on this controller (OQ-2) |
| TenantContextService | tenant scoping of all queries | setting.repository.ts:16,34; base.repository.ts:20-30 |
| Organizations module | separate embedded settings surface (attendance/academic/theme) | organization.schema.ts:96-112; organizations.controller.ts:56-72 |
| Feature Flags module | sibling boolean config surface | feature-flags.controller.ts:23-58 |
| BaseRepository | soft-delete + version + audit fields | base.schema.ts:8-35, base.repository.ts:32-74 |
| Mongo collection | settings (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/:keymissing key → 404RESOURCE_NOT_FOUND"Setting "key" not found." (settings.service.ts:20).PUT /settingsmissingkeyorvalue→ class-validator has no decorators on them (update-setting.dto.ts:5-10) → Mongooserequiredvalidation 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/descriptionare 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).isEncryptedexists 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 /settingslists 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:
findAllreturns a bare array (settings.service.ts:10-12) and the envelope interceptor only addsmetafor{data, meta}payloads (response-envelope.interceptor.ts:25-32,55-59) — the settings list is a single fetch withmetaomitted. - Group set is fixed to the enum:
academic, attendance, grading, notification, theme, general(setting.schema.ts:7-14). ACOACHINGgroup 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)
| # | Item | Impact |
|---|---|---|
| OQ-1 | Which module(s) consume settings at runtime? No consumer exists in src/. | Copy/help text, "who reads this" metadata |
| OQ-2 | settings.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-3 | PUT /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-4 | Bulk 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-5 | Soft 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-6 | isEncrypted is never written; secrets (SMTP creds etc.) are stored plaintext. Encryption pipeline (planned)? | Encrypted-badge UI, secret handling |
| OQ-7 | No history/audit of setting changes exposed (audit module exists; no settings events). History screen (planned). | "Setting detail history" tab |
| OQ-8 | Which 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)
| Term | Meaning |
|---|---|
| Setting | One 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) |
| Group | Fixed enum bucket: academic, attendance, grading, notification, theme, general |
| Upsert | findOneAndUpdate(..., {upsert: true}) — create if missing, else update value (+group) (setting.repository.ts:29-40) |
| Embedded settings | The settings object on the organizations doc (attendance/academic/theme) — separate surface |
| Envelope | {success,message,data,meta?,timestamp,requestId} (response-envelope.interceptor.ts:48-59) |