@murumets-ee/settings 0.16.3 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.d.mts +1 -1
- package/dist/admin.d.mts.map +1 -1
- package/dist/admin.mjs +1 -1
- package/dist/admin.mjs.map +1 -1
- package/package.json +4 -4
package/dist/admin.d.mts
CHANGED
|
@@ -85,7 +85,7 @@ interface SettingsDefinition<S extends Record<string, SettingConfig> = Record<st
|
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/admin/resources.d.ts
|
|
87
87
|
declare function settingsResources(definitions: SettingsDefinition[]): Array<{
|
|
88
|
-
|
|
88
|
+
name: string;
|
|
89
89
|
actions: readonly string[];
|
|
90
90
|
}>;
|
|
91
91
|
//#endregion
|
package/dist/admin.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.d.mts","names":[],"sources":["../src/types.ts","../src/admin/resources.ts","../src/admin/routes.ts"],"mappings":";;;;UAgBiB,iBAAA;EAMH;EAJZ,KAAA;EAOe;EALf,WAAA;;EAEA,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EACzC,IAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;EAEV;EAAA,SAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,iBAAA;EAC3C,IAAA;EACA,OAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,oBAAA,SAA6B,iBAAA;EAC5C,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,mBAAA,0DACP,iBAAA;EACR,IAAA;EACA,OAAA,EAAS,CAAA;EACT,OAAA,GAAU,CAAA;AAAA;AAAA,UAGK,iBAAA,sBAAuC,iBAAA;EACtD,IAAA;EACA,OAAA,GAAU,CAAA;EAZH;EAcP,MAAA,GAAS,OAAA,CAAQ,CAAA;EAXF;;;;;;;;;;;EAuBf,QAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,iBAAA;EAC1C,IAAA;EACA,OAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA,GACR,iBAAA,GACA,mBAAA,GACA,oBAAA,GACA,mBAAA,GACA,iBAAA,GACA,kBAAA;AAAA,KAgDQ,YAAA;AAAA,UAoCK,kBAAA,WACL,MAAA,SAAe,aAAA,IAAiB,MAAA,SAAe,aAAA;EArFvD;EAwFF,SAAA;EAxFoB;EA0FpB,KAAA,EAAO,YAAA;EA1Ce;EA4CtB,MAAA,EAAQ,CAAA;EA5Cc;EA8CtB,KAAA;EAVe;;;;;EAgBf,QAAA;EAf0C;;;;;EAqB1C,YAAA;AAAA;;;
|
|
1
|
+
{"version":3,"file":"admin.d.mts","names":[],"sources":["../src/types.ts","../src/admin/resources.ts","../src/admin/routes.ts"],"mappings":";;;;UAgBiB,iBAAA;EAMH;EAJZ,KAAA;EAOe;EALf,WAAA;;EAEA,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EACzC,IAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;EAEV;EAAA,SAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,iBAAA;EAC3C,IAAA;EACA,OAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,oBAAA,SAA6B,iBAAA;EAC5C,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,mBAAA,0DACP,iBAAA;EACR,IAAA;EACA,OAAA,EAAS,CAAA;EACT,OAAA,GAAU,CAAA;AAAA;AAAA,UAGK,iBAAA,sBAAuC,iBAAA;EACtD,IAAA;EACA,OAAA,GAAU,CAAA;EAZH;EAcP,MAAA,GAAS,OAAA,CAAQ,CAAA;EAXF;;;;;;;;;;;EAuBf,QAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,iBAAA;EAC1C,IAAA;EACA,OAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA,GACR,iBAAA,GACA,mBAAA,GACA,oBAAA,GACA,mBAAA,GACA,iBAAA,GACA,kBAAA;AAAA,KAgDQ,YAAA;AAAA,UAoCK,kBAAA,WACL,MAAA,SAAe,aAAA,IAAiB,MAAA,SAAe,aAAA;EArFvD;EAwFF,SAAA;EAxFoB;EA0FpB,KAAA,EAAO,YAAA;EA1Ce;EA4CtB,MAAA,EAAQ,CAAA;EA5Cc;EA8CtB,KAAA;EAVe;;;;;EAgBf,QAAA;EAf0C;;;;;EAqB1C,YAAA;AAAA;;;iBCrKc,iBAAA,CACd,WAAA,EAAa,kBAAA,KACZ,KAAA;EAAQ,IAAA;EAAc,OAAA;AAAA;;;;;;;;;;;;;ADczB;;;;;iBE6DgB,cAAA,CAAe,UAAA,EAAY,kBAAA,GAAqB,UAAA;AAAA,iBAChD,cAAA,CAAe,WAAA,EAAa,kBAAA,KAAuB,UAAA;AAAA,iBACnD,cAAA,CAAe,cAAA,QAAsB,kBAAA,KAAuB,UAAA"}
|
package/dist/admin.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{validateLocale as e}from"@murumets-ee/core";import{ZodError as t}from"zod";function n(e){return e.map(e=>({
|
|
1
|
+
import{validateLocale as e}from"@murumets-ee/core";import{ZodError as t}from"zod";function n(e){return e.map(e=>({name:`settings:${e.namespace}`,actions:[`view`,`update`]}))}function r(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":`application/json`}})}function i(e,t){return r({error:e},t)}function a(e){return{definition:e,clientPromise:null}}async function o(e,t){if(e.definition.scope===`user`){let{getApp:n}=await import(`@murumets-ee/core`),{createSettingsClient:r}=await import(`./client-factory-C44mk_oR.mjs`);return r(e.definition,{app:n(),scopeId:t})}return e.clientPromise||=(async()=>{let{getApp:t}=await import(`@murumets-ee/core`),{createSettingsClient:n}=await import(`./client-factory-C44mk_oR.mjs`);return n(e.definition,{app:t()})})(),e.clientPromise}function s(n){let s=typeof n==`function`?n:()=>Array.isArray(n)?n:[n],c=null,l=null,u=()=>{let e=s();if(c===null||e!==l){let t=new Map;for(let n of e)t.set(n.namespace,a(n));c=t,l=e}return c};async function d(t,n){let a=n.segments[0];if(!a)return i(`Missing settings namespace`,400);let s=u().get(a);if(!s)return i(`Unknown settings namespace: ${a}`,404);if(s.definition.scope!==`user`&&!n.checkPermission(`settings:${a}`,`view`))return n.audit?.({action:`settings.view.denied`,entityType:`settings`,userId:n.user.id,...n.user.name!==void 0&&{userName:n.user.name},metadata:{namespace:a,reason:`permission`}}),i(`Forbidden: cannot view settings:${a}`,403);let c=new URL(t.url),l;if(s.definition.scope===`user`&&(l=c.searchParams.get(`scopeId`)??n.user.id,l!==n.user.id))return n.audit?.({action:`settings.view.denied`,entityType:`settings`,userId:n.user.id,...n.user.name!==void 0&&{userName:n.user.name},metadata:{namespace:a,reason:`cross-user-scopeId`}}),i(`Forbidden: cannot read other users' preferences`,403);let d=await o(s,l),f=e(c.searchParams.get(`locale`));return r(await d.getAll(f?{locale:f}:void 0))}async function f(n,a){let s=a.segments[0];if(!s)return i(`Missing settings namespace`,400);let c=u().get(s);if(!c)return i(`Unknown settings namespace: ${s}`,404);if(c.definition.scope!==`user`&&!a.checkPermission(`settings:${s}`,`update`))return a.audit?.({action:`settings.update.denied`,entityType:`settings`,userId:a.user.id,...a.user.name!==void 0&&{userName:a.user.name},metadata:{namespace:s,reason:`permission`}}),i(`Forbidden: cannot update settings:${s}`,403);let{values:l,locale:d,scopeId:f}=await n.json(),p=e(d);if(!l||typeof l!=`object`||Array.isArray(l))return i(`Body must contain "values" object`,400);let m;if(c.definition.scope===`user`&&(m=f??a.user.id,m!==a.user.id))return a.audit?.({action:`settings.update.denied`,entityType:`settings`,userId:a.user.id,...a.user.name!==void 0&&{userName:a.user.name},metadata:{namespace:s,reason:`cross-user-scopeId`}}),i(`Forbidden: cannot update other users' preferences`,403);let h=await o(c,m);try{await h.setMany(l,p?{locale:p}:void 0)}catch(e){if(e instanceof t){let t={};for(let n of e.issues){let e=n.path.join(`.`)||`_`;t[e]=n.message}return a.audit?.({action:`settings.update.denied`,entityType:`settings`,userId:a.user.id,...a.user.name!==void 0&&{userName:a.user.name},metadata:{namespace:s,reason:`validation`,fields:Object.keys(t)}}),r({error:`Validation failed`,fieldErrors:t},400)}throw e}return a.audit?.({action:`settings.update`,entityType:`settings`,userId:a.user.id,...a.user.name!==void 0&&{userName:a.user.name},changes:{fields:l},metadata:{namespace:s,...p?{locale:p}:{},...m?{scopeId:m}:{}}}),r({success:!0})}return{prefix:`settings`,actions:[`view`,`update`],handlers:{GET:d,PATCH:f}}}export{n as settingsResources,s as settingsRoutes};
|
|
2
2
|
//# sourceMappingURL=admin.mjs.map
|
package/dist/admin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.mjs","names":[],"sources":["../src/admin/resources.ts","../src/admin/routes.ts"],"sourcesContent":["/**\n * Derive permission resource entries from settings definitions.\n *\n * Each definition produces one resource (`settings:<namespace>`) with\n * `['view', 'update']` actions. Spread the result into your\n * `pluginResources` array so the permission catalog includes them.\n *\n * @example\n * ```typescript\n * import { settingsResources } from '@murumets-ee/settings/admin'\n * import { siteSettings } from '@/settings/site'\n * import { ticketingSettings } from '@murumets-ee/ticketing'\n *\n * export const pluginResources = [\n * ...settingsResources([siteSettings, ticketingSettings]),\n * { resource: 'storage', actions: ['view', 'create', 'update', 'delete'] },\n * ]\n * ```\n */\n\nimport type { SettingsDefinition } from '../types.js'\n\nexport function settingsResources(\n definitions: SettingsDefinition[],\n): Array<{ resource: string; actions: readonly string[] }> {\n return definitions.map((def) => ({\n resource: `settings:${def.namespace}`,\n actions: ['view', 'update'] as const,\n }))\n}\n","/**\n * Settings admin routes for the centralized admin API handler.\n *\n * Provides get/update operations for typed settings, with per-namespace\n * permission checks. Accepts a single definition (backward-compat) or\n * an array of definitions for multi-namespace support.\n *\n * URL scheme:\n * - `GET /api/admin/settings/:namespace` — Get all settings (query: `?locale=`)\n * - `PATCH /api/admin/settings/:namespace` — Update settings (JSON body: `{ values, locale?, scopeId? }`)\n *\n * Each namespace is independently gated by `checkPermission('settings:<ns>', action)`.\n *\n * @example\n * ```typescript\n * import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'\n * import { settingsRoutes } from '@murumets-ee/settings/admin'\n * import { siteSettings } from '@/settings/site'\n * import { ticketingSettings, agentPreferences } from '@murumets-ee/ticketing'\n *\n * const handler = createAdminApiHandler({\n * authenticate: async (req) => { ... },\n * entities: [Article],\n * routes: [settingsRoutes([siteSettings, ticketingSettings, agentPreferences])],\n * })\n * ```\n */\n\nimport type { AdminRoute, AuditLogFn, AuthUser } from '@murumets-ee/core'\nimport { validateLocale } from '@murumets-ee/core'\nimport { ZodError } from 'zod'\nimport type { SettingsClient } from '../client.js'\nimport type { SettingConfig, SettingsDefinition } from '../types.js'\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction json(data: unknown, status = 200) {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\nfunction errorJson(message: string, status: number) {\n return json({ error: message }, status)\n}\n\n// ---------------------------------------------------------------------------\n// Per-namespace entry: definition + lazy client\n// ---------------------------------------------------------------------------\n\ninterface NamespaceEntry<S extends Record<string, SettingConfig> = Record<string, SettingConfig>> {\n definition: SettingsDefinition<S>\n clientPromise: Promise<SettingsClient<S>> | null\n}\n\nfunction createEntry<S extends Record<string, SettingConfig>>(\n definition: SettingsDefinition<S>,\n): NamespaceEntry<S> {\n return { definition, clientPromise: null }\n}\n\nasync function getClient<S extends Record<string, SettingConfig>>(\n entry: NamespaceEntry<S>,\n scopeId?: string,\n): Promise<SettingsClient<S>> {\n // User-scoped settings need a fresh client per scopeId — no caching\n if (entry.definition.scope === 'user') {\n const { getApp } = await import('@murumets-ee/core')\n const { createSettingsClient } = await import('../client-factory.js')\n return createSettingsClient(entry.definition, { app: getApp(), scopeId })\n }\n\n if (!entry.clientPromise) {\n entry.clientPromise = (async () => {\n const { getApp } = await import('@murumets-ee/core')\n const { createSettingsClient } = await import('../client-factory.js')\n return createSettingsClient(entry.definition, { app: getApp() })\n })()\n }\n return entry.clientPromise\n}\n\n// ---------------------------------------------------------------------------\n// Route factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create admin API routes for settings management.\n *\n * Accepts a single definition, an array, or a thunk returning the\n * current definitions list. The thunk form lets the settings plugin\n * aggregate contributions from other plugins at init time — its init\n * hook walks `app.plugins.all()`, pulls each plugin's `shared.settings`,\n * and pushes them into a mutable list, which the route handler reads\n * lazily on first request (after init has run).\n *\n * Each namespace gets its own permission resource (`settings:<namespace>`).\n * The merge engine in `@murumets-ee/core` auto-derives that resource and\n * the sidebar entry from `Plugin.shared.settings`, so plugin authors only\n * need to declare the namespace once.\n */\nexport function settingsRoutes(definition: SettingsDefinition): AdminRoute\nexport function settingsRoutes(definitions: SettingsDefinition[]): AdminRoute\nexport function settingsRoutes(getDefinitions: () => SettingsDefinition[]): AdminRoute\nexport function settingsRoutes(\n input: SettingsDefinition | SettingsDefinition[] | (() => SettingsDefinition[]),\n): AdminRoute {\n const getDefs: () => SettingsDefinition[] =\n typeof input === 'function' ? input : () => (Array.isArray(input) ? input : [input])\n\n // Lazy nsMap — built on first request so plugin-init contributions are\n // visible. Rebuilt automatically when the thunk returns a new array\n // reference (e.g. across tests with separate registries).\n let cachedNsMap: Map<string, NamespaceEntry> | null = null\n let lastDefs: SettingsDefinition[] | null = null\n const getNsMap = (): Map<string, NamespaceEntry> => {\n const defs = getDefs()\n if (cachedNsMap === null || defs !== lastDefs) {\n const next = new Map<string, NamespaceEntry>()\n for (const def of defs) {\n next.set(def.namespace, createEntry(def))\n }\n cachedNsMap = next\n lastDefs = defs\n }\n return cachedNsMap\n }\n\n // ---------- GET /api/admin/settings/:namespace ----------\n async function handleGet(\n req: Request,\n ctx: {\n segments: string[]\n user: AuthUser\n audit?: AuditLogFn\n checkPermission: (resource: string, action: string) => boolean\n },\n ): Promise<Response> {\n const namespace = ctx.segments[0]\n if (!namespace) return errorJson('Missing settings namespace', 400)\n\n const entry = getNsMap().get(namespace)\n if (!entry) return errorJson(`Unknown settings namespace: ${namespace}`, 404)\n\n // Per-namespace permission check\n if (!ctx.checkPermission(`settings:${namespace}`, 'view')) {\n ctx.audit?.({\n action: 'settings.view.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'permission' },\n })\n return errorJson(`Forbidden: cannot view settings:${namespace}`, 403)\n }\n\n // User-scoped: only allow reading own preferences\n const url = new URL(req.url)\n let scopeId: string | undefined\n if (entry.definition.scope === 'user') {\n scopeId = url.searchParams.get('scopeId') ?? ctx.user.id\n if (scopeId !== ctx.user.id) {\n ctx.audit?.({\n action: 'settings.view.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'cross-user-scopeId' },\n })\n return errorJson(\"Forbidden: cannot read other users' preferences\", 403)\n }\n }\n\n const client = await getClient(entry, scopeId)\n const locale = validateLocale(url.searchParams.get('locale'))\n const values = await client.getAll(locale ? { locale } : undefined)\n return json(values)\n }\n\n // ---------- PATCH /api/admin/settings/:namespace ----------\n async function handlePatch(\n req: Request,\n ctx: {\n segments: string[]\n user: AuthUser\n audit?: AuditLogFn\n checkPermission: (resource: string, action: string) => boolean\n },\n ): Promise<Response> {\n const namespace = ctx.segments[0]\n if (!namespace) return errorJson('Missing settings namespace', 400)\n\n const entry = getNsMap().get(namespace)\n if (!entry) return errorJson(`Unknown settings namespace: ${namespace}`, 404)\n\n // Per-namespace permission check\n if (!ctx.checkPermission(`settings:${namespace}`, 'update')) {\n ctx.audit?.({\n action: 'settings.update.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'permission' },\n })\n return errorJson(`Forbidden: cannot update settings:${namespace}`, 403)\n }\n\n const body = (await req.json()) as {\n values: Record<string, unknown>\n locale?: string\n scopeId?: string\n }\n const { values, locale: rawLocale, scopeId } = body\n const locale = validateLocale(rawLocale)\n\n if (!values || typeof values !== 'object' || Array.isArray(values)) {\n return errorJson('Body must contain \"values\" object', 400)\n }\n\n // User-scoped: enforce user isolation\n let resolvedScopeId: string | undefined\n if (entry.definition.scope === 'user') {\n resolvedScopeId = scopeId ?? ctx.user.id\n if (resolvedScopeId !== ctx.user.id) {\n ctx.audit?.({\n action: 'settings.update.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'cross-user-scopeId' },\n })\n return errorJson(\"Forbidden: cannot update other users' preferences\", 403)\n }\n }\n\n const client = await getClient(entry, resolvedScopeId)\n try {\n await client.setMany(\n values as Parameters<typeof client.setMany>[0],\n locale ? { locale } : undefined,\n )\n } catch (err) {\n if (err instanceof ZodError) {\n // Per-field validation failures: surface them with enough detail for\n // the form to highlight the offending field. Audited as a denied\n // update so spammy invalid payloads stay traceable.\n const fieldErrors: Record<string, string> = {}\n for (const issue of err.issues) {\n const path = issue.path.join('.') || '_'\n fieldErrors[path] = issue.message\n }\n ctx.audit?.({\n action: 'settings.update.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'validation', fields: Object.keys(fieldErrors) },\n })\n return json({ error: 'Validation failed', fieldErrors }, 400)\n }\n throw err\n }\n\n ctx.audit?.({\n action: 'settings.update',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n changes: { fields: values },\n metadata: {\n namespace,\n ...(locale ? { locale } : {}),\n ...(resolvedScopeId ? { scopeId: resolvedScopeId } : {}),\n },\n })\n\n return json({ success: true })\n }\n\n return {\n prefix: 'settings',\n // No `resource` — permission checks happen per-namespace inside handlers\n actions: ['view', 'update'],\n handlers: {\n GET: handleGet,\n PATCH: handlePatch,\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin.mjs","names":[],"sources":["../src/admin/resources.ts","../src/admin/routes.ts"],"sourcesContent":["/**\n * Derive permission resource entries from settings definitions.\n *\n * Each definition produces one resource (`settings:<namespace>`) with\n * `['view', 'update']` actions. Spread the result into your\n * `pluginResources` array so the permission catalog includes them.\n *\n * Shape: `{ name, actions }` — matches `PluginResource` so the result\n * flows directly into both `shared.pluginResources` (plugin\n * contributions) and `buildResourceCatalog(entities, pluginResources)`\n * (permission catalog) without a re-map step.\n *\n * @example\n * ```typescript\n * import { settingsResources } from '@murumets-ee/settings/admin'\n * import { siteSettings } from '@/settings/site'\n * import { ticketingSettings } from '@murumets-ee/ticketing'\n *\n * export const pluginResources = [\n * ...settingsResources([siteSettings, ticketingSettings]),\n * { name: 'storage', actions: ['view', 'create', 'update', 'delete'] },\n * ]\n * ```\n */\n\nimport type { SettingsDefinition } from '../types.js'\n\nexport function settingsResources(\n definitions: SettingsDefinition[],\n): Array<{ name: string; actions: readonly string[] }> {\n return definitions.map((def) => ({\n name: `settings:${def.namespace}`,\n actions: ['view', 'update'] as const,\n }))\n}\n","/**\n * Settings admin routes for the centralized admin API handler.\n *\n * Provides get/update operations for typed settings, with per-namespace\n * permission checks. Accepts a single definition (backward-compat) or\n * an array of definitions for multi-namespace support.\n *\n * URL scheme:\n * - `GET /api/admin/settings/:namespace` — Get all settings (query: `?locale=`)\n * - `PATCH /api/admin/settings/:namespace` — Update settings (JSON body: `{ values, locale?, scopeId? }`)\n *\n * Each namespace is independently gated by `checkPermission('settings:<ns>', action)`.\n *\n * @example\n * ```typescript\n * import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'\n * import { settingsRoutes } from '@murumets-ee/settings/admin'\n * import { siteSettings } from '@/settings/site'\n * import { ticketingSettings, agentPreferences } from '@murumets-ee/ticketing'\n *\n * const handler = createAdminApiHandler({\n * authenticate: async (req) => { ... },\n * entities: [Article],\n * routes: [settingsRoutes([siteSettings, ticketingSettings, agentPreferences])],\n * })\n * ```\n */\n\nimport type { AdminRoute, AuditLogFn, AuthUser } from '@murumets-ee/core'\nimport { validateLocale } from '@murumets-ee/core'\nimport { ZodError } from 'zod'\nimport type { SettingsClient } from '../client.js'\nimport type { SettingConfig, SettingsDefinition } from '../types.js'\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction json(data: unknown, status = 200) {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\nfunction errorJson(message: string, status: number) {\n return json({ error: message }, status)\n}\n\n// ---------------------------------------------------------------------------\n// Per-namespace entry: definition + lazy client\n// ---------------------------------------------------------------------------\n\ninterface NamespaceEntry<S extends Record<string, SettingConfig> = Record<string, SettingConfig>> {\n definition: SettingsDefinition<S>\n clientPromise: Promise<SettingsClient<S>> | null\n}\n\nfunction createEntry<S extends Record<string, SettingConfig>>(\n definition: SettingsDefinition<S>,\n): NamespaceEntry<S> {\n return { definition, clientPromise: null }\n}\n\nasync function getClient<S extends Record<string, SettingConfig>>(\n entry: NamespaceEntry<S>,\n scopeId?: string,\n): Promise<SettingsClient<S>> {\n // User-scoped settings need a fresh client per scopeId — no caching\n if (entry.definition.scope === 'user') {\n const { getApp } = await import('@murumets-ee/core')\n const { createSettingsClient } = await import('../client-factory.js')\n return createSettingsClient(entry.definition, { app: getApp(), scopeId })\n }\n\n if (!entry.clientPromise) {\n entry.clientPromise = (async () => {\n const { getApp } = await import('@murumets-ee/core')\n const { createSettingsClient } = await import('../client-factory.js')\n return createSettingsClient(entry.definition, { app: getApp() })\n })()\n }\n return entry.clientPromise\n}\n\n// ---------------------------------------------------------------------------\n// Route factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create admin API routes for settings management.\n *\n * Accepts a single definition, an array, or a thunk returning the\n * current definitions list. The thunk form lets the settings plugin\n * aggregate contributions from other plugins at init time — its init\n * hook walks `app.plugins.all()`, pulls each plugin's `shared.settings`,\n * and pushes them into a mutable list, which the route handler reads\n * lazily on first request (after init has run).\n *\n * Each namespace gets its own permission resource (`settings:<namespace>`).\n * The merge engine in `@murumets-ee/core` auto-derives that resource and\n * the sidebar entry from `Plugin.shared.settings`, so plugin authors only\n * need to declare the namespace once.\n */\nexport function settingsRoutes(definition: SettingsDefinition): AdminRoute\nexport function settingsRoutes(definitions: SettingsDefinition[]): AdminRoute\nexport function settingsRoutes(getDefinitions: () => SettingsDefinition[]): AdminRoute\nexport function settingsRoutes(\n input: SettingsDefinition | SettingsDefinition[] | (() => SettingsDefinition[]),\n): AdminRoute {\n const getDefs: () => SettingsDefinition[] =\n typeof input === 'function' ? input : () => (Array.isArray(input) ? input : [input])\n\n // Lazy nsMap — built on first request so plugin-init contributions are\n // visible. Rebuilt automatically when the thunk returns a new array\n // reference (e.g. across tests with separate registries).\n let cachedNsMap: Map<string, NamespaceEntry> | null = null\n let lastDefs: SettingsDefinition[] | null = null\n const getNsMap = (): Map<string, NamespaceEntry> => {\n const defs = getDefs()\n if (cachedNsMap === null || defs !== lastDefs) {\n const next = new Map<string, NamespaceEntry>()\n for (const def of defs) {\n next.set(def.namespace, createEntry(def))\n }\n cachedNsMap = next\n lastDefs = defs\n }\n return cachedNsMap\n }\n\n // ---------- GET /api/admin/settings/:namespace ----------\n async function handleGet(\n req: Request,\n ctx: {\n segments: string[]\n user: AuthUser\n audit?: AuditLogFn\n checkPermission: (resource: string, action: string) => boolean\n },\n ): Promise<Response> {\n const namespace = ctx.segments[0]\n if (!namespace) return errorJson('Missing settings namespace', 400)\n\n const entry = getNsMap().get(namespace)\n if (!entry) return errorJson(`Unknown settings namespace: ${namespace}`, 404)\n\n // Per-namespace permission check. User-scoped settings are exempt —\n // the scope-id check below is the real authorization boundary (you\n // can only read your own row), mirroring the PATCH handler.\n if (\n entry.definition.scope !== 'user' &&\n !ctx.checkPermission(`settings:${namespace}`, 'view')\n ) {\n ctx.audit?.({\n action: 'settings.view.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'permission' },\n })\n return errorJson(`Forbidden: cannot view settings:${namespace}`, 403)\n }\n\n // User-scoped: only allow reading own preferences\n const url = new URL(req.url)\n let scopeId: string | undefined\n if (entry.definition.scope === 'user') {\n scopeId = url.searchParams.get('scopeId') ?? ctx.user.id\n if (scopeId !== ctx.user.id) {\n ctx.audit?.({\n action: 'settings.view.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'cross-user-scopeId' },\n })\n return errorJson(\"Forbidden: cannot read other users' preferences\", 403)\n }\n }\n\n const client = await getClient(entry, scopeId)\n const locale = validateLocale(url.searchParams.get('locale'))\n const values = await client.getAll(locale ? { locale } : undefined)\n return json(values)\n }\n\n // ---------- PATCH /api/admin/settings/:namespace ----------\n async function handlePatch(\n req: Request,\n ctx: {\n segments: string[]\n user: AuthUser\n audit?: AuditLogFn\n checkPermission: (resource: string, action: string) => boolean\n },\n ): Promise<Response> {\n const namespace = ctx.segments[0]\n if (!namespace) return errorJson('Missing settings namespace', 400)\n\n const entry = getNsMap().get(namespace)\n if (!entry) return errorJson(`Unknown settings namespace: ${namespace}`, 404)\n\n // Per-namespace permission check. User-scoped settings (per-user\n // preferences, e.g. ticketing agent signature, notification toggles)\n // are exempt — the scope-id check below is the real authorization\n // boundary for those (you can only mutate your own row). Requiring a\n // separate `settings:<ns>:update` grant would force admins to hand\n // out blanket permissions just so users can edit their own prefs,\n // which is the opposite of the user-scope guarantee.\n if (\n entry.definition.scope !== 'user' &&\n !ctx.checkPermission(`settings:${namespace}`, 'update')\n ) {\n ctx.audit?.({\n action: 'settings.update.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'permission' },\n })\n return errorJson(`Forbidden: cannot update settings:${namespace}`, 403)\n }\n\n const body = (await req.json()) as {\n values: Record<string, unknown>\n locale?: string\n scopeId?: string\n }\n const { values, locale: rawLocale, scopeId } = body\n const locale = validateLocale(rawLocale)\n\n if (!values || typeof values !== 'object' || Array.isArray(values)) {\n return errorJson('Body must contain \"values\" object', 400)\n }\n\n // User-scoped: enforce user isolation\n let resolvedScopeId: string | undefined\n if (entry.definition.scope === 'user') {\n resolvedScopeId = scopeId ?? ctx.user.id\n if (resolvedScopeId !== ctx.user.id) {\n ctx.audit?.({\n action: 'settings.update.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'cross-user-scopeId' },\n })\n return errorJson(\"Forbidden: cannot update other users' preferences\", 403)\n }\n }\n\n const client = await getClient(entry, resolvedScopeId)\n try {\n await client.setMany(\n values as Parameters<typeof client.setMany>[0],\n locale ? { locale } : undefined,\n )\n } catch (err) {\n if (err instanceof ZodError) {\n // Per-field validation failures: surface them with enough detail for\n // the form to highlight the offending field. Audited as a denied\n // update so spammy invalid payloads stay traceable.\n const fieldErrors: Record<string, string> = {}\n for (const issue of err.issues) {\n const path = issue.path.join('.') || '_'\n fieldErrors[path] = issue.message\n }\n ctx.audit?.({\n action: 'settings.update.denied',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n metadata: { namespace, reason: 'validation', fields: Object.keys(fieldErrors) },\n })\n return json({ error: 'Validation failed', fieldErrors }, 400)\n }\n throw err\n }\n\n ctx.audit?.({\n action: 'settings.update',\n entityType: 'settings',\n userId: ctx.user.id,\n ...(ctx.user.name !== undefined && { userName: ctx.user.name }),\n changes: { fields: values },\n metadata: {\n namespace,\n ...(locale ? { locale } : {}),\n ...(resolvedScopeId ? { scopeId: resolvedScopeId } : {}),\n },\n })\n\n return json({ success: true })\n }\n\n return {\n prefix: 'settings',\n // No `resource` — permission checks happen per-namespace inside handlers\n actions: ['view', 'update'],\n handlers: {\n GET: handleGet,\n PATCH: handlePatch,\n },\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@murumets-ee/settings",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.4",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"drizzle-orm": "^0.45.2",
|
|
41
41
|
"zod": "^3.24.1",
|
|
42
42
|
"server-only": "^0.0.1",
|
|
43
|
-
"@murumets-ee/core": "0.16.
|
|
44
|
-
"@murumets-ee/
|
|
45
|
-
"@murumets-ee/
|
|
43
|
+
"@murumets-ee/core": "0.16.4",
|
|
44
|
+
"@murumets-ee/logging": "0.16.4",
|
|
45
|
+
"@murumets-ee/db": "0.16.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^20.19.40",
|