diff --git a/packages/plugins/plugin-auth/src/auth-plugin.ts b/packages/plugins/plugin-auth/src/auth-plugin.ts index 17e8da5b6..0ccf07fc2 100644 --- a/packages/plugins/plugin-auth/src/auth-plugin.ts +++ b/packages/plugins/plugin-auth/src/auth-plugin.ts @@ -7,7 +7,7 @@ import { SysUser, SysSession, SysAccount, SysVerification, SysOrganization, SysMember, SysInvitation, SysTeam, SysTeamMember, - SysApiKey, SysTwoFactor, + SysApiKey, SysTwoFactor, SysUserPreference, } from './objects/index.js'; /** @@ -107,7 +107,7 @@ export class AuthPlugin implements Plugin { SysUser, SysSession, SysAccount, SysVerification, SysOrganization, SysMember, SysInvitation, SysTeam, SysTeamMember, - SysApiKey, SysTwoFactor, + SysApiKey, SysTwoFactor, SysUserPreference, ], }); diff --git a/packages/plugins/plugin-auth/src/objects/index.ts b/packages/plugins/plugin-auth/src/objects/index.ts index b84cb452c..8575bf113 100644 --- a/packages/plugins/plugin-auth/src/objects/index.ts +++ b/packages/plugins/plugin-auth/src/objects/index.ts @@ -27,6 +27,7 @@ export { SysTeamMember } from './sys-team-member.object.js'; // ── Additional Auth Objects ──────────────────────────────────────────────── export { SysApiKey } from './sys-api-key.object.js'; export { SysTwoFactor } from './sys-two-factor.object.js'; +export { SysUserPreference } from './sys-user-preference.object.js'; // ── Backward Compatibility (deprecated) ──────────────────────────────────── /** @deprecated Use `SysUser` instead */ diff --git a/packages/plugins/plugin-auth/src/objects/sys-user-preference.object.ts b/packages/plugins/plugin-auth/src/objects/sys-user-preference.object.ts new file mode 100644 index 000000000..c1c9a7e7b --- /dev/null +++ b/packages/plugins/plugin-auth/src/objects/sys-user-preference.object.ts @@ -0,0 +1,82 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +/** + * sys_user_preference — System User Preference Object + * + * Per-user key-value preferences for storing UI state, settings, and personalization. + * Supports the User Preferences layer in the Config Resolution hierarchy + * (Runtime > User Preferences > Tenant > Env). + * + * Common use cases: + * - UI preferences: theme, locale, timezone, sidebar state + * - Feature flags: plugin.ai.auto_save, plugin.dev.debug_mode + * - User-specific settings: default_view, notifications_enabled + * + * @namespace sys + */ +export const SysUserPreference = ObjectSchema.create({ + namespace: 'sys', + name: 'user_preference', + label: 'User Preference', + pluralLabel: 'User Preferences', + icon: 'settings', + isSystem: true, + description: 'Per-user key-value preferences (theme, locale, etc.)', + titleFormat: '{key}', + compactLayout: ['user_id', 'key'], + + fields: { + id: Field.text({ + label: 'Preference ID', + required: true, + readonly: true, + }), + + created_at: Field.datetime({ + label: 'Created At', + defaultValue: 'NOW()', + readonly: true, + }), + + updated_at: Field.datetime({ + label: 'Updated At', + defaultValue: 'NOW()', + readonly: true, + }), + + user_id: Field.text({ + label: 'User ID', + required: true, + maxLength: 255, + description: 'Owner user of this preference', + }), + + key: Field.text({ + label: 'Key', + required: true, + maxLength: 255, + description: 'Preference key (e.g., theme, locale, plugin.ai.auto_save)', + }), + + value: Field.json({ + label: 'Value', + description: 'Preference value (any JSON-serializable type)', + }), + }, + + indexes: [ + { fields: ['user_id', 'key'], unique: true }, + { fields: ['user_id'], unique: false }, + ], + + enable: { + trackHistory: false, + searchable: false, + apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete'], + trash: false, + mru: false, + }, +}); diff --git a/packages/spec/src/system/constants/system-names.test.ts b/packages/spec/src/system/constants/system-names.test.ts index aa6804586..a662d1e49 100644 --- a/packages/spec/src/system/constants/system-names.test.ts +++ b/packages/spec/src/system/constants/system-names.test.ts @@ -22,6 +22,7 @@ describe('SystemObjectName', () => { expect(SystemObjectName.TEAM_MEMBER).toBe('sys_team_member'); expect(SystemObjectName.API_KEY).toBe('sys_api_key'); expect(SystemObjectName.TWO_FACTOR).toBe('sys_two_factor'); + expect(SystemObjectName.USER_PREFERENCE).toBe('sys_user_preference'); expect(SystemObjectName.ROLE).toBe('sys_role'); expect(SystemObjectName.PERMISSION_SET).toBe('sys_permission_set'); expect(SystemObjectName.AUDIT_LOG).toBe('sys_audit_log'); @@ -54,6 +55,7 @@ describe('SystemObjectName', () => { expect(keys).toContain('TEAM_MEMBER'); expect(keys).toContain('API_KEY'); expect(keys).toContain('TWO_FACTOR'); + expect(keys).toContain('USER_PREFERENCE'); expect(keys).toContain('ROLE'); expect(keys).toContain('PERMISSION_SET'); expect(keys).toContain('AUDIT_LOG'); diff --git a/packages/spec/src/system/constants/system-names.ts b/packages/spec/src/system/constants/system-names.ts index 5989b22b1..8ddf7030d 100644 --- a/packages/spec/src/system/constants/system-names.ts +++ b/packages/spec/src/system/constants/system-names.ts @@ -42,6 +42,8 @@ export const SystemObjectName = { API_KEY: 'sys_api_key', /** Authentication: two-factor authentication credentials */ TWO_FACTOR: 'sys_two_factor', + /** Authentication: user preferences (theme, locale, etc.) */ + USER_PREFERENCE: 'sys_user_preference', /** Security: role definition for RBAC */ ROLE: 'sys_role', /** Security: permission set grouping */