Skip to content

feat: per-user JSON UI settings store#151

Open
mauripunzueta wants to merge 1 commit into
mainfrom
feat/user-ui-settings
Open

feat: per-user JSON UI settings store#151
mauripunzueta wants to merge 1 commit into
mainfrom
feat/user-ui-settings

Conversation

@mauripunzueta

Copy link
Copy Markdown
Contributor

Summary

Adds a simple, extensible per-user settings store for the upcoming web UI. Settings like dark mode are just the first of many — the stored value is an opaque JSON object, so new keys need zero schema or code changes (the frontend owns the document shape). Example settings: theme/dark mode, recent FHIR queries by resource type, default tenant, active FHIR version.

The design is hybrid: a server-side store is the source of truth (settings roam across devices), intended to be fronted by a client-side localStorage cache for instant first paint.

Design decisions

  • Dedicated user_settings table, kept separate from the FHIR resources table so private UI prefs never leak into CapabilityStatement, _history, _search, or $export. Schema migration v10→v11 for both SQLite and PostgreSQL.
  • SettingsStore trait (get/put/patch) in helios-persistence core + an RFC 7386 JSON merge-patch helper. Implemented for SQLite and PostgreSQL with transactional read-modify-write and a monotonic version for optimistic locking (surfaced as a weak ETag).
  • Keyed by user only (issuer|subject), tenant-independent — a setting like "default tenant" is inherently cross-tenant. Falls back to a fixed local|default key when auth is disabled.
  • REST endpoints GET/PUT/PATCH /_user/settings. The leading-underscore path keeps them authenticated yet exempt from FHIR scope checks and invisible to FHIR machinery. PUT replaces; PATCH merge-patches (great for toggling one key); both honor If-Match, GET honors If-None-Match (304). GET returns {} by default so the UI always gets a usable document.
  • Wired into the SQLite and PostgreSQL standalone backends; other backends report the feature as unavailable (501).

API

Method Path Behavior
GET /_user/settings Fetch document (defaults to {}), ETag: W/"{version}"
PUT /_user/settings Replace whole document (JSON object)
PATCH /_user/settings RFC 7386 merge-patch a subset of keys

Tests

  • Persistence unit tests: merge-patch semantics + SQLite store (round-trip, version increment, patch merge/delete, optimistic lock).
  • PostgreSQL integration tests (testcontainers).
  • REST UserKey extractor unit tests.
  • End-to-end HTTP suite (8 tests): default {}, round-trip, single-key merge, null-delete, stale If-Match → 412, matching If-Match, non-object body → 400, If-None-Match → 304.

All affected crates pass cargo fmt, clippy -D warnings (CI flags), and tests.

Also

Refreshes the HTS README to reflect completed PostgreSQL backend parity (the only change there).

Notes for review

  • defaultTenant is keyed per-user globally (deliberate — it's cross-tenant). Future per-tenant settings can nest as perTenant: {...} without changing the table key.
  • The frontend localStorage cache layer is out of scope here (UI team owns it).

Add a simple, extensible per-user settings store for the upcoming web UI
(theme/dark mode, default tenant, active FHIR version, recent queries, …).
The value is an opaque JSON object, so new settings keys need no schema or
code changes — the frontend owns the document shape.

Design:
- Dedicated `user_settings` table (one opaque JSON document per user), kept
  separate from the FHIR `resources` table so UI preferences never leak into
  CapabilityStatement, _history, _search, or $export. Schema migration v10→v11
  for both SQLite and PostgreSQL backends.
- New `SettingsStore` trait (get/put/patch) in helios-persistence core, plus an
  RFC 7386 JSON merge-patch helper. Implemented for SQLite and PostgreSQL with
  transactional read-modify-write and a monotonic `version` for optimistic
  locking (surfaced as a weak ETag).
- Keyed by user only (issuer|subject), tenant-independent; falls back to a fixed
  `local|default` key when auth is disabled.
- REST endpoints GET/PUT/PATCH /_user/settings. The leading-underscore path keeps
  them authenticated yet exempt from FHIR scope checks and invisible to FHIR
  machinery. PUT replaces the document; PATCH merge-patches; both honor If-Match,
  GET honors If-None-Match (304).
- Wired into the SQLite and PostgreSQL standalone backends in the hfs binary;
  other backends report the feature as unavailable (501).

Tests: persistence unit tests (merge-patch + SQLite store), PostgreSQL
integration tests, REST extractor tests, and an end-to-end HTTP suite covering
defaults, round-trip, merge/delete, optimistic locking, validation, and 304.

Also refresh the HTS README to reflect completed PostgreSQL backend parity.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant