@open-mercato/core 0.6.3-develop.3525.3.cc18b8a43f → 0.6.3-develop.3574.1.f3c5dcf345

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.
Files changed (67) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/queue-runner.js +15 -3
  3. package/dist/helpers/integration/queue-runner.js.map +2 -2
  4. package/dist/helpers/integration/queue.js +1 -1
  5. package/dist/helpers/integration/queue.js.map +2 -2
  6. package/dist/modules/auth/api/roles/acl/route.js +37 -1
  7. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  8. package/dist/modules/auth/api/roles/route.js +36 -2
  9. package/dist/modules/auth/api/roles/route.js.map +2 -2
  10. package/dist/modules/auth/api/users/acl/route.js +36 -1
  11. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  12. package/dist/modules/auth/api/users/route.js +89 -11
  13. package/dist/modules/auth/api/users/route.js.map +2 -2
  14. package/dist/modules/auth/backend/roles/[id]/edit/page.js +32 -14
  15. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  16. package/dist/modules/auth/backend/users/[id]/edit/page.js +1 -1
  17. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  18. package/dist/modules/auth/backend/users/create/page.js +1 -1
  19. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  20. package/dist/modules/auth/backend/users/roleOptions.js +1 -1
  21. package/dist/modules/auth/backend/users/roleOptions.js.map +2 -2
  22. package/dist/modules/auth/commands/roles.js +8 -0
  23. package/dist/modules/auth/commands/roles.js.map +2 -2
  24. package/dist/modules/auth/commands/users.js +3 -0
  25. package/dist/modules/auth/commands/users.js.map +2 -2
  26. package/dist/modules/auth/components/AclEditor.js +53 -26
  27. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  28. package/dist/modules/auth/lib/grantChecks.js +97 -2
  29. package/dist/modules/auth/lib/grantChecks.js.map +2 -2
  30. package/dist/modules/business_rules/lib/expression-evaluator.js +9 -7
  31. package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
  32. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +41 -12
  33. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  34. package/dist/modules/sales/commands/documents.js +104 -0
  35. package/dist/modules/sales/commands/documents.js.map +2 -2
  36. package/dist/modules/sales/data/validators.js +20 -1
  37. package/dist/modules/sales/data/validators.js.map +2 -2
  38. package/dist/modules/translations/components/TranslationManager.js +9 -4
  39. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  40. package/dist/modules/workflows/lib/event-trigger-service.js +23 -11
  41. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  42. package/jest.config.cjs +1 -0
  43. package/package.json +7 -7
  44. package/src/helpers/integration/queue-runner.ts +21 -2
  45. package/src/helpers/integration/queue.ts +1 -1
  46. package/src/modules/auth/api/roles/acl/route.ts +39 -1
  47. package/src/modules/auth/api/roles/route.ts +38 -2
  48. package/src/modules/auth/api/users/acl/route.ts +41 -1
  49. package/src/modules/auth/api/users/route.ts +92 -11
  50. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +35 -13
  51. package/src/modules/auth/backend/users/[id]/edit/page.tsx +1 -1
  52. package/src/modules/auth/backend/users/create/page.tsx +1 -1
  53. package/src/modules/auth/backend/users/roleOptions.ts +2 -1
  54. package/src/modules/auth/commands/roles.ts +8 -0
  55. package/src/modules/auth/commands/users.ts +3 -0
  56. package/src/modules/auth/components/AclEditor.tsx +50 -24
  57. package/src/modules/auth/lib/grantChecks.ts +116 -2
  58. package/src/modules/business_rules/lib/expression-evaluator.ts +9 -13
  59. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +38 -12
  60. package/src/modules/sales/commands/documents.ts +116 -0
  61. package/src/modules/sales/data/validators.ts +38 -0
  62. package/src/modules/sales/i18n/de.json +2 -0
  63. package/src/modules/sales/i18n/en.json +2 -0
  64. package/src/modules/sales/i18n/es.json +2 -0
  65. package/src/modules/sales/i18n/pl.json +2 -0
  66. package/src/modules/translations/components/TranslationManager.tsx +9 -4
  67. package/src/modules/workflows/lib/event-trigger-service.ts +24 -11
@@ -1,7 +1,7 @@
1
1
  import { CrudHttpError, forbidden } from "@open-mercato/shared/lib/crud/errors";
2
2
  import { hasFeature } from "@open-mercato/shared/security/features";
