Files
HealthLog/prisma/migrations/0168_operator_module_availability/migration.sql
T
Marc-André Bombeck 5d55347dd0 feat(modules): add operator-level server-wide module availability
Second layer of the module enable/disable model: an operator can turn a
module off for the whole server, and an operator-disabled module is off
for every account regardless of personal preference — mirroring the
coach master flag above the per-user opt-out.

Storage is a DISABLED allowlist on AppSettings.moduleAvailabilityJson
(absent/empty/true = available, explicit false = disabled), resolved by
getOperatorModuleAvailability(). The gate now resolves every toggleable
module as operatorAvailable(key) AND userEnabled(key); cycle and coach
keep their existing delegation but are still AND-ed with operator
availability. Core domains are never disableable.

GET/PATCH /api/admin/settings/module-availability is cookie-only
(requireAdmin); a strict per-module-key schema rejects core domains and
unknown keys at the boundary.
2026-06-15 22:44:35 +02:00

40 lines
1.9 KiB
SQL

-- v1.18.0 — operator-level server-wide module availability.
--
-- A single nullable JSONB blob on `app_settings` carrying a DISABLED
-- allowlist for the toggleable ("secondary") modules — the second layer of
-- the two-layer module model. This sits ABOVE the per-user
-- `users.module_preferences_json`: an operator-disabled module is off for
-- EVERY account regardless of that account's personal preference, exactly
-- like the coach master flag (`assistant_coach_enabled`) sits above
-- `users.disable_coach`.
--
-- The contract mirrors the per-user column (default-on):
--
-- * NULL column → every module available
-- * empty object `{}` → every module available
-- * key absent → that module available
-- * key present & `false` → that module DISABLED server-wide
-- * key present & `true` → available (redundant-but-allowed)
--
-- No backfill — every existing instance keeps every module available. The
-- application resolver (`src/lib/modules/gate.ts`) tolerates a missing
-- column / null row and reads all-available, so a partial deploy where the
-- schema lands ahead of (or behind) the app code keeps working.
--
-- CORE domains (weight, blood pressure, pulse, medications) are never
-- written here and the admin write endpoint refuses them, so the
-- measurement engine + meds can never be turned off server-wide either.
--
-- No additional index — the single `app_settings` singleton row is read by
-- primary key, matching the convention of every other JSON column on the
-- table.
--
-- Idempotent guard (`IF NOT EXISTS`) so reruns are safe on prod.
--
-- Reversibility: down migration is
-- ALTER TABLE "app_settings" DROP COLUMN IF EXISTS "module_availability_json";
-- A roll-back loses operator-level opt-outs (every module falls back to
-- available), which is the safe default — no data is destroyed.
ALTER TABLE "app_settings"
ADD COLUMN IF NOT EXISTS "module_availability_json" JSONB;