08 — Form Specifications (Settings Module)
- 1. Upsert Form —
PUT /settings(update-setting.dto.ts) - 2. Value-type-specific editing (core form behavior)
- 3. New Setting Form (FAB sheet)
- 4. Batch Save —
PUT /settings/bulk - 5. Delete — no form
- Form-level rules (all)
- Client-side error priority
Every field of every Settings form/editor, field-by-field. Validation mirrors the class-validator decorators exactly from
src/modules/settings/dto/update-setting.dto.ts; where the DTO has no decorator (key/value), the server accepts anything and a missing required field surfaces as a Mongoose validation error → 500 (OQ-3). Client validates inline first; the server remains the authority forgroupenum.
1. Upsert Form — PUT /settings (update-setting.dto.ts)
| # | Field | Label | Type / Editor | Required | Validation (server) | Client UX |
|---|---|---|---|---|---|---|
| 1 | key | Key | text, mono | yes (TS !) | no decorators (update-setting.dto.ts:6-7) — missing/undefined → Mongoose required error → 500 (OQ-3) | trim; non-empty; pattern hint ^[a-z0-9._-]+$ (convention, not enforced); error: "Enter a key" |
| 2 | value | Value | typed editor by runtime type (string / number / boolean / JSON) | yes (TS !) | no decorators (update-setting.dto.ts:9-10) — any JSON accepted; stored raw (setting.schema.ts:21-22) | type-specific (see §2); never coerced |
| 3 | group | Group | AppDropdown (enum) | no | @IsEnum(SettingGroup) optional (update-setting.dto.ts:12-15) → 400 VALIDATION_ERROR with details | default GENERAL (setting.schema.ts:24-25); dropdown restricts to 6 values |
| 4 | label | Label | text | no | @IsString() optional (update-setting.dto.ts:17-20) | hidden — never persisted (settings.service.ts:25 drops it) |
| 5 | description | Description | text | no | @IsString() optional (update-setting.dto.ts:22-25) | hidden — never persisted |
Server 400 details mapping: invalid group → details: [{message: "group must be one of the following values: academic, attendance, grading, notification, theme, general"}] (http-exception.filter.ts:103-108).
Submit behavior: save → PUT /settings → 200 returns the saved doc
(setting.repository.ts:35-39, new: true); form reconciles to server truth. Retry-safe
(idempotent upsert) — a network retry never duplicates.
2. Value-type-specific editing (core form behavior)
| Type | Editor | Formatting / formatters | Local validation | Server |
|---|---|---|---|---|
string | single-line AppTextField | none; mono for key-like values | non-empty (unless the key was created with an empty string — allowed) | stored as-is |
number | numeric AppTextField | FilteringTextInputFormatter.allow(RegExp(r'[0-9.\-]')) | double.tryParse != null | stored as JSON number (value: unknown → $set {value} raw, setting.repository.ts:37) |
boolean | AppSwitch (SwitchListTile) | — | — | stored as JSON true/false; instant save on toggle |
object / array / null | AppJsonEditor (mono, multi-line) | pretty-print on save (JsonEncoder.withIndent(' ')) | jsonDecode must succeed | stored as raw JSON object |
| unknown | JSON editor fallback | same | same | — |
Rule: no type coercion on save — a numeric-looking string stays a string; the client never guesses. Type chips in the list re-derive from the last server response.
3. New Setting Form (FAB sheet)
Fields: key (1), group (3), value as JSON editor (type unknown at creation).
On success → row appears in its group; group move after creation = edit the group dropdown
and save again (upsert persists group when present, setting.repository.ts:37).
4. Batch Save — PUT /settings/bulk
| Aspect | Contract |
|---|---|
| Request | JSON array of UpdateSettingDto (settings.controller.ts:43-47) |
| Server | sequential upsert loop, all results returned (settings.service.ts:28-34) — not transactional (OQ-4) |
| Client | only dirty keys serialized; each item = the full {key, value, group} (never partial {value} — an omitted group on an existing row is fine, but sending the known group is the stable form) |
| Failure | "Saved N of M" + per-row retry; re-send the full dirty set (idempotent) |
| Duplicate keys in one batch | last item wins (loop order) — client dedupes by key before sending |
5. Delete — no form
DELETE /settings/:key (settings.controller.ts:49-53) — confirm dialog only; no body.
404 → treat as removed. Soft-delete consequence: re-creating the key fails with E11000 → 500
(OQ-5); dialog copy warns.
Form-level rules (all)
- Double-submit: Save disabled while pending (00-shared/08 §6).
- Optimistic: explicit-save editors are server-confirm; boolean toggle is the only optimistic write (rollback on error — 00-shared/06 §3.5).
- Undo: none for delete (soft-delete not reversible through the API — OQ-5); value edits undo = re-edit (last-write-wins).
- Abandonment: dirty explicit editors prompt "Discard changes?"; batch mode Cancel restores last-server values.
- Keyboard:
Entersaves single-line editors,Ctrl/Cmd+Entersaves JSON editor,Tabfield order key → group → value;Esccancels. - Error copy: business 4xx
messageallowed; codes drive the rest (00-shared/07 §11).
Client-side error priority
- 400 VALIDATION → field (group enum; JSON parse is client-side pre-submit).
- 401 UNAUTHENTICATED → silent refresh; fail → session expiry.
- 404 RESOURCE_NOT_FOUND (detail/delete) → treat as removed.
- 500 INTERNAL_SERVER_ERROR → generic + requestId; form kept (retry safe).
- 429 RATE_LIMITED → countdown (global
apitier, 00-shared/07 §4).