3
- import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
- import { Role, RoleAcl } from "@open-mercato/core/modules/auth/data/entities";
3
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { Role, RoleAcl, UserAcl, UserRole } from "@open-mercato/core/modules/auth/data/entities";
5
5
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
6
  async function assertActorCanGrantRoleTokens(input) {
7
7
  const tokens = normalizeStringList(input.roleTokens);
@@ -55,6 +55,96 @@ async function assertActorCanGrantAcl(input) {
55
55
  function normalizeGrantFeatureList(features) {
56
56
  return normalizeStringList(features);
57
57
  }
58
+ async function assertActorCanModifySuperAdminUserTarget(input) {
59
+ const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input);
60
+ if (actorIsSuperAdmin) return;
61
+ const targetIsSuperAdmin = await isUserEffectivelySuperAdmin(input.em, input.targetUserId);
62
+ if (targetIsSuperAdmin) {
63
+ throw forbidden("Only super administrators can modify super administrator accounts.");
64
+ }
65
+ }
66
+ async function assertActorCanModifySuperAdminRoleTarget(input) {
67
+ const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input);
68
+ if (actorIsSuperAdmin) return;
69
+ const targetIsSuperAdmin = await isRoleEffectivelySuperAdmin(input.em, input.targetRoleId);
70
+ if (targetIsSuperAdmin) {
71
+ throw forbidden("Only super administrators can modify super administrator roles.");
72
+ }
73
+ }
74
+ async function resolveActorIsSuperAdmin(input) {
75
+ if (typeof input.actorIsSuperAdmin === "boolean") return input.actorIsSuperAdmin;
76
+ const acl = await loadActorAcl(input);
77
+ return acl.isSuperAdmin;
78
+ }
79
+ async function isUserEffectivelySuperAdmin(em, userId) {
80
+ const directGrant = await em.findOne(
81
+ UserAcl,
82
+ { user: userId, isSuperAdmin: true }
83
+ );
84
+ if (directGrant && directGrant.isSuperAdmin === true) return true;
85
+ const links = await findWithDecryption(
86
+ em,
87
+ UserRole,
88
+ { user: userId },
89
+ { populate: ["role"] },
90
+ { tenantId: null, organizationId: null }
91
+ );
92
+ const roleIds = (Array.isArray(links) ? links : []).map((link) => {
93
+ const role = link.role;
94
+ if (!role) return null;
95
+ if (typeof role === "string") return role;
96
+ return role.id ? String(role.id) : null;
97
+ }).filter((id) => typeof id === "string" && id.length > 0);
98
+ if (!roleIds.length) return false;
99
+ const roleGrant = await em.findOne(
100
+ RoleAcl,
101
+ { role: { $in: roleIds }, isSuperAdmin: true }
102
+ );
103
+ return !!roleGrant && roleGrant.isSuperAdmin === true;
104
+ }
105
+ async function isRoleEffectivelySuperAdmin(em, roleId) {
106
+ const grant = await em.findOne(
107
+ RoleAcl,
108
+ { role: roleId, isSuperAdmin: true }
109
+ );
110
+ return !!grant && grant.isSuperAdmin === true;
111
+ }
112
+ async function listSuperAdminUserIds(em, tenantId) {
113
+ const ids = /* @__PURE__ */ new Set();
114
+ const userAclFilter = { isSuperAdmin: true };
115
+ if (tenantId) userAclFilter.tenantId = tenantId;
116
+ const userAcls = await em.find(UserAcl, userAclFilter);
117
+ for (const acl of userAcls) {
118
+ const userRef = acl.user;
119
+ const userId = userRef && typeof userRef === "object" ? userRef.id : userRef;
120
+ if (userId) ids.add(String(userId));
121
+ }
122
+ const roleAcls = await em.find(
123
+ RoleAcl,
124
+ { isSuperAdmin: true }
125
+ );
126
+ const roleIds = roleAcls.map((acl) => {
127
+ const roleRef = acl.role;
128
+ if (!roleRef) return null;
129
+ if (typeof roleRef === "string") return roleRef;
130
+ return roleRef.id ? String(roleRef.id) : null;
131
+ }).filter((id) => typeof id === "string" && id.length > 0);
132
+ if (roleIds.length) {
133
+ const links = await findWithDecryption(
134
+ em,
135
+ UserRole,
136
+ { role: { $in: roleIds } },
137
+ {},
138
+ { tenantId: null, organizationId: null }
139
+ );
140
+ for (const link of Array.isArray(links) ? links : []) {
141
+ const userRef = link.user;
142
+ const userId = userRef && typeof userRef === "object" ? userRef.id : userRef;
143
+ if (userId) ids.add(String(userId));
144
+ }
145
+ }
146
+ return ids;
147
+ }
58
148
  async function loadActorAcl(input) {
59
149
  const actorUserId = normalizeNullableString(input.actorUserId);
60
150
  if (!actorUserId) throw forbidden("Not authorized to grant ACL privileges.");
@@ -155,6 +245,11 @@ export {
155
245
  assertActorCanGrantAcl,
156
246
  assertActorCanGrantRoleTokens,
157
247
  assertActorCanGrantRoles,
248
+ assertActorCanModifySuperAdminRoleTarget,
249
+ assertActorCanModifySuperAdminUserTarget,
250
+ isRoleEffectivelySuperAdmin,
251
+ isUserEffectivelySuperAdmin,
252
+ listSuperAdminUserIds,
158
253
  normalizeGrantFeatureList
159
254
  };
160
255
  //# sourceMappingURL=grantChecks.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/lib/grantChecks.ts"],
4
- "sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Role, RoleAcl } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\ntype ActorAcl = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype GrantCheckContext = {\n em: EntityManager\n rbacService: RbacService\n actorUserId: string | null | undefined\n tenantId: string | null | undefined\n organizationId?: string | null | undefined\n}\n\ntype RoleGrantCheckInput = GrantCheckContext & {\n roles: Role[]\n}\n\ntype RoleTokenGrantCheckInput = GrantCheckContext & {\n roleTokens: unknown\n}\n\ntype FeatureGrantCheckInput = GrantCheckContext & {\n features: unknown\n isSuperAdmin?: boolean\n organizations?: string[] | null\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function assertActorCanGrantRoleTokens(input: RoleTokenGrantCheckInput): Promise<Role[]> {\n const tokens = normalizeStringList(input.roleTokens)\n if (!tokens.length) return []\n\n const tenantId = normalizeNullableString(input.tenantId)\n const roles = await resolveRolesForGrant(input.em, tokens, tenantId)\n await assertActorCanGrantRoles({ ...input, tenantId, roles })\n return roles\n}\n\nexport async function assertActorCanGrantRoles(input: RoleGrantCheckInput): Promise<void> {\n if (!input.roles.length) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n const actorAcl = await loadActorAcl({ ...input, tenantId })\n if (actorAcl.isSuperAdmin) return\n\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant roles.')\n }\n\n for (const role of input.roles) {\n const roleTenantId = normalizeNullableString(role.tenantId)\n if (roleTenantId !== tenantId) {\n throw forbidden('Cannot grant a role outside the target tenant.')\n }\n\n const acl = await findOneWithDecryption(\n input.em,\n RoleAcl,\n { role, tenantId } as FilterQuery<RoleAcl>,\n {},\n { tenantId, organizationId: null },\n )\n if (!acl) continue\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: normalizeStringList(acl.featuresJson),\n organizations: normalizeOrganizationList(acl.organizationsJson),\n })\n }\n}\n\nexport async function assertActorCanGrantAcl(input: FeatureGrantCheckInput): Promise<void> {\n const actorAcl = await loadActorAcl(input)\n if (actorAcl.isSuperAdmin) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant ACL features.')\n }\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!input.isSuperAdmin,\n features: normalizeStringList(input.features),\n organizations: input.organizations === undefined ? undefined : normalizeOrganizationList(input.organizations),\n })\n}\n\nexport function normalizeGrantFeatureList(features: unknown): string[] {\n return normalizeStringList(features)\n}\n\nasync function loadActorAcl(input: GrantCheckContext): Promise<ActorAcl> {\n const actorUserId = normalizeNullableString(input.actorUserId)\n if (!actorUserId) throw forbidden('Not authorized to grant ACL privileges.')\n\n const acl = await input.rbacService.loadAcl(actorUserId, {\n tenantId: normalizeNullableString(input.tenantId),\n organizationId: normalizeNullableString(input.organizationId),\n })\n\n return {\n isSuperAdmin: !!acl?.isSuperAdmin,\n features: normalizeStringList(acl?.features),\n organizations: normalizeOrganizationList(acl?.organizations),\n }\n}\n\nasync function resolveRolesForGrant(\n em: EntityManager,\n roleTokens: string[],\n tenantId: string | null,\n): Promise<Role[]> {\n const roles: Role[] = []\n const missingRoles: string[] = []\n\n for (const token of roleTokens) {\n const role = await resolveRoleForGrant(em, token, tenantId)\n if (!role) {\n missingRoles.push(token)\n } else {\n roles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((role) => `\"${role}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n return roles\n}\n\nasync function resolveRoleForGrant(\n em: EntityManager,\n token: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const where: Record<string, unknown> = UUID_RE.test(token)\n ? { id: token, deletedAt: null }\n : { name: token, deletedAt: null }\n if (tenantId) where.tenantId = tenantId\n return findOneWithDecryption(\n em,\n Role,\n where as FilterQuery<Role>,\n {},\n { tenantId, organizationId: null },\n )\n}\n\nfunction assertActorCanGrantAclSnapshot(\n actorAcl: ActorAcl,\n requested: {\n isSuperAdmin: boolean\n features: string[]\n organizations?: string[] | null\n },\n): void {\n if (requested.isSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n\n const actorGrantableFeatures = actorAcl.features.filter((grant) => grant !== '*')\n for (const feature of requested.features) {\n if (feature === '*') {\n throw forbidden('Only super administrators can grant global wildcard access.')\n }\n if (isWildcardFeature(feature)) {\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature wildcard ${feature}.`)\n }\n continue\n }\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature ${feature}.`)\n }\n }\n\n if (requested.organizations !== undefined) {\n assertActorCanGrantOrganizations(actorAcl.organizations, requested.organizations)\n }\n}\n\nfunction assertActorCanGrantOrganizations(\n actorOrganizations: string[] | null,\n requestedOrganizations: string[] | null,\n): void {\n if (actorOrganizations === null || actorOrganizations.includes('__all__')) return\n\n if (requestedOrganizations === null || requestedOrganizations.includes('__all__')) {\n throw forbidden('Cannot grant unrestricted organization access.')\n }\n\n for (const organizationId of requestedOrganizations) {\n if (!actorOrganizations.includes(organizationId)) {\n throw forbidden('Cannot grant organization access outside actor scope.')\n }\n }\n}\n\nfunction normalizeStringList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n const dedup = new Set<string>()\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n return normalizeStringList(values)\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction isWildcardFeature(feature: string): boolean {\n return feature.endsWith('.*')\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Role, RoleAcl, UserAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\ntype ActorAcl = {\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n}\n\ntype GrantCheckContext = {\n em: EntityManager\n rbacService: RbacService\n actorUserId: string | null | undefined\n tenantId: string | null | undefined\n organizationId?: string | null | undefined\n}\n\ntype RoleGrantCheckInput = GrantCheckContext & {\n roles: Role[]\n}\n\ntype RoleTokenGrantCheckInput = GrantCheckContext & {\n roleTokens: unknown\n}\n\ntype FeatureGrantCheckInput = GrantCheckContext & {\n features: unknown\n isSuperAdmin?: boolean\n organizations?: string[] | null\n}\n\ntype SuperAdminUserTargetInput = GrantCheckContext & {\n targetUserId: string\n actorIsSuperAdmin?: boolean\n}\n\ntype SuperAdminRoleTargetInput = GrantCheckContext & {\n targetRoleId: string\n actorIsSuperAdmin?: boolean\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function assertActorCanGrantRoleTokens(input: RoleTokenGrantCheckInput): Promise<Role[]> {\n const tokens = normalizeStringList(input.roleTokens)\n if (!tokens.length) return []\n\n const tenantId = normalizeNullableString(input.tenantId)\n const roles = await resolveRolesForGrant(input.em, tokens, tenantId)\n await assertActorCanGrantRoles({ ...input, tenantId, roles })\n return roles\n}\n\nexport async function assertActorCanGrantRoles(input: RoleGrantCheckInput): Promise<void> {\n if (!input.roles.length) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n const actorAcl = await loadActorAcl({ ...input, tenantId })\n if (actorAcl.isSuperAdmin) return\n\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant roles.')\n }\n\n for (const role of input.roles) {\n const roleTenantId = normalizeNullableString(role.tenantId)\n if (roleTenantId !== tenantId) {\n throw forbidden('Cannot grant a role outside the target tenant.')\n }\n\n const acl = await findOneWithDecryption(\n input.em,\n RoleAcl,\n { role, tenantId } as FilterQuery<RoleAcl>,\n {},\n { tenantId, organizationId: null },\n )\n if (!acl) continue\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: normalizeStringList(acl.featuresJson),\n organizations: normalizeOrganizationList(acl.organizationsJson),\n })\n }\n}\n\nexport async function assertActorCanGrantAcl(input: FeatureGrantCheckInput): Promise<void> {\n const actorAcl = await loadActorAcl(input)\n if (actorAcl.isSuperAdmin) return\n\n const tenantId = normalizeNullableString(input.tenantId)\n if (!tenantId) {\n throw forbidden('Tenant context is required to grant ACL features.')\n }\n\n assertActorCanGrantAclSnapshot(actorAcl, {\n isSuperAdmin: !!input.isSuperAdmin,\n features: normalizeStringList(input.features),\n organizations: input.organizations === undefined ? undefined : normalizeOrganizationList(input.organizations),\n })\n}\n\nexport function normalizeGrantFeatureList(features: unknown): string[] {\n return normalizeStringList(features)\n}\n\nexport async function assertActorCanModifySuperAdminUserTarget(input: SuperAdminUserTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isUserEffectivelySuperAdmin(input.em, input.targetUserId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator accounts.')\n }\n}\n\nexport async function assertActorCanModifySuperAdminRoleTarget(input: SuperAdminRoleTargetInput): Promise<void> {\n const actorIsSuperAdmin = await resolveActorIsSuperAdmin(input)\n if (actorIsSuperAdmin) return\n const targetIsSuperAdmin = await isRoleEffectivelySuperAdmin(input.em, input.targetRoleId)\n if (targetIsSuperAdmin) {\n throw forbidden('Only super administrators can modify super administrator roles.')\n }\n}\n\nasync function resolveActorIsSuperAdmin(input: GrantCheckContext & { actorIsSuperAdmin?: boolean }): Promise<boolean> {\n if (typeof input.actorIsSuperAdmin === 'boolean') return input.actorIsSuperAdmin\n const acl = await loadActorAcl(input)\n return acl.isSuperAdmin\n}\n\nexport async function isUserEffectivelySuperAdmin(em: EntityManager, userId: string): Promise<boolean> {\n const directGrant = await em.findOne(\n UserAcl,\n { user: userId as unknown, isSuperAdmin: true } as FilterQuery<UserAcl>,\n )\n if (directGrant && (directGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true) return true\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as unknown } as FilterQuery<UserRole>,\n { populate: ['role'] },\n { tenantId: null, organizationId: null },\n )\n const roleIds = (Array.isArray(links) ? links : [])\n .map((link) => {\n const role = (link as { role?: { id?: unknown } | string | null }).role\n if (!role) return null\n if (typeof role === 'string') return role\n return role.id ? String(role.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (!roleIds.length) return false\n const roleGrant = await em.findOne(\n RoleAcl,\n { role: { $in: roleIds } as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!roleGrant && (roleGrant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function isRoleEffectivelySuperAdmin(em: EntityManager, roleId: string): Promise<boolean> {\n const grant = await em.findOne(\n RoleAcl,\n { role: roleId as unknown, isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n return !!grant && (grant as { isSuperAdmin?: boolean }).isSuperAdmin === true\n}\n\nexport async function listSuperAdminUserIds(em: EntityManager, tenantId: string | null): Promise<Set<string>> {\n const ids = new Set<string>()\n const userAclFilter: Record<string, unknown> = { isSuperAdmin: true }\n if (tenantId) userAclFilter.tenantId = tenantId\n const userAcls = await em.find(UserAcl, userAclFilter as FilterQuery<UserAcl>)\n for (const acl of userAcls) {\n const userRef = (acl as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n const roleAcls = await em.find(\n RoleAcl,\n { isSuperAdmin: true } as FilterQuery<RoleAcl>,\n )\n const roleIds = roleAcls\n .map((acl) => {\n const roleRef = (acl as { role?: { id?: unknown } | string | null }).role\n if (!roleRef) return null\n if (typeof roleRef === 'string') return roleRef\n return roleRef.id ? String(roleRef.id) : null\n })\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (roleIds.length) {\n const links = await findWithDecryption(\n em,\n UserRole,\n { role: { $in: roleIds } as unknown } as FilterQuery<UserRole>,\n {},\n { tenantId: null, organizationId: null },\n )\n for (const link of Array.isArray(links) ? links : []) {\n const userRef = (link as { user?: { id?: unknown } | string | null }).user\n const userId = userRef && typeof userRef === 'object'\n ? userRef.id\n : userRef\n if (userId) ids.add(String(userId))\n }\n }\n return ids\n}\n\nasync function loadActorAcl(input: GrantCheckContext): Promise<ActorAcl> {\n const actorUserId = normalizeNullableString(input.actorUserId)\n if (!actorUserId) throw forbidden('Not authorized to grant ACL privileges.')\n\n const acl = await input.rbacService.loadAcl(actorUserId, {\n tenantId: normalizeNullableString(input.tenantId),\n organizationId: normalizeNullableString(input.organizationId),\n })\n\n return {\n isSuperAdmin: !!acl?.isSuperAdmin,\n features: normalizeStringList(acl?.features),\n organizations: normalizeOrganizationList(acl?.organizations),\n }\n}\n\nasync function resolveRolesForGrant(\n em: EntityManager,\n roleTokens: string[],\n tenantId: string | null,\n): Promise<Role[]> {\n const roles: Role[] = []\n const missingRoles: string[] = []\n\n for (const token of roleTokens) {\n const role = await resolveRoleForGrant(em, token, tenantId)\n if (!role) {\n missingRoles.push(token)\n } else {\n roles.push(role)\n }\n }\n\n if (missingRoles.length) {\n const labels = missingRoles.map((role) => `\"${role}\"`).join(', ')\n throw new CrudHttpError(400, { error: `Role(s) not found: ${labels}` })\n }\n\n return roles\n}\n\nasync function resolveRoleForGrant(\n em: EntityManager,\n token: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const where: Record<string, unknown> = UUID_RE.test(token)\n ? { id: token, deletedAt: null }\n : { name: token, deletedAt: null }\n if (tenantId) where.tenantId = tenantId\n return findOneWithDecryption(\n em,\n Role,\n where as FilterQuery<Role>,\n {},\n { tenantId, organizationId: null },\n )\n}\n\nfunction assertActorCanGrantAclSnapshot(\n actorAcl: ActorAcl,\n requested: {\n isSuperAdmin: boolean\n features: string[]\n organizations?: string[] | null\n },\n): void {\n if (requested.isSuperAdmin) {\n throw forbidden('Only super administrators can grant super admin access.')\n }\n\n const actorGrantableFeatures = actorAcl.features.filter((grant) => grant !== '*')\n for (const feature of requested.features) {\n if (feature === '*') {\n throw forbidden('Only super administrators can grant global wildcard access.')\n }\n if (isWildcardFeature(feature)) {\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature wildcard ${feature}.`)\n }\n continue\n }\n if (!hasFeature(actorGrantableFeatures, feature)) {\n throw forbidden(`Cannot grant feature ${feature}.`)\n }\n }\n\n if (requested.organizations !== undefined) {\n assertActorCanGrantOrganizations(actorAcl.organizations, requested.organizations)\n }\n}\n\nfunction assertActorCanGrantOrganizations(\n actorOrganizations: string[] | null,\n requestedOrganizations: string[] | null,\n): void {\n if (actorOrganizations === null || actorOrganizations.includes('__all__')) return\n\n if (requestedOrganizations === null || requestedOrganizations.includes('__all__')) {\n throw forbidden('Cannot grant unrestricted organization access.')\n }\n\n for (const organizationId of requestedOrganizations) {\n if (!actorOrganizations.includes(organizationId)) {\n throw forbidden('Cannot grant organization access outside actor scope.')\n }\n }\n}\n\nfunction normalizeStringList(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n const dedup = new Set<string>()\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n dedup.add(trimmed)\n }\n return Array.from(dedup)\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n return normalizeStringList(values)\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction isWildcardFeature(feature: string): boolean {\n return feature.endsWith('.*')\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { testLinearRegex } from "@open-mercato/shared/lib/regex/linear";
1
2
  import { getNestedValue, resolveSpecialValue } from "./value-resolver.js";
2
3
  function evaluateExpression(expression, data, context) {
3
4
  if (isGroupCondition(expression)) {
@@ -154,8 +155,8 @@ function endsWith(str, suffix) {
154
155
  if (str == null || suffix == null) return false;
155
156
  return String(str).endsWith(String(suffix));
156
157
  }
157
- const REGEX_TIMEOUT_MS = 100;
158
158
  const MAX_REGEX_LENGTH = 200;
159
+ const MAX_REGEX_INPUT_LENGTH = 1e4;
159
160
  function matches(str, pattern) {
160
161
  if (str == null || pattern == null) return false;
161
162
  try {
@@ -166,14 +167,15 @@ function matches(str, pattern) {
166
167
  if (/(\(.*[+*]\).*[+*])/.test(patternStr)) {
167
168
  return false;
168
169
  }
169
- const regex = new RegExp(patternStr);
170
170
  const testStr = String(str);
171
- const startTime = Date.now();
172
- const result = regex.test(testStr);
173
- if (Date.now() - startTime > REGEX_TIMEOUT_MS) {
174
- throw new Error("Regex execution timeout - potential ReDoS pattern detected");
171
+ if (testStr.length > MAX_REGEX_INPUT_LENGTH) {
172
+ return false;
175
173
  }
176
- return result;
174
+ const result = testLinearRegex(patternStr, testStr, {
175
+ maxPatternLength: MAX_REGEX_LENGTH,
176
+ maxInputLength: MAX_REGEX_INPUT_LENGTH
177
+ });
178
+ return result.ok ? result.matched : false;
177
179
  } catch (error) {
178
180
  const message = error instanceof Error ? error.message : "Unknown error";
179
181
  if (process.env.NODE_ENV !== "test") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/business_rules/lib/expression-evaluator.ts"],
4
- "sourcesContent": ["import type { ComparisonOperator, LogicalOperator } from '../data/validators'\nimport { getNestedValue, resolveSpecialValue } from './value-resolver'\n\n/**\n * Type definitions for condition expressions\n */\nexport type ConditionExpression = SimpleCondition | GroupCondition\n\nexport interface SimpleCondition {\n field: string\n operator: ComparisonOperator\n value: any\n valueField?: string // For field-to-field comparison\n}\n\nexport interface GroupCondition {\n operator: LogicalOperator // 'AND' | 'OR' | 'NOT'\n rules: ConditionExpression[]\n}\n\nexport interface EvaluationContext {\n user?: {\n id?: string\n email?: string\n role?: string\n [key: string]: any\n }\n tenant?: {\n id?: string\n [key: string]: any\n }\n organization?: {\n id?: string\n [key: string]: any\n }\n now?: Date\n today?: Date\n [key: string]: any\n}\n\n/**\n * Evaluate a condition expression (simple or grouped)\n */\nexport function evaluateExpression(\n expression: ConditionExpression,\n data: any,\n context: EvaluationContext\n): boolean {\n if (isGroupCondition(expression)) {\n return evaluateGroupCondition(expression, data, context)\n }\n\n return evaluateSimpleCondition(expression, data, context)\n}\n\n/**\n * Check if expression is a group condition\n */\nfunction isGroupCondition(expression: ConditionExpression): expression is GroupCondition {\n return 'rules' in expression && Array.isArray(expression.rules)\n}\n\n/**\n * Evaluate a simple condition\n */\nfunction evaluateSimpleCondition(\n condition: SimpleCondition,\n data: any,\n context: EvaluationContext\n): boolean {\n // Left side: always resolve from data (field path)\n const leftValue = resolveFieldPath(condition.field, data)\n\n let rightValue: any\n if (condition.valueField) {\n // Field-to-field comparison: resolve from data\n rightValue = resolveFieldPath(condition.valueField, data)\n } else {\n // Static value or special value\n rightValue = resolveStaticOrSpecialValue(condition.value, context)\n }\n\n const result = applyOperator(leftValue, condition.operator, rightValue)\n\n // Detailed logging for debugging\n console.log('[RULE EVAL] Simple condition:', {\n field: condition.field,\n operator: condition.operator,\n expectedValue: rightValue,\n actualValue: leftValue,\n actualValueType: typeof leftValue,\n result: result ? '\u2713 PASS' : '\u2717 FAIL',\n })\n\n return result\n}\n\n/**\n * Evaluate a group condition (AND, OR, NOT)\n */\nfunction evaluateGroupCondition(\n condition: GroupCondition,\n data: any,\n context: EvaluationContext\n): boolean {\n const { operator, rules } = condition\n\n if (!rules || rules.length === 0) {\n return true\n }\n\n console.log(`[RULE EVAL] Group condition: ${operator} with ${rules.length} rules`)\n\n let result: boolean\n\n switch (operator) {\n case 'AND':\n result = rules.every((rule) => evaluateExpression(rule, data, context))\n break\n\n case 'OR':\n result = rules.some((rule) => evaluateExpression(rule, data, context))\n break\n\n case 'NOT':\n // NOT operator - negate the first rule (or all rules combined with AND)\n if (rules.length === 1) {\n result = !evaluateExpression(rules[0], data, context)\n } else {\n // Multiple rules - combine with AND then negate\n result = !rules.every((rule) => evaluateExpression(rule, data, context))\n }\n break\n\n default:\n throw new Error(`Unknown logical operator: ${operator}`)\n }\n\n console.log(`[RULE EVAL] Group ${operator} result: ${result ? '\u2713 PASS' : '\u2717 FAIL'}`)\n\n return result\n}\n\n/**\n * Resolve a field path from data\n */\nfunction resolveFieldPath(path: string, data: any): any {\n return getNestedValue(data, path)\n}\n\n/**\n * Resolve a static value or special template value\n */\nfunction resolveStaticOrSpecialValue(value: any, context: EvaluationContext): any {\n // If not a string, return as-is (already a value)\n if (typeof value !== 'string') {\n return value\n }\n\n // Check for special values (template variables)\n if (value.startsWith('{{') && value.endsWith('}}')) {\n return resolveSpecialValue(value, context)\n }\n\n // Return static string value as-is\n return value\n}\n\n/**\n * Apply a comparison operator\n */\nfunction applyOperator(left: any, operator: ComparisonOperator, right: any): boolean {\n switch (operator) {\n case '=':\n case '==':\n return equals(left, right)\n\n case '!=':\n return !equals(left, right)\n\n case '>':\n return compare(left, right) > 0\n\n case '>=':\n return compare(left, right) >= 0\n\n case '<':\n return compare(left, right) < 0\n\n case '<=':\n return compare(left, right) <= 0\n\n case 'IN':\n return isIn(left, right)\n\n case 'NOT_IN':\n return !isIn(left, right)\n\n case 'CONTAINS':\n return contains(left, right)\n\n case 'NOT_CONTAINS':\n return !contains(left, right)\n\n case 'STARTS_WITH':\n return startsWith(left, right)\n\n case 'ENDS_WITH':\n return endsWith(left, right)\n\n case 'MATCHES':\n return matches(left, right)\n\n case 'IS_EMPTY':\n return isEmpty(left)\n\n case 'IS_NOT_EMPTY':\n return !isEmpty(left)\n\n default:\n throw new Error(`Unknown operator: ${operator}`)\n }\n}\n\n/**\n * Equality comparison (handles different types)\n */\nfunction equals(left: any, right: any): boolean {\n // Handle null/undefined\n if (left == null && right == null) return true\n if (left == null || right == null) return false\n\n // Type coercion for numbers\n if (typeof left === 'number' || typeof right === 'number') {\n return Number(left) === Number(right)\n }\n\n // String comparison (case-sensitive)\n return left === right\n}\n\n/**\n * Comparison for ordering (>, <, >=, <=)\n */\nfunction compare(left: any, right: any): number {\n // Handle null/undefined\n if (left == null && right == null) return 0\n if (left == null) return -1\n if (right == null) return 1\n\n // Date comparison\n if (left instanceof Date && right instanceof Date) {\n return left.getTime() - right.getTime()\n }\n\n // String date comparison\n if (isDateString(left) && isDateString(right)) {\n return new Date(left).getTime() - new Date(right).getTime()\n }\n\n // Numeric comparison\n const leftNum = Number(left)\n const rightNum = Number(right)\n if (!isNaN(leftNum) && !isNaN(rightNum)) {\n return leftNum - rightNum\n }\n\n // String comparison\n const leftStr = String(left)\n const rightStr = String(right)\n return leftStr.localeCompare(rightStr)\n}\n\n/**\n * Check if value is in array\n */\nfunction isIn(value: any, array: any): boolean {\n if (!Array.isArray(array)) {\n return false\n }\n\n return array.some((item) => equals(value, item))\n}\n\n/**\n * Check if array/string contains value\n */\nfunction contains(container: any, value: any): boolean {\n if (container == null) return false\n\n // Array contains\n if (Array.isArray(container)) {\n return container.some((item) => equals(item, value))\n }\n\n // String contains\n if (typeof container === 'string') {\n return container.includes(String(value))\n }\n\n return false\n}\n\n/**\n * Check if string starts with value\n */\nfunction startsWith(str: any, prefix: any): boolean {\n if (str == null || prefix == null) return false\n\n return String(str).startsWith(String(prefix))\n}\n\n/**\n * Check if string ends with value\n */\nfunction endsWith(str: any, suffix: any): boolean {\n if (str == null || suffix == null) return false\n\n return String(str).endsWith(String(suffix))\n}\n\n/**\n * Security limits for regex operations\n */\nconst REGEX_TIMEOUT_MS = 100\nconst MAX_REGEX_LENGTH = 200\n\n/**\n * Check if string matches regex pattern (with ReDoS protection)\n */\nfunction matches(str: any, pattern: any): boolean {\n if (str == null || pattern == null) return false\n\n try {\n const patternStr = String(pattern)\n\n // Prevent overly long patterns\n if (patternStr.length > MAX_REGEX_LENGTH) {\n return false\n }\n\n // Check for dangerous patterns that can cause exponential backtracking\n // Patterns like (a+)+, (a*)*, (a+)*, etc.\n if (/(\\(.*[+*]\\).*[+*])/.test(patternStr)) {\n return false\n }\n\n const regex = new RegExp(patternStr)\n const testStr = String(str)\n\n // Use a simple timeout mechanism\n const startTime = Date.now()\n const result = regex.test(testStr)\n\n if (Date.now() - startTime > REGEX_TIMEOUT_MS) {\n throw new Error('Regex execution timeout - potential ReDoS pattern detected')\n }\n\n return result\n } catch (error) {\n // Log the error for debugging but don't expose to user\n const message = error instanceof Error ? error.message : 'Unknown error'\n if (process.env.NODE_ENV !== 'test') {\n console.error('Regex matching failed:', message)\n }\n return false\n }\n}\n\n/**\n * Check if value is empty (null, undefined, empty string, empty array, empty object)\n */\nfunction isEmpty(value: any): boolean {\n if (value == null) return true\n\n if (typeof value === 'string') return value.trim() === ''\n\n if (Array.isArray(value)) return value.length === 0\n\n if (typeof value === 'object') return Object.keys(value).length === 0\n\n return false\n}\n\n/**\n * Check if string looks like a date (ISO format or common date formats)\n */\nfunction isDateString(value: any): boolean {\n if (typeof value !== 'string') return false\n\n // ISO date format: YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss\n const isoDateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2})?/\n if (isoDateRegex.test(value)) {\n const date = new Date(value)\n return !isNaN(date.getTime())\n }\n\n return false\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ComparisonOperator, LogicalOperator } from '../data/validators'\nimport { testLinearRegex } from '@open-mercato/shared/lib/regex/linear'\nimport { getNestedValue, resolveSpecialValue } from './value-resolver'\n\n/**\n * Type definitions for condition expressions\n */\nexport type ConditionExpression = SimpleCondition | GroupCondition\n\nexport interface SimpleCondition {\n field: string\n operator: ComparisonOperator\n value: any\n valueField?: string // For field-to-field comparison\n}\n\nexport interface GroupCondition {\n operator: LogicalOperator // 'AND' | 'OR' | 'NOT'\n rules: ConditionExpression[]\n}\n\nexport interface EvaluationContext {\n user?: {\n id?: string\n email?: string\n role?: string\n [key: string]: any\n }\n tenant?: {\n id?: string\n [key: string]: any\n }\n organization?: {\n id?: string\n [key: string]: any\n }\n now?: Date\n today?: Date\n [key: string]: any\n}\n\n/**\n * Evaluate a condition expression (simple or grouped)\n */\nexport function evaluateExpression(\n expression: ConditionExpression,\n data: any,\n context: EvaluationContext\n): boolean {\n if (isGroupCondition(expression)) {\n return evaluateGroupCondition(expression, data, context)\n }\n\n return evaluateSimpleCondition(expression, data, context)\n}\n\n/**\n * Check if expression is a group condition\n */\nfunction isGroupCondition(expression: ConditionExpression): expression is GroupCondition {\n return 'rules' in expression && Array.isArray(expression.rules)\n}\n\n/**\n * Evaluate a simple condition\n */\nfunction evaluateSimpleCondition(\n condition: SimpleCondition,\n data: any,\n context: EvaluationContext\n): boolean {\n // Left side: always resolve from data (field path)\n const leftValue = resolveFieldPath(condition.field, data)\n\n let rightValue: any\n if (condition.valueField) {\n // Field-to-field comparison: resolve from data\n rightValue = resolveFieldPath(condition.valueField, data)\n } else {\n // Static value or special value\n rightValue = resolveStaticOrSpecialValue(condition.value, context)\n }\n\n const result = applyOperator(leftValue, condition.operator, rightValue)\n\n // Detailed logging for debugging\n console.log('[RULE EVAL] Simple condition:', {\n field: condition.field,\n operator: condition.operator,\n expectedValue: rightValue,\n actualValue: leftValue,\n actualValueType: typeof leftValue,\n result: result ? '\u2713 PASS' : '\u2717 FAIL',\n })\n\n return result\n}\n\n/**\n * Evaluate a group condition (AND, OR, NOT)\n */\nfunction evaluateGroupCondition(\n condition: GroupCondition,\n data: any,\n context: EvaluationContext\n): boolean {\n const { operator, rules } = condition\n\n if (!rules || rules.length === 0) {\n return true\n }\n\n console.log(`[RULE EVAL] Group condition: ${operator} with ${rules.length} rules`)\n\n let result: boolean\n\n switch (operator) {\n case 'AND':\n result = rules.every((rule) => evaluateExpression(rule, data, context))\n break\n\n case 'OR':\n result = rules.some((rule) => evaluateExpression(rule, data, context))\n break\n\n case 'NOT':\n // NOT operator - negate the first rule (or all rules combined with AND)\n if (rules.length === 1) {\n result = !evaluateExpression(rules[0], data, context)\n } else {\n // Multiple rules - combine with AND then negate\n result = !rules.every((rule) => evaluateExpression(rule, data, context))\n }\n break\n\n default:\n throw new Error(`Unknown logical operator: ${operator}`)\n }\n\n console.log(`[RULE EVAL] Group ${operator} result: ${result ? '\u2713 PASS' : '\u2717 FAIL'}`)\n\n return result\n}\n\n/**\n * Resolve a field path from data\n */\nfunction resolveFieldPath(path: string, data: any): any {\n return getNestedValue(data, path)\n}\n\n/**\n * Resolve a static value or special template value\n */\nfunction resolveStaticOrSpecialValue(value: any, context: EvaluationContext): any {\n // If not a string, return as-is (already a value)\n if (typeof value !== 'string') {\n return value\n }\n\n // Check for special values (template variables)\n if (value.startsWith('{{') && value.endsWith('}}')) {\n return resolveSpecialValue(value, context)\n }\n\n // Return static string value as-is\n return value\n}\n\n/**\n * Apply a comparison operator\n */\nfunction applyOperator(left: any, operator: ComparisonOperator, right: any): boolean {\n switch (operator) {\n case '=':\n case '==':\n return equals(left, right)\n\n case '!=':\n return !equals(left, right)\n\n case '>':\n return compare(left, right) > 0\n\n case '>=':\n return compare(left, right) >= 0\n\n case '<':\n return compare(left, right) < 0\n\n case '<=':\n return compare(left, right) <= 0\n\n case 'IN':\n return isIn(left, right)\n\n case 'NOT_IN':\n return !isIn(left, right)\n\n case 'CONTAINS':\n return contains(left, right)\n\n case 'NOT_CONTAINS':\n return !contains(left, right)\n\n case 'STARTS_WITH':\n return startsWith(left, right)\n\n case 'ENDS_WITH':\n return endsWith(left, right)\n\n case 'MATCHES':\n return matches(left, right)\n\n case 'IS_EMPTY':\n return isEmpty(left)\n\n case 'IS_NOT_EMPTY':\n return !isEmpty(left)\n\n default:\n throw new Error(`Unknown operator: ${operator}`)\n }\n}\n\n/**\n * Equality comparison (handles different types)\n */\nfunction equals(left: any, right: any): boolean {\n // Handle null/undefined\n if (left == null && right == null) return true\n if (left == null || right == null) return false\n\n // Type coercion for numbers\n if (typeof left === 'number' || typeof right === 'number') {\n return Number(left) === Number(right)\n }\n\n // String comparison (case-sensitive)\n return left === right\n}\n\n/**\n * Comparison for ordering (>, <, >=, <=)\n */\nfunction compare(left: any, right: any): number {\n // Handle null/undefined\n if (left == null && right == null) return 0\n if (left == null) return -1\n if (right == null) return 1\n\n // Date comparison\n if (left instanceof Date && right instanceof Date) {\n return left.getTime() - right.getTime()\n }\n\n // String date comparison\n if (isDateString(left) && isDateString(right)) {\n return new Date(left).getTime() - new Date(right).getTime()\n }\n\n // Numeric comparison\n const leftNum = Number(left)\n const rightNum = Number(right)\n if (!isNaN(leftNum) && !isNaN(rightNum)) {\n return leftNum - rightNum\n }\n\n // String comparison\n const leftStr = String(left)\n const rightStr = String(right)\n return leftStr.localeCompare(rightStr)\n}\n\n/**\n * Check if value is in array\n */\nfunction isIn(value: any, array: any): boolean {\n if (!Array.isArray(array)) {\n return false\n }\n\n return array.some((item) => equals(value, item))\n}\n\n/**\n * Check if array/string contains value\n */\nfunction contains(container: any, value: any): boolean {\n if (container == null) return false\n\n // Array contains\n if (Array.isArray(container)) {\n return container.some((item) => equals(item, value))\n }\n\n // String contains\n if (typeof container === 'string') {\n return container.includes(String(value))\n }\n\n return false\n}\n\n/**\n * Check if string starts with value\n */\nfunction startsWith(str: any, prefix: any): boolean {\n if (str == null || prefix == null) return false\n\n return String(str).startsWith(String(prefix))\n}\n\n/**\n * Check if string ends with value\n */\nfunction endsWith(str: any, suffix: any): boolean {\n if (str == null || suffix == null) return false\n\n return String(str).endsWith(String(suffix))\n}\n\nconst MAX_REGEX_LENGTH = 200\nconst MAX_REGEX_INPUT_LENGTH = 10_000\n\n/**\n * Check if string matches regex pattern (with ReDoS protection)\n */\nfunction matches(str: any, pattern: any): boolean {\n if (str == null || pattern == null) return false\n\n try {\n const patternStr = String(pattern)\n\n // Prevent overly long patterns\n if (patternStr.length > MAX_REGEX_LENGTH) {\n return false\n }\n\n // Check for dangerous patterns that can cause exponential backtracking\n // Patterns like (a+)+, (a*)*, (a+)*, etc.\n if (/(\\(.*[+*]\\).*[+*])/.test(patternStr)) {\n return false\n }\n\n const testStr = String(str)\n if (testStr.length > MAX_REGEX_INPUT_LENGTH) {\n return false\n }\n\n const result = testLinearRegex(patternStr, testStr, {\n maxPatternLength: MAX_REGEX_LENGTH,\n maxInputLength: MAX_REGEX_INPUT_LENGTH,\n })\n return result.ok ? result.matched : false\n } catch (error) {\n // Log the error for debugging but don't expose to user\n const message = error instanceof Error ? error.message : 'Unknown error'\n if (process.env.NODE_ENV !== 'test') {\n console.error('Regex matching failed:', message)\n }\n return false\n }\n}\n\n/**\n * Check if value is empty (null, undefined, empty string, empty array, empty object)\n */\nfunction isEmpty(value: any): boolean {\n if (value == null) return true\n\n if (typeof value === 'string') return value.trim() === ''\n\n if (Array.isArray(value)) return value.length === 0\n\n if (typeof value === 'object') return Object.keys(value).length === 0\n\n return false\n}\n\n/**\n * Check if string looks like a date (ISO format or common date formats)\n */\nfunction isDateString(value: any): boolean {\n if (typeof value !== 'string') return false\n\n // ISO date format: YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss\n const isoDateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2})?/\n if (isoDateRegex.test(value)) {\n const date = new Date(value)\n return !isNaN(date.getTime())\n }\n\n return false\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -38,7 +38,7 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
38
38
  (widget) => resolveWidgetText(t, widget.id, "description", widget.description || ""),
39
39
  [t]
40
40
  );
41
- const { kind, targetId, tenantId, organizationId } = props;
41
+ const { kind, targetId, tenantId, organizationId, preserveOnTenantChange = false } = props;
42
42
  const [catalog, setCatalog] = React.useState([]);
43
43
  const [loading, setLoading] = React.useState(true);
44
44
  const [saving, setSaving] = React.useState(false);
@@ -74,10 +74,19 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
74
74
  }).filter((item) => item !== null);
75
75
  setCatalog(mapped);
76
76
  }, []);
77
- const loadRoleData = React.useCallback(async () => {
77
+ const tenantIdRef = React.useRef(tenantId);
78
+ React.useEffect(() => {
79
+ tenantIdRef.current = tenantId;
80
+ }, [tenantId]);
81
+ const organizationIdRef = React.useRef(organizationId);
82
+ React.useEffect(() => {
83
+ organizationIdRef.current = organizationId;
84
+ }, [organizationId]);
85
+ const hasMountedRef = React.useRef(false);
86
+ const loadRoleData = React.useCallback(async (forTenantId, forOrganizationId) => {
78
87
  const params = new URLSearchParams({ roleId: targetId });
79
- if (tenantId) params.set("tenantId", tenantId);
80
- if (organizationId) params.set("organizationId", organizationId);
88
+ if (forTenantId) params.set("tenantId", forTenantId);
89
+ if (forOrganizationId) params.set("organizationId", forOrganizationId);
81
90
  const data = await readApiResultOrThrow(
82
91
  `/api/dashboards/roles/widgets?${params.toString()}`,
83
92
  void 0,
@@ -89,11 +98,11 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
89
98
  setMode("override");
90
99
  setOriginalMode("override");
91
100
  setEffective(ids);
92
- }, [organizationId, targetId, tenantId]);
93
- const loadUserData = React.useCallback(async () => {
101
+ }, [targetId]);
102
+ const loadUserData = React.useCallback(async (forTenantId, forOrganizationId) => {
94
103
  const params = new URLSearchParams({ userId: targetId });
95
- if (tenantId) params.set("tenantId", tenantId);
96
- if (organizationId) params.set("organizationId", organizationId);
104
+ if (forTenantId) params.set("tenantId", forTenantId);
105
+ if (forOrganizationId) params.set("organizationId", forOrganizationId);
97
106
  const data = await readApiResultOrThrow(
98
107
  `/api/dashboards/users/widgets?${params.toString()}`,
99
108
  void 0,
@@ -105,7 +114,7 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
105
114
  setMode(data.mode || "inherit");
106
115
  setOriginalMode(data.mode || "inherit");
107
116
  setEffective(Array.isArray(data.effectiveWidgetIds) ? data.effectiveWidgetIds : []);
108
- }, [organizationId, targetId, tenantId]);
117
+ }, [targetId]);
109
118
  React.useEffect(() => {
110
119
  let cancelled = false;
111
120
  async function load() {
@@ -113,15 +122,18 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
113
122
  setError(null);
114
123
  try {
115
124
  await loadCatalog();
116
- if (kind === "role") await loadRoleData();
117
- else await loadUserData();
125
+ if (kind === "role") await loadRoleData(tenantIdRef.current, organizationIdRef.current);
126
+ else await loadUserData(tenantIdRef.current, organizationIdRef.current);
118
127
  } catch (err) {
119
128
  console.error("Failed to load widget visibility data", err);
120
129
  if (!cancelled) {
121
130
  setError(tRef.current("dashboards.widgets.error.load", "Unable to load widget configuration."));
122
131
  }
123
132
  } finally {
124
- if (!cancelled) setLoading(false);
133
+ if (!cancelled) {
134
+ setLoading(false);
135
+ hasMountedRef.current = true;
136
+ }
125
137
  }
126
138
  }
127
139
  load();
@@ -129,6 +141,23 @@ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2
129
141
  cancelled = true;
130
142
  };
131
143
  }, [kind, loadCatalog, loadRoleData, loadUserData]);
144
+ React.useEffect(() => {
145
+ if (!hasMountedRef.current) return;
146
+ if (preserveOnTenantChange) return;
147
+ let cancelled = false;
148
+ async function refetch() {
149
+ try {
150
+ if (kind === "role") await loadRoleData(tenantId, organizationId);
151
+ else await loadUserData(tenantId, organizationId);
152
+ } catch (err) {
153
+ console.error("Failed to reload widget visibility data", err);
154
+ }
155
+ }
156
+ refetch();
157
+ return () => {
158
+ cancelled = true;
159
+ };
160
+ }, [tenantId, organizationId, kind, loadRoleData, loadUserData, preserveOnTenantChange]);
132
161
  const toggle = React.useCallback((id) => {
133
162
  setSelected((prev) => prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]);
134
163
  }, []);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dashboards/components/WidgetVisibilityEditor.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup } from '@open-mercato/ui/primitives/radio'\nimport { RadioField } from '@open-mercato/ui/primitives/radio-field'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype WidgetCatalogItem = {\n id: string\n title: string\n description: string | null\n}\n\ntype RoleResponse = {\n widgetIds: string[]\n hasCustom: boolean\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype UserResponse = {\n mode: 'inherit' | 'override'\n widgetIds: string[]\n hasCustom: boolean\n effectiveWidgetIds: string[]\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype BaseProps = {\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype RoleProps = BaseProps & {\n kind: 'role'\n targetId: string\n}\n\ntype UserProps = BaseProps & {\n kind: 'user'\n targetId: string\n}\n\ntype WidgetVisibilityEditorProps = RoleProps | UserProps\n\nexport type WidgetVisibilityEditorHandle = {\n save: () => Promise<void>\n}\n\nconst EMPTY: string[] = []\n\nfunction resolveWidgetText(\n t: (key: string, fallback: string) => string,\n id: string,\n field: 'title' | 'description',\n fallback: string,\n): string {\n const key1 = `${id}.${field}`\n const result1 = t(key1, '')\n if (result1 && result1 !== key1) return result1\n\n const key2 = `dashboard.widgets.${id}.${field}`\n const result2 = t(key2, '')\n if (result2 && result2 !== key2) return result2\n\n const dotIndex = id.lastIndexOf('.')\n if (dotIndex > 0) {\n const prefix = id.slice(0, dotIndex)\n const lastPart = id.slice(dotIndex + 1)\n const key3 = `${prefix}.widgets.${lastPart}.${field}`\n const result3 = t(key3, '')\n if (result3 && result3 !== key3) return result3\n }\n\n return fallback\n}\n\nexport const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHandle, WidgetVisibilityEditorProps>(function WidgetVisibilityEditor(props, ref) {\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n\n const resolveTitle = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'title', widget.title),\n [t],\n )\n\n const resolveDescription = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'description', widget.description || ''),\n [t],\n )\n const { kind, targetId, tenantId, organizationId } = props\n const [catalog, setCatalog] = React.useState<WidgetCatalogItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const [selected, setSelected] = React.useState<string[]>(EMPTY)\n const [original, setOriginal] = React.useState<string[]>(EMPTY)\n const [mode, setMode] = React.useState<'inherit' | 'override'>('inherit')\n const [originalMode, setOriginalMode] = React.useState<'inherit' | 'override'>('inherit')\n const [effective, setEffective] = React.useState<string[]>(EMPTY)\n\n const dirty = React.useMemo(() => {\n if (kind === 'user') {\n if (mode !== originalMode) return true\n if (mode === 'override') return selected.join('|') !== original.join('|')\n return false\n }\n return selected.join('|') !== original.join('|')\n }, [kind, mode, original, originalMode, selected])\n\n const loadCatalog = React.useCallback(async () => {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/dashboards/widgets/catalog',\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const mapped = items\n .map((item: unknown): WidgetCatalogItem | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const id = typeof entry.id === 'string' ? entry.id : null\n if (!id || !id.length) return null\n const title =\n typeof entry.title === 'string' && entry.title.length ? entry.title : id\n const description =\n typeof entry.description === 'string' && entry.description.length ? entry.description : null\n return { id, title, description }\n })\n .filter((item: WidgetCatalogItem | null): item is WidgetCatalogItem => item !== null)\n setCatalog(mapped)\n }, [])\n\n const loadRoleData = React.useCallback(async () => {\n const params = new URLSearchParams({ roleId: targetId })\n if (tenantId) params.set('tenantId', tenantId)\n if (organizationId) params.set('organizationId', organizationId)\n const data = await readApiResultOrThrow<RoleResponse>(\n `/api/dashboards/roles/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode('override')\n setOriginalMode('override')\n setEffective(ids)\n }, [organizationId, targetId, tenantId])\n\n const loadUserData = React.useCallback(async () => {\n const params = new URLSearchParams({ userId: targetId })\n if (tenantId) params.set('tenantId', tenantId)\n if (organizationId) params.set('organizationId', organizationId)\n const data = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode(data.mode || 'inherit')\n setOriginalMode(data.mode || 'inherit')\n setEffective(Array.isArray(data.effectiveWidgetIds) ? data.effectiveWidgetIds : [])\n }, [organizationId, targetId, tenantId])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n await loadCatalog()\n if (kind === 'role') await loadRoleData()\n else await loadUserData()\n } catch (err) {\n console.error('Failed to load widget visibility data', err)\n if (!cancelled) {\n setError(tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.'))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [kind, loadCatalog, loadRoleData, loadUserData])\n\n const toggle = React.useCallback((id: string) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n const resetSelections = React.useCallback(() => {\n setSelected(original)\n setMode(originalMode)\n }, [original, originalMode])\n\n const save = React.useCallback(async () => {\n if (loading) return\n if (error && catalog.length === 0) return\n if (!dirty) return\n setSaving(true)\n setError(null)\n try {\n const saveError = t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.')\n if (kind === 'role') {\n const payload = {\n roleId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/roles/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n setOriginalMode('override')\n setEffective(selected)\n } else {\n const payload = {\n userId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n mode,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/users/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n if (mode === 'inherit') {\n const refreshed = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?userId=${encodeURIComponent(targetId)}`,\n undefined,\n { errorMessage: saveError },\n )\n setEffective(Array.isArray(refreshed.effectiveWidgetIds) ? refreshed.effectiveWidgetIds : [])\n } else {\n setEffective(selected)\n }\n setOriginal(selected)\n setOriginalMode(mode)\n }\n try { flash(t('dashboards.widgets.flash.saved', 'Dashboard widgets updated'), 'success') } catch {}\n } catch (err) {\n console.error('Failed to save widget visibility', err)\n setError(t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.'))\n } finally {\n setSaving(false)\n }\n }, [catalog.length, dirty, error, kind, loading, mode, organizationId, selected, t, targetId, tenantId])\n\n React.useImperativeHandle(ref, () => ({ save }), [save])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('dashboards.widgets.loading', 'Loading widget options\u2026')}\n </div>\n )\n }\n\n if (error && catalog.length === 0) {\n return <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n }\n\n return (\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n\n {kind === 'user' && (\n <RadioGroup\n className=\"flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\"\n name=\"widgetOverride\"\n value={mode}\n onValueChange={(next) => setMode(next as 'inherit' | 'override')}\n >\n <RadioField\n value=\"inherit\"\n label={t('dashboards.widgets.mode.inherit', 'Inherit from roles')}\n />\n <RadioField\n value=\"override\"\n label={t('dashboards.widgets.mode.override', 'Override for this user')}\n />\n </RadioGroup>\n )}\n\n {kind === 'user' && mode === 'inherit' && (\n <div className=\"rounded-md border border-muted bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.mode.hint', 'This user currently inherits widgets from their assigned roles. Switch to override to customize.')}\n </div>\n )}\n\n {(kind === 'role' || mode === 'override') && (\n <div className=\"space-y-3\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => toggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{resolveTitle(widget)}</div>\n {widget.description ? <div className=\"mt-1 text-xs text-muted-foreground\">{resolveDescription(widget)}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n\n {kind === 'user' && effective.length > 0 && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.effective', 'Effective widgets:')} {effective.map((id) => { const meta = catalog.find((m) => m.id === id); return meta ? resolveTitle(meta) : id }).join(', ')}\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={save} disabled={saving || !dirty}>\n {saving ? t('dashboards.widgets.saving', 'Saving\u2026') : t('dashboards.widgets.save', 'Save widgets')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={resetSelections} disabled={!dirty}>\n {t('dashboards.widgets.reset', 'Reset')}\n </Button>\n </div>\n </div>\n )\n})\n\nWidgetVisibilityEditor.displayName = 'WidgetVisibilityEditor'\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup } from '@open-mercato/ui/primitives/radio'\nimport { RadioField } from '@open-mercato/ui/primitives/radio-field'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype WidgetCatalogItem = {\n id: string\n title: string\n description: string | null\n}\n\ntype RoleResponse = {\n widgetIds: string[]\n hasCustom: boolean\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype UserResponse = {\n mode: 'inherit' | 'override'\n widgetIds: string[]\n hasCustom: boolean\n effectiveWidgetIds: string[]\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype BaseProps = {\n tenantId?: string | null\n organizationId?: string | null\n preserveOnTenantChange?: boolean\n}\n\ntype RoleProps = BaseProps & {\n kind: 'role'\n targetId: string\n}\n\ntype UserProps = BaseProps & {\n kind: 'user'\n targetId: string\n}\n\ntype WidgetVisibilityEditorProps = RoleProps | UserProps\n\nexport type WidgetVisibilityEditorHandle = {\n save: () => Promise<void>\n}\n\nconst EMPTY: string[] = []\n\nfunction resolveWidgetText(\n t: (key: string, fallback: string) => string,\n id: string,\n field: 'title' | 'description',\n fallback: string,\n): string {\n const key1 = `${id}.${field}`\n const result1 = t(key1, '')\n if (result1 && result1 !== key1) return result1\n\n const key2 = `dashboard.widgets.${id}.${field}`\n const result2 = t(key2, '')\n if (result2 && result2 !== key2) return result2\n\n const dotIndex = id.lastIndexOf('.')\n if (dotIndex > 0) {\n const prefix = id.slice(0, dotIndex)\n const lastPart = id.slice(dotIndex + 1)\n const key3 = `${prefix}.widgets.${lastPart}.${field}`\n const result3 = t(key3, '')\n if (result3 && result3 !== key3) return result3\n }\n\n return fallback\n}\n\nexport const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHandle, WidgetVisibilityEditorProps>(function WidgetVisibilityEditor(props, ref) {\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n\n const resolveTitle = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'title', widget.title),\n [t],\n )\n\n const resolveDescription = React.useCallback(\n (widget: WidgetCatalogItem) => resolveWidgetText(t, widget.id, 'description', widget.description || ''),\n [t],\n )\n const { kind, targetId, tenantId, organizationId, preserveOnTenantChange = false } = props\n const [catalog, setCatalog] = React.useState<WidgetCatalogItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const [selected, setSelected] = React.useState<string[]>(EMPTY)\n const [original, setOriginal] = React.useState<string[]>(EMPTY)\n const [mode, setMode] = React.useState<'inherit' | 'override'>('inherit')\n const [originalMode, setOriginalMode] = React.useState<'inherit' | 'override'>('inherit')\n const [effective, setEffective] = React.useState<string[]>(EMPTY)\n\n const dirty = React.useMemo(() => {\n if (kind === 'user') {\n if (mode !== originalMode) return true\n if (mode === 'override') return selected.join('|') !== original.join('|')\n return false\n }\n return selected.join('|') !== original.join('|')\n }, [kind, mode, original, originalMode, selected])\n\n const loadCatalog = React.useCallback(async () => {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/dashboards/widgets/catalog',\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const mapped = items\n .map((item: unknown): WidgetCatalogItem | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const id = typeof entry.id === 'string' ? entry.id : null\n if (!id || !id.length) return null\n const title =\n typeof entry.title === 'string' && entry.title.length ? entry.title : id\n const description =\n typeof entry.description === 'string' && entry.description.length ? entry.description : null\n return { id, title, description }\n })\n .filter((item: WidgetCatalogItem | null): item is WidgetCatalogItem => item !== null)\n setCatalog(mapped)\n }, [])\n\n const tenantIdRef = React.useRef(tenantId)\n React.useEffect(() => { tenantIdRef.current = tenantId }, [tenantId])\n const organizationIdRef = React.useRef(organizationId)\n React.useEffect(() => { organizationIdRef.current = organizationId }, [organizationId])\n const hasMountedRef = React.useRef(false)\n\n const loadRoleData = React.useCallback(async (forTenantId: string | null | undefined, forOrganizationId: string | null | undefined) => {\n const params = new URLSearchParams({ roleId: targetId })\n if (forTenantId) params.set('tenantId', forTenantId)\n if (forOrganizationId) params.set('organizationId', forOrganizationId)\n const data = await readApiResultOrThrow<RoleResponse>(\n `/api/dashboards/roles/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode('override')\n setOriginalMode('override')\n setEffective(ids)\n }, [targetId])\n\n const loadUserData = React.useCallback(async (forTenantId: string | null | undefined, forOrganizationId: string | null | undefined) => {\n const params = new URLSearchParams({ userId: targetId })\n if (forTenantId) params.set('tenantId', forTenantId)\n if (forOrganizationId) params.set('organizationId', forOrganizationId)\n const data = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?${params.toString()}`,\n undefined,\n { errorMessage: tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode(data.mode || 'inherit')\n setOriginalMode(data.mode || 'inherit')\n setEffective(Array.isArray(data.effectiveWidgetIds) ? data.effectiveWidgetIds : [])\n }, [targetId])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n await loadCatalog()\n if (kind === 'role') await loadRoleData(tenantIdRef.current, organizationIdRef.current)\n else await loadUserData(tenantIdRef.current, organizationIdRef.current)\n } catch (err) {\n console.error('Failed to load widget visibility data', err)\n if (!cancelled) {\n setError(tRef.current('dashboards.widgets.error.load', 'Unable to load widget configuration.'))\n }\n } finally {\n if (!cancelled) {\n setLoading(false)\n hasMountedRef.current = true\n }\n }\n }\n load()\n return () => { cancelled = true }\n }, [kind, loadCatalog, loadRoleData, loadUserData])\n\n React.useEffect(() => {\n if (!hasMountedRef.current) return\n if (preserveOnTenantChange) return\n let cancelled = false\n async function refetch() {\n try {\n if (kind === 'role') await loadRoleData(tenantId, organizationId)\n else await loadUserData(tenantId, organizationId)\n } catch (err) {\n console.error('Failed to reload widget visibility data', err)\n }\n }\n refetch()\n return () => { cancelled = true }\n }, [tenantId, organizationId, kind, loadRoleData, loadUserData, preserveOnTenantChange])\n\n const toggle = React.useCallback((id: string) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n const resetSelections = React.useCallback(() => {\n setSelected(original)\n setMode(originalMode)\n }, [original, originalMode])\n\n const save = React.useCallback(async () => {\n if (loading) return\n if (error && catalog.length === 0) return\n if (!dirty) return\n setSaving(true)\n setError(null)\n try {\n const saveError = t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.')\n if (kind === 'role') {\n const payload = {\n roleId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/roles/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n setOriginalMode('override')\n setEffective(selected)\n } else {\n const payload = {\n userId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n mode,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/users/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n if (mode === 'inherit') {\n const refreshed = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?userId=${encodeURIComponent(targetId)}`,\n undefined,\n { errorMessage: saveError },\n )\n setEffective(Array.isArray(refreshed.effectiveWidgetIds) ? refreshed.effectiveWidgetIds : [])\n } else {\n setEffective(selected)\n }\n setOriginal(selected)\n setOriginalMode(mode)\n }\n try { flash(t('dashboards.widgets.flash.saved', 'Dashboard widgets updated'), 'success') } catch {}\n } catch (err) {\n console.error('Failed to save widget visibility', err)\n setError(t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.'))\n } finally {\n setSaving(false)\n }\n }, [catalog.length, dirty, error, kind, loading, mode, organizationId, selected, t, targetId, tenantId])\n\n React.useImperativeHandle(ref, () => ({ save }), [save])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('dashboards.widgets.loading', 'Loading widget options\u2026')}\n </div>\n )\n }\n\n if (error && catalog.length === 0) {\n return <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n }\n\n return (\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n\n {kind === 'user' && (\n <RadioGroup\n className=\"flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\"\n name=\"widgetOverride\"\n value={mode}\n onValueChange={(next) => setMode(next as 'inherit' | 'override')}\n >\n <RadioField\n value=\"inherit\"\n label={t('dashboards.widgets.mode.inherit', 'Inherit from roles')}\n />\n <RadioField\n value=\"override\"\n label={t('dashboards.widgets.mode.override', 'Override for this user')}\n />\n </RadioGroup>\n )}\n\n {kind === 'user' && mode === 'inherit' && (\n <div className=\"rounded-md border border-muted bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.mode.hint', 'This user currently inherits widgets from their assigned roles. Switch to override to customize.')}\n </div>\n )}\n\n {(kind === 'role' || mode === 'override') && (\n <div className=\"space-y-3\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => toggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{resolveTitle(widget)}</div>\n {widget.description ? <div className=\"mt-1 text-xs text-muted-foreground\">{resolveDescription(widget)}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n\n {kind === 'user' && effective.length > 0 && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.effective', 'Effective widgets:')} {effective.map((id) => { const meta = catalog.find((m) => m.id === id); return meta ? resolveTitle(meta) : id }).join(', ')}\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={save} disabled={saving || !dirty}>\n {saving ? t('dashboards.widgets.saving', 'Saving\u2026') : t('dashboards.widgets.save', 'Save widgets')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={resetSelections} disabled={!dirty}>\n {t('dashboards.widgets.reset', 'Reset')}\n </Button>\n </div>\n </div>\n )\n})\n\nWidgetVisibilityEditor.displayName = 'WidgetVisibilityEditor'\n"],
5
+ "mappings": "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",
6
6
  "names": ["WidgetVisibilityEditor"]
7
7
  }