fix: complete managed record settings

- Expose canonical defaults and reject invalid settings families\n- Localize Guardian controls and refresh dependent caches
This commit is contained in:
Marc-André Bombeck committed 2026-08-06 03:40:13 +02:00
1 parent e788675b82
commit 247ac8494c
16 files changed
+759 -91

No files matched your search

+22 -5
View File
@@ -4,8 +4,12 @@ import {
ACCEPTED_INSIGHTS_TILE_IDS,
INSIGHTS_SECTION_IDS,
} from "@/lib/insights-layout";
import { MODULE_REGISTRY } from "@/lib/modules/registry";
import { coachPrefsSchema } from "@/lib/validations/coach-prefs";
import { modulePrefsPatchSchema } from "@/lib/validations/modules";
import {
modulePrefsPatchSchema,
WRITABLE_MODULE_KEYS,
} from "@/lib/validations/modules";
import { thresholdsUpdateSchema } from "@/lib/validations/thresholds";
/**
@@ -54,7 +58,7 @@ const profilePatchSchema = z
displayName: z.string().trim().min(1).max(100).nullable().optional(),
heightCm: z.number().finite().min(30).max(300).nullable().optional(),
dateOfBirth: z.string().date().nullable().optional(),
gender: z.string().trim().min(1).max(64).nullable().optional(),
gender: z.enum(["MALE", "FEMALE", "OTHER"]).nullable().optional(),
locale: z.enum(["de", "en", "es", "fr", "it", "pl"]).nullable().optional(),
timezone: timezoneSchema.optional(),
unitPreference: z.enum(["metric", "imperial"]).optional(),
@@ -161,7 +165,7 @@ export type ManagedRecordSettingsPatch = {
export function isManagedRecordSettingsFamily(
value: string,
): value is ManagedRecordSettingsFamily {
return value in MANAGED_RECORD_SETTINGS_PATCH_SCHEMAS;
return Object.hasOwn(MANAGED_RECORD_SETTINGS_PATCH_SCHEMAS, value);
}
/** Parse one named DTO family. Strict schemas make an actor field a 422. */
@@ -179,15 +183,28 @@ export function safeParseManagedRecordSettingsPatch<
return MANAGED_RECORD_SETTINGS_PATCH_SCHEMAS[family].safeParse(value);
}
/**
* The same direct module inventory and default posture as `/api/auth/me/modules`.
* Every entry is present for a fresh record so a Guardian can administer the
* whole supported surface instead of seeing only previously persisted keys.
*/
export const MANAGED_RECORD_SETTINGS_MODULE_DEFAULTS = Object.freeze(
Object.fromEntries(
WRITABLE_MODULE_KEYS.map((key) => [key, !MODULE_REGISTRY[key].optIn]),
),
) as Readonly<Record<(typeof WRITABLE_MODULE_KEYS)[number], boolean>>;
/** Preserve only directly-owned module keys from a legacy persisted blob. */
export function managedModulePreferencesFrom(
raw: unknown,
): ManagedRecordSettingsPatch["modules"]["modulePreferences"] {
const allowed: Record<string, boolean> = {
...MANAGED_RECORD_SETTINGS_MODULE_DEFAULTS,
};
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
return {};
return allowed;
}
const allowed: Record<string, boolean> = {};
for (const [key, value] of Object.entries(raw)) {
const parsed = modulePrefsPatchSchema.safeParse({ [key]: value });
if (parsed.success && value !== undefined) {
+1
View File
@@ -16,6 +16,7 @@ export {
isManagedRecordSettingsFamily,
managedModulePreferencesFrom,
MANAGED_RECORD_SETTINGS_FIELD_ALLOWLIST,
MANAGED_RECORD_SETTINGS_MODULE_DEFAULTS,
parseManagedRecordSettingsPatch,
safeParseManagedRecordSettingsPatch,
type ManagedRecordSettingsFamily,
+1 -1
View File
@@ -38,7 +38,7 @@ import {
* opt into it gets a clean 422 rather than persisting an inert `true` the
* gate would ignore anyway.
*/
const WRITABLE_MODULE_KEYS = MODULE_KEYS.filter(
export const WRITABLE_MODULE_KEYS = MODULE_KEYS.filter(
(k) => moduleDelegatesTo(k) === undefined && !isCodeDisabledModule(k),
);