Settings Module
The Settings module is the per-user key/value preference store. It exposes two parallel access patterns over a single backing store: a whole-object view (fetch/replace the entire UserSettings document) and a per-key view (atomic individual reads/writes, batch sets, prefix queries). The web client uses the whole-object view for the Settings page and the per-key view for incremental UI state (active theme, sidebar collapsed, etc.).
Class Diagram
Request Flow
- HTTP Request → Controller receives whole-settings or single-key request.
- Validation →
validator.goenforces key naming rules and value-type constraints before the Service is invoked. - Cache Check → Service consults Redis; misses fall through to the Repository.
- Persistence → Repository writes the settings row; Service invalidates the cache key on write.
- Response → Updated settings (or the single value) are returned.
Key Components
Controller
Two parallel surfaces:
- Whole-object (
GetSettings/UpdateSettings/ResetSettings) — operates on the entireUserSettingsdocument. - Per-key (
Get/Set/Delete+GetAll/SetBatch/Reset) — fine-grained access for clients that only need a slice.
Service
GetByPrefixlets feature-flag-style consumers query a namespaced subset (e.g.theme.*).- All writes invalidate the cached
UserSettingsblob to keep the two access surfaces consistent.
Repository
Stores settings as a JSON column under a single user_settings row per user — single-row updates avoid N writes for batch sets.
Validator
Centralizes key/value rules so the Service stays focused on persistence and caching.
Special Behaviors
Single-row JSON storage
All a user’s settings live in one row in user_settings with the values stored as a JSON column. That single-row choice means:
- A whole-object update is one SQL
UPDATEregardless of payload size. - A batch-of-N set is also one
UPDATE(server-side merge). - The cached form is a single Redis key; invalidations are simple.
The trade-off: there’s no per-key Postgres-level index. Prefix queries are filtered in the Service after the row is loaded — fine for the < 100-key universe each user typically has, but it would need rethinking at much larger scale.
Validator runs at the controller boundary
validator.go is invoked in the Controller before the Service is touched. That keeps key naming rules (allowed characters, max length, namespace prefixes) and value-type constraints (e.g. boolean keys can’t accept arbitrary strings) one layer away from the Service’s persistence + caching focus.
Cache invalidation on every write
Both surfaces invalidate the cached UserSettings JSON on write. That means a per-key Set and a whole-object UpdateSettings are observationally equivalent — the next read sees the same merged object regardless of which write surface was used. No drift between the two views.
SDK Surface
SettingsService— both surfaces:getSettings/updateSettings/resetSettings(whole-object) andget/getAll/set/setBatch/delete/reset(per-key).UserSettings— the structured shape (general / privacy / notifications / network sections).actions/settings— undo-aware actions for incremental UI state changes.
Cross-Module Integration
| Module | Relationship |
|---|---|
| Users | One row per user; deletes cascade. |
| Themes | Active theme id stored under theme.activeId. |
API Reference
See the interactive API reference /settings tag for live examples.