@open-mercato/core 0.4.2-canary-e2aeb1a7bf → 0.4.2-canary-8a04af8836
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/modules/auth/lib/setup-app.js +8 -3
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +184 -1
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/auth/lib/setup-app.ts +9 -3
- package/src/modules/configs/lib/upgrade-actions.ts +203 -3
- package/src/modules/staff/i18n/de.json +2 -0
- package/src/modules/staff/i18n/es.json +2 -0
- package/src/modules/staff/i18n/pl.json +2 -0
- package/src/modules/workflows/lib/__tests__/transition-handler.test.ts +6 -3
|
@@ -13,6 +13,7 @@ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
|
13
13
|
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
|
|
14
14
|
const DEFAULT_ROLE_NAMES = ["employee", "admin", "superadmin"];
|
|
15
15
|
const DEMO_SUPERADMIN_EMAIL = "superadmin@acme.com";
|
|
16
|
+
const DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split("@")[1] ?? "acme.com";
|
|
16
17
|
async function ensureRolesInContext(em, roleNames, tenantId) {
|
|
17
18
|
for (const name of roleNames) {
|
|
18
19
|
const existing = await em.findOne(Role, { name, tenantId });
|
|
@@ -110,11 +111,10 @@ async function setupInitialTenant(em, options) {
|
|
|
110
111
|
{ email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) }
|
|
111
112
|
];
|
|
112
113
|
if (includeDerivedUsers) {
|
|
113
|
-
const [, domain] = String(primaryUser.email).split("@");
|
|
114
114
|
const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin);
|
|
115
115
|
const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee);
|
|
116
|
-
const adminEmail = adminOverride ??
|
|
117
|
-
const employeeEmail = employeeOverride ??
|
|
116
|
+
const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`;
|
|
117
|
+
const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`;
|
|
118
118
|
const adminPassword = readEnvValue("OM_INIT_ADMIN_PASSWORD") || "secret";
|
|
119
119
|
const employeePassword = readEnvValue("OM_INIT_EMPLOYEE_PASSWORD") || "secret";
|
|
120
120
|
const adminPasswordHash = adminPassword ? await resolvePasswordHash({ email: adminEmail, password: adminPassword }) : null;
|
|
@@ -311,6 +311,11 @@ async function ensureDefaultRoleAcls(em, tenantId, modules, options = {}) {
|
|
|
311
311
|
if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin);
|
|
312
312
|
if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee);
|
|
313
313
|
}
|
|
314
|
+
console.log("\u2705 Seeded default role features", {
|
|
315
|
+
superadmin: superadminFeatures,
|
|
316
|
+
admin: adminFeatures,
|
|
317
|
+
employee: employeeFeatures
|
|
318
|
+
});
|
|
314
319
|
if (includeSuperadminRole && superadminRole) {
|
|
315
320
|
await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true });
|
|
316
321
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/lib/setup-app.ts"],
|
|
4
|
-
"sourcesContent": ["import { hash } from 'bcryptjs'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { normalizeTenantId } from './tenantAccess'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport type { Module } from '@open-mercato/shared/modules/registry'\nimport { isEncryptionDebugEnabled, isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { EncryptionMap } from '@open-mercato/core/modules/entities/data/entities'\nimport { DEFAULT_ENCRYPTION_MAPS } from '@open-mercato/core/modules/entities/lib/encryptionDefaults'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const\nconst DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'\n\nexport type EnsureRolesOptions = {\n roleNames?: string[]\n tenantId?: string | null\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string | null,\n) {\n for (const name of roleNames) {\n const existing = await em.findOne(Role, { name, tenantId })\n if (existing) continue\n if (tenantId !== null) {\n const globalRole = await em.findOne(Role, { name, tenantId: null })\n if (globalRole) {\n globalRole.tenantId = tenantId\n em.persist(globalRole)\n continue\n }\n }\n em.persist(em.create(Role, { name, tenantId, createdAt: new Date() }))\n }\n}\n\nexport async function ensureRoles(em: EntityManager, options: EnsureRolesOptions = {}) {\n const roleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const tenantId = normalizeTenantId(options.tenantId ?? null) ?? null\n await em.transactional(async (tem) => {\n await ensureRolesInContext(tem, roleNames, tenantId)\n await tem.flush()\n })\n}\n\nasync function findRoleByName(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null\n let role = await em.findOne(Role, { name, tenantId: normalizedTenant })\n if (!role && normalizedTenant !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n return role\n}\n\nasync function findRoleByNameOrFail(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role> {\n const role = await findRoleByName(em, name, tenantId)\n if (!role) throw new Error(`ROLE_NOT_FOUND:${name}`)\n return role\n}\n\ntype PrimaryUserInput = {\n email: string\n password?: string\n hashedPassword?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n confirm?: boolean\n}\n\nconst DERIVED_EMAIL_ENV = {\n admin: 'OM_INIT_ADMIN_EMAIL',\n employee: 'OM_INIT_EMPLOYEE_EMAIL',\n} as const\n\nexport type SetupInitialTenantOptions = {\n orgName: string\n primaryUser: PrimaryUserInput\n roleNames?: string[]\n includeDerivedUsers?: boolean\n failIfUserExists?: boolean\n primaryUserRoles?: string[]\n includeSuperadminRole?: boolean\n /** Optional list of enabled modules. When provided, module setup hooks are called. */\n modules?: Module[]\n}\n\nexport type SetupInitialTenantResult = {\n tenantId: string\n organizationId: string\n users: Array<{ user: User; roles: string[]; created: boolean }>\n reusedExistingUser: boolean\n}\n\nexport async function setupInitialTenant(\n em: EntityManager,\n options: SetupInitialTenantOptions,\n): Promise<SetupInitialTenantResult> {\n const {\n primaryUser,\n includeDerivedUsers = true,\n failIfUserExists = false,\n primaryUserRoles,\n includeSuperadminRole = true,\n } = options\n const primaryRolesInput = primaryUserRoles && primaryUserRoles.length ? primaryUserRoles : ['superadmin']\n const primaryRoles = includeSuperadminRole\n ? primaryRolesInput\n : primaryRolesInput.filter((role) => role !== 'superadmin')\n if (primaryRoles.length === 0) {\n throw new Error('PRIMARY_ROLES_REQUIRED')\n }\n const defaultRoleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const resolvedRoleNames = includeSuperadminRole\n ? defaultRoleNames\n : defaultRoleNames.filter((role) => role !== 'superadmin')\n const roleNames = Array.from(new Set([...resolvedRoleNames, ...primaryRoles]))\n\n const mainEmail = primaryUser.email\n const existingUser = await em.findOne(User, { email: mainEmail })\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\n }\n\n let tenantId: string | undefined\n let organizationId: string | undefined\n let reusedExistingUser = false\n const userSnapshots: Array<{ user: User; roles: string[]; created: boolean }> = []\n\n await em.transactional(async (tem) => {\n if (!existingUser) return\n reusedExistingUser = true\n tenantId = existingUser.tenantId ? String(existingUser.tenantId) : undefined\n organizationId = existingUser.organizationId ? String(existingUser.organizationId) : undefined\n const roleTenantId = normalizeTenantId(existingUser.tenantId ?? null) ?? null\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n const requiredRoleSet = new Set([...roleNames, ...primaryRoles])\n const links = await findWithDecryption(\n tem,\n UserRole,\n { user: existingUser },\n { populate: ['role'] },\n { tenantId: roleTenantId, organizationId: null },\n )\n const currentRoles = new Set(links.map((link) => link.role.name))\n for (const roleName of requiredRoleSet) {\n if (!currentRoles.has(roleName)) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n tem.persist(tem.create(UserRole, { user: existingUser, role, createdAt: new Date() }))\n }\n }\n await tem.flush()\n const roles = Array.from(new Set([...currentRoles, ...roleNames]))\n userSnapshots.push({ user: existingUser, roles, created: false })\n })\n\n if (!existingUser) {\n const baseUsers: Array<{\n email: string\n roles: string[]\n name?: string | null\n passwordHash?: string | null\n }> = [\n { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },\n ]\n if (includeDerivedUsers) {\n const [, domain] = String(primaryUser.email).split('@')\n const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)\n const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)\n const adminEmail = adminOverride ?? (domain ? `admin@${domain}` : '')\n const employeeEmail = employeeOverride ?? (domain ? `employee@${domain}` : '')\n const adminPassword = readEnvValue('OM_INIT_ADMIN_PASSWORD') || 'secret'\n const employeePassword = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD') || 'secret'\n const adminPasswordHash = adminPassword ? await resolvePasswordHash({ email: adminEmail, password: adminPassword }) : null\n const employeePasswordHash = employeePassword\n ? await resolvePasswordHash({ email: employeeEmail, password: employeePassword })\n : null\n addUniqueBaseUser(baseUsers, { email: adminEmail, roles: ['admin'], passwordHash: adminPasswordHash })\n addUniqueBaseUser(baseUsers, { email: employeeEmail, roles: ['employee'], passwordHash: employeePasswordHash })\n }\n const passwordHash = await resolvePasswordHash(primaryUser)\n\n await em.transactional(async (tem) => {\n const tenant = tem.create(Tenant, {\n name: `${options.orgName} Tenant`,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(tenant)\n await tem.flush()\n\n const organization = tem.create(Organization, {\n name: options.orgName,\n tenant,\n isActive: true,\n depth: 0,\n ancestorIds: [],\n childIds: [],\n descendantIds: [],\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(organization)\n await tem.flush()\n\n tenantId = String(tenant.id)\n organizationId = String(organization.id)\n const roleTenantId = tenantId\n\n if (isTenantDataEncryptionEnabled()) {\n try {\n const kms = createKmsService()\n if (kms.isHealthy()) {\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] provisioning tenant DEK', { tenantId: String(tenant.id) })\n }\n await kms.createTenantDek(String(tenant.id))\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] created tenant DEK during setup', { tenantId: String(tenant.id) })\n }\n } else {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] KMS not healthy, skipping tenant DEK creation', { tenantId: String(tenant.id) })\n }\n }\n } catch (err) {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] Failed to create tenant DEK', err)\n }\n }\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n if (isTenantDataEncryptionEnabled()) {\n for (const spec of DEFAULT_ENCRYPTION_MAPS) {\n const existing = await tem.findOne(EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null })\n if (!existing) {\n tem.persist(tem.create(EncryptionMap, {\n entityId: spec.entityId,\n tenantId: tenant.id,\n organizationId: organization.id,\n fieldsJson: spec.fields,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n }))\n } else {\n existing.fieldsJson = spec.fields\n existing.isActive = true\n }\n }\n await tem.flush()\n }\n })\n\n await em.transactional(async (tem) => {\n if (!tenantId || !organizationId) return\n const roleTenantId = tenantId\n const encryptionService = isTenantDataEncryptionEnabled()\n ? new TenantDataEncryptionService(tem as any, { kms: createKmsService() })\n : null\n if (encryptionService) {\n await encryptionService.invalidateMap('auth:user', String(tenantId), String(organizationId))\n await encryptionService.invalidateMap('auth:user', String(tenantId), null)\n }\n\n for (const base of baseUsers) {\n const resolvedPasswordHash = base.passwordHash ?? passwordHash\n let user = await tem.findOne(User, { email: base.email })\n const confirm = primaryUser.confirm ?? true\n const encryptedPayload = encryptionService\n ? await encryptionService.encryptEntityPayload('auth:user', { email: base.email }, tenantId, organizationId)\n : { email: base.email, emailHash: computeEmailHash(base.email) }\n if (user) {\n user.passwordHash = resolvedPasswordHash\n user.organizationId = organizationId\n user.tenantId = tenantId\n if (isTenantDataEncryptionEnabled()) {\n user.email = encryptedPayload.email as any\n user.emailHash = (encryptedPayload as any).emailHash ?? computeEmailHash(base.email)\n }\n if (base.name) user.name = base.name\n if (confirm) user.isConfirmed = true\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: false })\n } else {\n user = tem.create(User, {\n email: (encryptedPayload as any).email ?? base.email,\n emailHash: isTenantDataEncryptionEnabled() ? (encryptedPayload as any).emailHash ?? computeEmailHash(base.email) : undefined,\n passwordHash: resolvedPasswordHash,\n organizationId,\n tenantId,\n name: base.name ?? undefined,\n isConfirmed: confirm,\n createdAt: new Date(),\n })\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: true })\n }\n await tem.flush()\n for (const roleName of base.roles) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n const existingLink = await tem.findOne(UserRole, { user, role })\n if (!existingLink) tem.persist(tem.create(UserRole, { user, role, createdAt: new Date() }))\n }\n await tem.flush()\n }\n })\n }\n\n if (!tenantId || !organizationId) {\n throw new Error('SETUP_FAILED')\n }\n\n if (!reusedExistingUser) {\n await rebuildHierarchyForTenant(em, tenantId)\n }\n\n const resolvedModules = options.modules ?? tryGetModules()\n await ensureDefaultRoleAcls(em, tenantId, resolvedModules, { includeSuperadminRole })\n await deactivateDemoSuperAdminIfSelfOnboardingEnabled(em)\n\n // Call module onTenantCreated hooks\n for (const mod of resolvedModules) {\n if (mod.setup?.onTenantCreated) {\n await mod.setup.onTenantCreated({ em, tenantId, organizationId })\n }\n }\n\n return {\n tenantId,\n organizationId,\n users: userSnapshots,\n reusedExistingUser,\n }\n}\n\nfunction resolvePrimaryName(input: PrimaryUserInput): string | null {\n if (input.displayName && input.displayName.trim()) return input.displayName.trim()\n const parts = [input.firstName, input.lastName].map((value) => value?.trim()).filter(Boolean)\n if (parts.length) return parts.join(' ')\n return null\n}\n\nfunction readEnvValue(key: string): string | undefined {\n const value = process.env[key]\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nfunction addUniqueBaseUser(\n baseUsers: Array<{ email: string; roles: string[]; name?: string | null; passwordHash?: string | null }>,\n entry: { email: string; roles: string[]; name?: string | null; passwordHash?: string | null },\n) {\n if (!entry.email) return\n const normalized = entry.email.toLowerCase()\n if (baseUsers.some((user) => user.email.toLowerCase() === normalized)) return\n baseUsers.push(entry)\n}\n\nfunction isDemoModeEnabled(): boolean {\n const parsed = parseBooleanToken(process.env.DEMO_MODE ?? '')\n return parsed === false ? false : true\n}\n\nfunction shouldKeepDemoSuperadminDuringInit(): boolean {\n if (process.env.OM_INIT_FLOW !== 'true') return false\n if (!readEnvValue('OM_INIT_SUPERADMIN_EMAIL')) return false\n return isDemoModeEnabled()\n}\n\nasync function resolvePasswordHash(input: PrimaryUserInput): Promise<string | null> {\n if (typeof input.hashedPassword === 'string') return input.hashedPassword\n if (input.password) return hash(input.password, 10)\n return null\n}\n\nasync function ensureDefaultRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules: Module[],\n options: { includeSuperadminRole?: boolean } = {},\n) {\n const includeSuperadminRole = options.includeSuperadminRole ?? true\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const superadminRole = includeSuperadminRole ? await findRoleByName(em, 'superadmin', roleTenantId) : null\n const adminRole = await findRoleByName(em, 'admin', roleTenantId)\n const employeeRole = await findRoleByName(em, 'employee', roleTenantId)\n\n // Merge features from all enabled modules' setup configs\n const superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: string[] = []\n\n for (const mod of modules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n if (roleFeatures.superadmin) superadminFeatures.push(...roleFeatures.superadmin)\n if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin)\n if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)\n }\n\n if (includeSuperadminRole && superadminRole) {\n await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })\n }\n if (adminRole) {\n await ensureRoleAclFor(em, adminRole, tenantId, adminFeatures)\n }\n if (employeeRole) {\n await ensureRoleAclFor(em, employeeRole, tenantId, employeeFeatures)\n }\n}\n\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await em.findOne(RoleAcl, { role, tenantId })\n if (!existing) {\n const acl = em.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: !!options.isSuperAdmin,\n createdAt: new Date(),\n })\n await em.persistAndFlush(acl)\n return\n }\n const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []\n const merged = Array.from(new Set([...currentFeatures, ...features]))\n const changed =\n merged.length !== currentFeatures.length ||\n merged.some((value, index) => value !== currentFeatures[index])\n if (changed) existing.featuresJson = merged\n if (options.isSuperAdmin && !existing.isSuperAdmin) {\n existing.isSuperAdmin = true\n }\n if (changed || options.isSuperAdmin) {\n await em.persistAndFlush(existing)\n }\n}\n\nasync function deactivateDemoSuperAdminIfSelfOnboardingEnabled(em: EntityManager) {\n if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== 'true') return\n if (shouldKeepDemoSuperadminDuringInit()) return\n try {\n const user = await em.findOne(User, { email: DEMO_SUPERADMIN_EMAIL })\n if (!user) return\n let dirty = false\n if (user.passwordHash) {\n user.passwordHash = null\n dirty = true\n }\n if (user.isConfirmed !== false) {\n user.isConfirmed = false\n dirty = true\n }\n if (dirty) {\n await em.persistAndFlush(user)\n }\n } catch (error) {\n console.error('[auth.setup] failed to deactivate demo superadmin user', error)\n }\n}\n\n/** Try to get modules from runtime registry; returns empty array if not yet registered. */\nfunction tryGetModules(): Module[] {\n try {\n const { getModules } = require('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { hash } from 'bcryptjs'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Role, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { normalizeTenantId } from './tenantAccess'\nimport { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'\nimport type { Module } from '@open-mercato/shared/modules/registry'\nimport { isEncryptionDebugEnabled, isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { EncryptionMap } from '@open-mercato/core/modules/entities/data/entities'\nimport { DEFAULT_ENCRYPTION_MAPS } from '@open-mercato/core/modules/entities/lib/encryptionDefaults'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const\nconst DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'\nconst DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split('@')[1] ?? 'acme.com'\n\nexport type EnsureRolesOptions = {\n roleNames?: string[]\n tenantId?: string | null\n}\n\nasync function ensureRolesInContext(\n em: EntityManager,\n roleNames: string[],\n tenantId: string | null,\n) {\n for (const name of roleNames) {\n const existing = await em.findOne(Role, { name, tenantId })\n if (existing) continue\n if (tenantId !== null) {\n const globalRole = await em.findOne(Role, { name, tenantId: null })\n if (globalRole) {\n globalRole.tenantId = tenantId\n em.persist(globalRole)\n continue\n }\n }\n em.persist(em.create(Role, { name, tenantId, createdAt: new Date() }))\n }\n}\n\nexport async function ensureRoles(em: EntityManager, options: EnsureRolesOptions = {}) {\n const roleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const tenantId = normalizeTenantId(options.tenantId ?? null) ?? null\n await em.transactional(async (tem) => {\n await ensureRolesInContext(tem, roleNames, tenantId)\n await tem.flush()\n })\n}\n\nasync function findRoleByName(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null\n let role = await em.findOne(Role, { name, tenantId: normalizedTenant })\n if (!role && normalizedTenant !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n return role\n}\n\nasync function findRoleByNameOrFail(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role> {\n const role = await findRoleByName(em, name, tenantId)\n if (!role) throw new Error(`ROLE_NOT_FOUND:${name}`)\n return role\n}\n\ntype PrimaryUserInput = {\n email: string\n password?: string\n hashedPassword?: string | null\n firstName?: string | null\n lastName?: string | null\n displayName?: string | null\n confirm?: boolean\n}\n\nconst DERIVED_EMAIL_ENV = {\n admin: 'OM_INIT_ADMIN_EMAIL',\n employee: 'OM_INIT_EMPLOYEE_EMAIL',\n} as const\n\nexport type SetupInitialTenantOptions = {\n orgName: string\n primaryUser: PrimaryUserInput\n roleNames?: string[]\n includeDerivedUsers?: boolean\n failIfUserExists?: boolean\n primaryUserRoles?: string[]\n includeSuperadminRole?: boolean\n /** Optional list of enabled modules. When provided, module setup hooks are called. */\n modules?: Module[]\n}\n\nexport type SetupInitialTenantResult = {\n tenantId: string\n organizationId: string\n users: Array<{ user: User; roles: string[]; created: boolean }>\n reusedExistingUser: boolean\n}\n\nexport async function setupInitialTenant(\n em: EntityManager,\n options: SetupInitialTenantOptions,\n): Promise<SetupInitialTenantResult> {\n const {\n primaryUser,\n includeDerivedUsers = true,\n failIfUserExists = false,\n primaryUserRoles,\n includeSuperadminRole = true,\n } = options\n const primaryRolesInput = primaryUserRoles && primaryUserRoles.length ? primaryUserRoles : ['superadmin']\n const primaryRoles = includeSuperadminRole\n ? primaryRolesInput\n : primaryRolesInput.filter((role) => role !== 'superadmin')\n if (primaryRoles.length === 0) {\n throw new Error('PRIMARY_ROLES_REQUIRED')\n }\n const defaultRoleNames = options.roleNames ?? [...DEFAULT_ROLE_NAMES]\n const resolvedRoleNames = includeSuperadminRole\n ? defaultRoleNames\n : defaultRoleNames.filter((role) => role !== 'superadmin')\n const roleNames = Array.from(new Set([...resolvedRoleNames, ...primaryRoles]))\n\n const mainEmail = primaryUser.email\n const existingUser = await em.findOne(User, { email: mainEmail })\n if (existingUser && failIfUserExists) {\n throw new Error('USER_EXISTS')\n }\n\n let tenantId: string | undefined\n let organizationId: string | undefined\n let reusedExistingUser = false\n const userSnapshots: Array<{ user: User; roles: string[]; created: boolean }> = []\n\n await em.transactional(async (tem) => {\n if (!existingUser) return\n reusedExistingUser = true\n tenantId = existingUser.tenantId ? String(existingUser.tenantId) : undefined\n organizationId = existingUser.organizationId ? String(existingUser.organizationId) : undefined\n const roleTenantId = normalizeTenantId(existingUser.tenantId ?? null) ?? null\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n const requiredRoleSet = new Set([...roleNames, ...primaryRoles])\n const links = await findWithDecryption(\n tem,\n UserRole,\n { user: existingUser },\n { populate: ['role'] },\n { tenantId: roleTenantId, organizationId: null },\n )\n const currentRoles = new Set(links.map((link) => link.role.name))\n for (const roleName of requiredRoleSet) {\n if (!currentRoles.has(roleName)) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n tem.persist(tem.create(UserRole, { user: existingUser, role, createdAt: new Date() }))\n }\n }\n await tem.flush()\n const roles = Array.from(new Set([...currentRoles, ...roleNames]))\n userSnapshots.push({ user: existingUser, roles, created: false })\n })\n\n if (!existingUser) {\n const baseUsers: Array<{\n email: string\n roles: string[]\n name?: string | null\n passwordHash?: string | null\n }> = [\n { email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },\n ]\n if (includeDerivedUsers) {\n const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)\n const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)\n const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`\n const adminPassword = readEnvValue('OM_INIT_ADMIN_PASSWORD') || 'secret'\n const employeePassword = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD') || 'secret'\n const adminPasswordHash = adminPassword ? await resolvePasswordHash({ email: adminEmail, password: adminPassword }) : null\n const employeePasswordHash = employeePassword\n ? await resolvePasswordHash({ email: employeeEmail, password: employeePassword })\n : null\n addUniqueBaseUser(baseUsers, { email: adminEmail, roles: ['admin'], passwordHash: adminPasswordHash })\n addUniqueBaseUser(baseUsers, { email: employeeEmail, roles: ['employee'], passwordHash: employeePasswordHash })\n }\n const passwordHash = await resolvePasswordHash(primaryUser)\n\n await em.transactional(async (tem) => {\n const tenant = tem.create(Tenant, {\n name: `${options.orgName} Tenant`,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(tenant)\n await tem.flush()\n\n const organization = tem.create(Organization, {\n name: options.orgName,\n tenant,\n isActive: true,\n depth: 0,\n ancestorIds: [],\n childIds: [],\n descendantIds: [],\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n tem.persist(organization)\n await tem.flush()\n\n tenantId = String(tenant.id)\n organizationId = String(organization.id)\n const roleTenantId = tenantId\n\n if (isTenantDataEncryptionEnabled()) {\n try {\n const kms = createKmsService()\n if (kms.isHealthy()) {\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] provisioning tenant DEK', { tenantId: String(tenant.id) })\n }\n await kms.createTenantDek(String(tenant.id))\n if (isEncryptionDebugEnabled()) {\n console.info('\uD83D\uDD11 [encryption][setup] created tenant DEK during setup', { tenantId: String(tenant.id) })\n }\n } else {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] KMS not healthy, skipping tenant DEK creation', { tenantId: String(tenant.id) })\n }\n }\n } catch (err) {\n if (isEncryptionDebugEnabled()) {\n console.warn('\u26A0\uFE0F [encryption][setup] Failed to create tenant DEK', err)\n }\n }\n }\n\n await ensureRolesInContext(tem, roleNames, roleTenantId)\n await tem.flush()\n\n if (isTenantDataEncryptionEnabled()) {\n for (const spec of DEFAULT_ENCRYPTION_MAPS) {\n const existing = await tem.findOne(EncryptionMap, { entityId: spec.entityId, tenantId: tenant.id, organizationId: organization.id, deletedAt: null })\n if (!existing) {\n tem.persist(tem.create(EncryptionMap, {\n entityId: spec.entityId,\n tenantId: tenant.id,\n organizationId: organization.id,\n fieldsJson: spec.fields,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n }))\n } else {\n existing.fieldsJson = spec.fields\n existing.isActive = true\n }\n }\n await tem.flush()\n }\n })\n\n await em.transactional(async (tem) => {\n if (!tenantId || !organizationId) return\n const roleTenantId = tenantId\n const encryptionService = isTenantDataEncryptionEnabled()\n ? new TenantDataEncryptionService(tem as any, { kms: createKmsService() })\n : null\n if (encryptionService) {\n await encryptionService.invalidateMap('auth:user', String(tenantId), String(organizationId))\n await encryptionService.invalidateMap('auth:user', String(tenantId), null)\n }\n\n for (const base of baseUsers) {\n const resolvedPasswordHash = base.passwordHash ?? passwordHash\n let user = await tem.findOne(User, { email: base.email })\n const confirm = primaryUser.confirm ?? true\n const encryptedPayload = encryptionService\n ? await encryptionService.encryptEntityPayload('auth:user', { email: base.email }, tenantId, organizationId)\n : { email: base.email, emailHash: computeEmailHash(base.email) }\n if (user) {\n user.passwordHash = resolvedPasswordHash\n user.organizationId = organizationId\n user.tenantId = tenantId\n if (isTenantDataEncryptionEnabled()) {\n user.email = encryptedPayload.email as any\n user.emailHash = (encryptedPayload as any).emailHash ?? computeEmailHash(base.email)\n }\n if (base.name) user.name = base.name\n if (confirm) user.isConfirmed = true\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: false })\n } else {\n user = tem.create(User, {\n email: (encryptedPayload as any).email ?? base.email,\n emailHash: isTenantDataEncryptionEnabled() ? (encryptedPayload as any).emailHash ?? computeEmailHash(base.email) : undefined,\n passwordHash: resolvedPasswordHash,\n organizationId,\n tenantId,\n name: base.name ?? undefined,\n isConfirmed: confirm,\n createdAt: new Date(),\n })\n tem.persist(user)\n userSnapshots.push({ user, roles: base.roles, created: true })\n }\n await tem.flush()\n for (const roleName of base.roles) {\n const role = await findRoleByNameOrFail(tem, roleName, roleTenantId)\n const existingLink = await tem.findOne(UserRole, { user, role })\n if (!existingLink) tem.persist(tem.create(UserRole, { user, role, createdAt: new Date() }))\n }\n await tem.flush()\n }\n })\n }\n\n if (!tenantId || !organizationId) {\n throw new Error('SETUP_FAILED')\n }\n\n if (!reusedExistingUser) {\n await rebuildHierarchyForTenant(em, tenantId)\n }\n\n const resolvedModules = options.modules ?? tryGetModules()\n await ensureDefaultRoleAcls(em, tenantId, resolvedModules, { includeSuperadminRole })\n await deactivateDemoSuperAdminIfSelfOnboardingEnabled(em)\n\n // Call module onTenantCreated hooks\n for (const mod of resolvedModules) {\n if (mod.setup?.onTenantCreated) {\n await mod.setup.onTenantCreated({ em, tenantId, organizationId })\n }\n }\n\n return {\n tenantId,\n organizationId,\n users: userSnapshots,\n reusedExistingUser,\n }\n}\n\nfunction resolvePrimaryName(input: PrimaryUserInput): string | null {\n if (input.displayName && input.displayName.trim()) return input.displayName.trim()\n const parts = [input.firstName, input.lastName].map((value) => value?.trim()).filter(Boolean)\n if (parts.length) return parts.join(' ')\n return null\n}\n\nfunction readEnvValue(key: string): string | undefined {\n const value = process.env[key]\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nfunction addUniqueBaseUser(\n baseUsers: Array<{ email: string; roles: string[]; name?: string | null; passwordHash?: string | null }>,\n entry: { email: string; roles: string[]; name?: string | null; passwordHash?: string | null },\n) {\n if (!entry.email) return\n const normalized = entry.email.toLowerCase()\n if (baseUsers.some((user) => user.email.toLowerCase() === normalized)) return\n baseUsers.push(entry)\n}\n\nfunction isDemoModeEnabled(): boolean {\n const parsed = parseBooleanToken(process.env.DEMO_MODE ?? '')\n return parsed === false ? false : true\n}\n\nfunction shouldKeepDemoSuperadminDuringInit(): boolean {\n if (process.env.OM_INIT_FLOW !== 'true') return false\n if (!readEnvValue('OM_INIT_SUPERADMIN_EMAIL')) return false\n return isDemoModeEnabled()\n}\n\nasync function resolvePasswordHash(input: PrimaryUserInput): Promise<string | null> {\n if (typeof input.hashedPassword === 'string') return input.hashedPassword\n if (input.password) return hash(input.password, 10)\n return null\n}\n\nasync function ensureDefaultRoleAcls(\n em: EntityManager,\n tenantId: string,\n modules: Module[],\n options: { includeSuperadminRole?: boolean } = {},\n) {\n const includeSuperadminRole = options.includeSuperadminRole ?? true\n const roleTenantId = normalizeTenantId(tenantId) ?? null\n const superadminRole = includeSuperadminRole ? await findRoleByName(em, 'superadmin', roleTenantId) : null\n const adminRole = await findRoleByName(em, 'admin', roleTenantId)\n const employeeRole = await findRoleByName(em, 'employee', roleTenantId)\n\n // Merge features from all enabled modules' setup configs\n const superadminFeatures: string[] = []\n const adminFeatures: string[] = []\n const employeeFeatures: string[] = []\n\n for (const mod of modules) {\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (!roleFeatures) continue\n if (roleFeatures.superadmin) superadminFeatures.push(...roleFeatures.superadmin)\n if (roleFeatures.admin) adminFeatures.push(...roleFeatures.admin)\n if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)\n }\n\n console.log('\u2705 Seeded default role features', {\n superadmin: superadminFeatures,\n admin: adminFeatures,\n employee: employeeFeatures,\n })\n\n if (includeSuperadminRole && superadminRole) {\n await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })\n }\n if (adminRole) {\n await ensureRoleAclFor(em, adminRole, tenantId, adminFeatures)\n }\n if (employeeRole) {\n await ensureRoleAclFor(em, employeeRole, tenantId, employeeFeatures)\n }\n}\n\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await em.findOne(RoleAcl, { role, tenantId })\n if (!existing) {\n const acl = em.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: !!options.isSuperAdmin,\n createdAt: new Date(),\n })\n await em.persistAndFlush(acl)\n return\n }\n const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []\n const merged = Array.from(new Set([...currentFeatures, ...features]))\n const changed =\n merged.length !== currentFeatures.length ||\n merged.some((value, index) => value !== currentFeatures[index])\n if (changed) existing.featuresJson = merged\n if (options.isSuperAdmin && !existing.isSuperAdmin) {\n existing.isSuperAdmin = true\n }\n if (changed || options.isSuperAdmin) {\n await em.persistAndFlush(existing)\n }\n}\n\nasync function deactivateDemoSuperAdminIfSelfOnboardingEnabled(em: EntityManager) {\n if (process.env.SELF_SERVICE_ONBOARDING_ENABLED !== 'true') return\n if (shouldKeepDemoSuperadminDuringInit()) return\n try {\n const user = await em.findOne(User, { email: DEMO_SUPERADMIN_EMAIL })\n if (!user) return\n let dirty = false\n if (user.passwordHash) {\n user.passwordHash = null\n dirty = true\n }\n if (user.isConfirmed !== false) {\n user.isConfirmed = false\n dirty = true\n }\n if (dirty) {\n await em.persistAndFlush(user)\n }\n } catch (error) {\n console.error('[auth.setup] failed to deactivate demo superadmin user', error)\n }\n}\n\n/** Try to get modules from runtime registry; returns empty array if not yet registered. */\nfunction tryGetModules(): Module[] {\n try {\n const { getModules } = require('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import * as semver from "semver";
|
|
2
|
+
import { getModules } from "@open-mercato/shared/lib/modules/registry";
|
|
3
|
+
import { Role, RoleAcl } from "@open-mercato/core/modules/auth/data/entities";
|
|
4
|
+
import { normalizeTenantId } from "@open-mercato/core/modules/auth/lib/tenantAccess";
|
|
2
5
|
function compareVersions(a, b) {
|
|
3
6
|
const cleanA = semver.valid(semver.coerce(a));
|
|
4
7
|
const cleanB = semver.valid(semver.coerce(b));
|
|
@@ -10,7 +13,187 @@ function compareVersions(a, b) {
|
|
|
10
13
|
}
|
|
11
14
|
return semver.compare(cleanA, cleanB);
|
|
12
15
|
}
|
|
13
|
-
|
|
16
|
+
function collectRoleFeatures(modules, moduleIds) {
|
|
17
|
+
const result = {
|
|
18
|
+
superadmin: [],
|
|
19
|
+
admin: [],
|
|
20
|
+
employee: []
|
|
21
|
+
};
|
|
22
|
+
const targetIds = new Set(moduleIds);
|
|
23
|
+
for (const mod of modules) {
|
|
24
|
+
if (!targetIds.has(mod.id)) continue;
|
|
25
|
+
const roleFeatures = mod.setup?.defaultRoleFeatures;
|
|
26
|
+
if (roleFeatures?.superadmin) result.superadmin.push(...roleFeatures.superadmin);
|
|
27
|
+
if (roleFeatures?.admin) result.admin.push(...roleFeatures.admin);
|
|
28
|
+
if (roleFeatures?.employee) result.employee.push(...roleFeatures.employee);
|
|
29
|
+
}
|
|
30
|
+
result.superadmin = Array.from(new Set(result.superadmin));
|
|
31
|
+
result.admin = Array.from(new Set(result.admin));
|
|
32
|
+
result.employee = Array.from(new Set(result.employee));
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
async function findRoleByName(em, name, tenantId) {
|
|
36
|
+
const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null;
|
|
37
|
+
let role = await em.findOne(Role, { name, tenantId: normalizedTenant });
|
|
38
|
+
if (!role && normalizedTenant !== null) {
|
|
39
|
+
role = await em.findOne(Role, { name, tenantId: null });
|
|
40
|
+
}
|
|
41
|
+
return role;
|
|
42
|
+
}
|
|
43
|
+
async function ensureRoleAclFor(em, role, tenantId, features, options = {}) {
|
|
44
|
+
const existing = await em.findOne(RoleAcl, { role, tenantId });
|
|
45
|
+
if (!existing) {
|
|
46
|
+
const acl = em.create(RoleAcl, {
|
|
47
|
+
role,
|
|
48
|
+
tenantId,
|
|
49
|
+
featuresJson: features,
|
|
50
|
+
isSuperAdmin: !!options.isSuperAdmin,
|
|
51
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
52
|
+
});
|
|
53
|
+
await em.persistAndFlush(acl);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : [];
|
|
57
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...currentFeatures, ...features]));
|
|
58
|
+
const changed = merged.length !== currentFeatures.length || merged.some((value, index) => value !== currentFeatures[index]);
|
|
59
|
+
if (changed) existing.featuresJson = merged;
|
|
60
|
+
if (options.isSuperAdmin && !existing.isSuperAdmin) {
|
|
61
|
+
existing.isSuperAdmin = true;
|
|
62
|
+
}
|
|
63
|
+
if (changed || options.isSuperAdmin) {
|
|
64
|
+
await em.persistAndFlush(existing);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function ensureRoleAclsForModules(em, tenantId, moduleIds) {
|
|
68
|
+
const modules = getModules();
|
|
69
|
+
const roleFeatures = collectRoleFeatures(modules, moduleIds);
|
|
70
|
+
if (!roleFeatures.superadmin.length && !roleFeatures.admin.length && !roleFeatures.employee.length) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const normalizedTenant = normalizeTenantId(tenantId) ?? null;
|
|
74
|
+
const [superadminRole, adminRole, employeeRole] = await Promise.all([
|
|
75
|
+
findRoleByName(em, "superadmin", normalizedTenant),
|
|
76
|
+
findRoleByName(em, "admin", normalizedTenant),
|
|
77
|
+
findRoleByName(em, "employee", normalizedTenant)
|
|
78
|
+
]);
|
|
79
|
+
if (superadminRole && roleFeatures.superadmin.length) {
|
|
80
|
+
await ensureRoleAclFor(em, superadminRole, tenantId, roleFeatures.superadmin, { isSuperAdmin: true });
|
|
81
|
+
}
|
|
82
|
+
if (adminRole && roleFeatures.admin.length) {
|
|
83
|
+
await ensureRoleAclFor(em, adminRole, tenantId, roleFeatures.admin);
|
|
84
|
+
}
|
|
85
|
+
if (employeeRole && roleFeatures.employee.length) {
|
|
86
|
+
await ensureRoleAclFor(em, employeeRole, tenantId, roleFeatures.employee);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function safeImport(label, loader) {
|
|
90
|
+
try {
|
|
91
|
+
return await loader();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn(`[upgrade-actions] Skipping ${label} because module import failed.`, error);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const upgradeActions = [
|
|
98
|
+
{
|
|
99
|
+
id: "configs.upgrades.catalog.examples",
|
|
100
|
+
version: "0.3.4",
|
|
101
|
+
messageKey: "upgrades.v034.message",
|
|
102
|
+
ctaKey: "upgrades.v034.cta",
|
|
103
|
+
successKey: "upgrades.v034.success",
|
|
104
|
+
loadingKey: "upgrades.v034.loading",
|
|
105
|
+
async run(ctx) {
|
|
106
|
+
const catalogSeeds = await safeImport(
|
|
107
|
+
"catalog examples",
|
|
108
|
+
() => import("@open-mercato/core/modules/catalog/lib/seeds")
|
|
109
|
+
);
|
|
110
|
+
if (!catalogSeeds?.installExampleCatalogData) return;
|
|
111
|
+
await catalogSeeds.installExampleCatalogData(
|
|
112
|
+
ctx.container,
|
|
113
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
114
|
+
ctx.em
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "configs.upgrades.sales.examples",
|
|
120
|
+
version: "0.3.6",
|
|
121
|
+
messageKey: "upgrades.v036.message",
|
|
122
|
+
ctaKey: "upgrades.v036.cta",
|
|
123
|
+
successKey: "upgrades.v036.success",
|
|
124
|
+
loadingKey: "upgrades.v036.loading",
|
|
125
|
+
async run(ctx) {
|
|
126
|
+
const salesSeeds = await safeImport(
|
|
127
|
+
"sales examples",
|
|
128
|
+
() => import("@open-mercato/core/modules/sales/seed/examples")
|
|
129
|
+
);
|
|
130
|
+
if (!salesSeeds?.seedSalesExamples) return;
|
|
131
|
+
await salesSeeds.seedSalesExamples(ctx.em, ctx.container, {
|
|
132
|
+
tenantId: ctx.tenantId,
|
|
133
|
+
organizationId: ctx.organizationId
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "configs.upgrades.examples.currencies_workflows",
|
|
139
|
+
version: "0.3.13",
|
|
140
|
+
messageKey: "upgrades.v0313.message",
|
|
141
|
+
ctaKey: "upgrades.v0313.cta",
|
|
142
|
+
successKey: "upgrades.v0313.success",
|
|
143
|
+
loadingKey: "upgrades.v0313.loading",
|
|
144
|
+
async run(ctx) {
|
|
145
|
+
const currenciesSeeds = await safeImport(
|
|
146
|
+
"currencies examples",
|
|
147
|
+
() => import("@open-mercato/core/modules/currencies/lib/seeds")
|
|
148
|
+
);
|
|
149
|
+
const workflowsSeeds = await safeImport(
|
|
150
|
+
"workflows examples",
|
|
151
|
+
() => import("@open-mercato/core/modules/workflows/lib/seeds")
|
|
152
|
+
);
|
|
153
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
154
|
+
if (currenciesSeeds?.seedExampleCurrencies) {
|
|
155
|
+
await currenciesSeeds.seedExampleCurrencies(ctx.em, scope);
|
|
156
|
+
}
|
|
157
|
+
if (workflowsSeeds?.seedExampleWorkflows) {
|
|
158
|
+
await workflowsSeeds.seedExampleWorkflows(ctx.em, scope);
|
|
159
|
+
}
|
|
160
|
+
await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ["currencies", "workflows"]);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "configs.upgrades.examples.planner_staff_resources",
|
|
165
|
+
version: "0.4.1",
|
|
166
|
+
messageKey: "upgrades.v041.message",
|
|
167
|
+
ctaKey: "upgrades.v041.cta",
|
|
168
|
+
successKey: "upgrades.v041.success",
|
|
169
|
+
loadingKey: "upgrades.v041.loading",
|
|
170
|
+
async run(ctx) {
|
|
171
|
+
const plannerSeeds = await safeImport(
|
|
172
|
+
"planner examples",
|
|
173
|
+
() => import("@open-mercato/core/modules/planner/lib/seeds")
|
|
174
|
+
);
|
|
175
|
+
const staffSeeds = await safeImport(
|
|
176
|
+
"staff examples",
|
|
177
|
+
() => import("@open-mercato/core/modules/staff/lib/seeds")
|
|
178
|
+
);
|
|
179
|
+
const resourcesSeeds = await safeImport(
|
|
180
|
+
"resources examples",
|
|
181
|
+
() => import("@open-mercato/core/modules/resources/lib/seeds")
|
|
182
|
+
);
|
|
183
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
184
|
+
if (plannerSeeds?.seedPlannerAvailabilityRuleSetDefaults) {
|
|
185
|
+
await plannerSeeds.seedPlannerAvailabilityRuleSetDefaults(ctx.em, scope);
|
|
186
|
+
}
|
|
187
|
+
if (staffSeeds?.seedStaffTeamExamples) {
|
|
188
|
+
await staffSeeds.seedStaffTeamExamples(ctx.em, scope);
|
|
189
|
+
}
|
|
190
|
+
if (resourcesSeeds?.seedResourcesResourceExamples) {
|
|
191
|
+
await resourcesSeeds.seedResourcesResourceExamples(ctx.em, scope);
|
|
192
|
+
}
|
|
193
|
+
await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ["planner", "staff", "resources"]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
];
|
|
14
197
|
function actionsUpToVersion(version) {
|
|
15
198
|
return upgradeActions.filter((action) => compareVersions(action.version, version) <= 0).sort((a, b) => compareVersions(a.version, b.version) || a.id.localeCompare(b.id));
|
|
16
199
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/configs/lib/upgrade-actions.ts"],
|
|
4
|
-
"sourcesContent": ["import type {
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport * as semver from 'semver'\nimport { getModules } from '@open-mercato/shared/lib/modules/registry'\nimport type { Module } from '@open-mercato/shared/modules/registry'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { Role, RoleAcl } from '@open-mercato/core/modules/auth/data/entities'\nimport { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\n\nexport type UpgradeActionContext = {\n tenantId: string\n organizationId: string\n container: AppContainer\n em: EntityManager\n}\n\nexport type UpgradeActionDefinition = {\n id: string\n version: string\n messageKey: string\n ctaKey: string\n successKey: string\n loadingKey?: string\n run: (ctx: UpgradeActionContext) => Promise<void>\n}\n\n/**\n * Compare two semantic version strings.\n * Uses the semver library for robust version comparison.\n * Returns negative if a < b, positive if a > b, 0 if equal.\n * Throws an error if either version string is invalid.\n */\nexport function compareVersions(a: string, b: string): number {\n const cleanA = semver.valid(semver.coerce(a))\n const cleanB = semver.valid(semver.coerce(b))\n if (!cleanA) {\n throw new Error(`Invalid version string: \"${a}\". Expected a valid semver format (e.g., \"1.2.3\").`)\n }\n if (!cleanB) {\n throw new Error(`Invalid version string: \"${b}\". Expected a valid semver format (e.g., \"1.2.3\").`)\n }\n return semver.compare(cleanA, cleanB)\n}\n\ntype RoleName = 'superadmin' | 'admin' | 'employee'\n\nfunction collectRoleFeatures(modules: Module[], moduleIds: string[]): Record<RoleName, string[]> {\n const result: Record<RoleName, string[]> = {\n superadmin: [],\n admin: [],\n employee: [],\n }\n const targetIds = new Set(moduleIds)\n for (const mod of modules) {\n if (!targetIds.has(mod.id)) continue\n const roleFeatures = mod.setup?.defaultRoleFeatures\n if (roleFeatures?.superadmin) result.superadmin.push(...roleFeatures.superadmin)\n if (roleFeatures?.admin) result.admin.push(...roleFeatures.admin)\n if (roleFeatures?.employee) result.employee.push(...roleFeatures.employee)\n }\n result.superadmin = Array.from(new Set(result.superadmin))\n result.admin = Array.from(new Set(result.admin))\n result.employee = Array.from(new Set(result.employee))\n return result\n}\n\nasync function findRoleByName(\n em: EntityManager,\n name: string,\n tenantId: string | null,\n): Promise<Role | null> {\n const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null\n let role = await em.findOne(Role, { name, tenantId: normalizedTenant })\n if (!role && normalizedTenant !== null) {\n role = await em.findOne(Role, { name, tenantId: null })\n }\n return role\n}\n\nasync function ensureRoleAclFor(\n em: EntityManager,\n role: Role,\n tenantId: string,\n features: string[],\n options: { isSuperAdmin?: boolean } = {},\n) {\n const existing = await em.findOne(RoleAcl, { role, tenantId })\n if (!existing) {\n const acl = em.create(RoleAcl, {\n role,\n tenantId,\n featuresJson: features,\n isSuperAdmin: !!options.isSuperAdmin,\n createdAt: new Date(),\n })\n await em.persistAndFlush(acl)\n return\n }\n const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []\n const merged = Array.from(new Set([...currentFeatures, ...features]))\n const changed =\n merged.length !== currentFeatures.length ||\n merged.some((value, index) => value !== currentFeatures[index])\n if (changed) existing.featuresJson = merged\n if (options.isSuperAdmin && !existing.isSuperAdmin) {\n existing.isSuperAdmin = true\n }\n if (changed || options.isSuperAdmin) {\n await em.persistAndFlush(existing)\n }\n}\n\nasync function ensureRoleAclsForModules(\n em: EntityManager,\n tenantId: string,\n moduleIds: string[],\n) {\n const modules = getModules()\n const roleFeatures = collectRoleFeatures(modules, moduleIds)\n if (!roleFeatures.superadmin.length && !roleFeatures.admin.length && !roleFeatures.employee.length) {\n return\n }\n const normalizedTenant = normalizeTenantId(tenantId) ?? null\n const [superadminRole, adminRole, employeeRole] = await Promise.all([\n findRoleByName(em, 'superadmin', normalizedTenant),\n findRoleByName(em, 'admin', normalizedTenant),\n findRoleByName(em, 'employee', normalizedTenant),\n ])\n if (superadminRole && roleFeatures.superadmin.length) {\n await ensureRoleAclFor(em, superadminRole, tenantId, roleFeatures.superadmin, { isSuperAdmin: true })\n }\n if (adminRole && roleFeatures.admin.length) {\n await ensureRoleAclFor(em, adminRole, tenantId, roleFeatures.admin)\n }\n if (employeeRole && roleFeatures.employee.length) {\n await ensureRoleAclFor(em, employeeRole, tenantId, roleFeatures.employee)\n }\n}\n\nasync function safeImport<T>(label: string, loader: () => Promise<T>): Promise<T | null> {\n try {\n return await loader()\n } catch (error) {\n console.warn(`[upgrade-actions] Skipping ${label} because module import failed.`, error)\n return null\n }\n}\n\nexport const upgradeActions: UpgradeActionDefinition[] = [\n {\n id: 'configs.upgrades.catalog.examples',\n version: '0.3.4',\n messageKey: 'upgrades.v034.message',\n ctaKey: 'upgrades.v034.cta',\n successKey: 'upgrades.v034.success',\n loadingKey: 'upgrades.v034.loading',\n async run(ctx) {\n const catalogSeeds = await safeImport('catalog examples', () =>\n import('@open-mercato/core/modules/catalog/lib/seeds')\n )\n if (!catalogSeeds?.installExampleCatalogData) return\n await catalogSeeds.installExampleCatalogData(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n ctx.em,\n )\n },\n },\n {\n id: 'configs.upgrades.sales.examples',\n version: '0.3.6',\n messageKey: 'upgrades.v036.message',\n ctaKey: 'upgrades.v036.cta',\n successKey: 'upgrades.v036.success',\n loadingKey: 'upgrades.v036.loading',\n async run(ctx) {\n const salesSeeds = await safeImport('sales examples', () =>\n import('@open-mercato/core/modules/sales/seed/examples')\n )\n if (!salesSeeds?.seedSalesExamples) return\n await salesSeeds.seedSalesExamples(ctx.em, ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n },\n },\n {\n id: 'configs.upgrades.examples.currencies_workflows',\n version: '0.3.13',\n messageKey: 'upgrades.v0313.message',\n ctaKey: 'upgrades.v0313.cta',\n successKey: 'upgrades.v0313.success',\n loadingKey: 'upgrades.v0313.loading',\n async run(ctx) {\n const currenciesSeeds = await safeImport('currencies examples', () =>\n import('@open-mercato/core/modules/currencies/lib/seeds')\n )\n const workflowsSeeds = await safeImport('workflows examples', () =>\n import('@open-mercato/core/modules/workflows/lib/seeds')\n )\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n if (currenciesSeeds?.seedExampleCurrencies) {\n await currenciesSeeds.seedExampleCurrencies(ctx.em, scope)\n }\n if (workflowsSeeds?.seedExampleWorkflows) {\n await workflowsSeeds.seedExampleWorkflows(ctx.em, scope)\n }\n await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['currencies', 'workflows'])\n },\n },\n {\n id: 'configs.upgrades.examples.planner_staff_resources',\n version: '0.4.1',\n messageKey: 'upgrades.v041.message',\n ctaKey: 'upgrades.v041.cta',\n successKey: 'upgrades.v041.success',\n loadingKey: 'upgrades.v041.loading',\n async run(ctx) {\n const plannerSeeds = await safeImport('planner examples', () =>\n import('@open-mercato/core/modules/planner/lib/seeds')\n )\n const staffSeeds = await safeImport('staff examples', () =>\n import('@open-mercato/core/modules/staff/lib/seeds')\n )\n const resourcesSeeds = await safeImport('resources examples', () =>\n import('@open-mercato/core/modules/resources/lib/seeds')\n )\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n if (plannerSeeds?.seedPlannerAvailabilityRuleSetDefaults) {\n await plannerSeeds.seedPlannerAvailabilityRuleSetDefaults(ctx.em, scope)\n }\n if (staffSeeds?.seedStaffTeamExamples) {\n await staffSeeds.seedStaffTeamExamples(ctx.em, scope)\n }\n if (resourcesSeeds?.seedResourcesResourceExamples) {\n await resourcesSeeds.seedResourcesResourceExamples(ctx.em, scope)\n }\n await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['planner', 'staff', 'resources'])\n },\n },\n]\n\nexport function actionsUpToVersion(version: string): UpgradeActionDefinition[] {\n return upgradeActions\n .filter((action) => compareVersions(action.version, version) <= 0)\n .sort((a, b) => compareVersions(a.version, b.version) || a.id.localeCompare(b.id))\n}\n\nexport function findUpgradeAction(actionId: string, maxVersion: string): UpgradeActionDefinition | undefined {\n const matches = actionsUpToVersion(maxVersion).filter((action) => action.id === actionId)\n if (!matches.length) return undefined\n return matches[matches.length - 1]\n}\n"],
|
|
5
|
+
"mappings": "AACA,YAAY,YAAY;AACxB,SAAS,kBAAkB;AAG3B,SAAS,MAAM,eAAe;AAC9B,SAAS,yBAAyB;AAyB3B,SAAS,gBAAgB,GAAW,GAAmB;AAC5D,QAAM,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC,CAAC;AAC5C,QAAM,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC,CAAC;AAC5C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,4BAA4B,CAAC,oDAAoD;AAAA,EACnG;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,4BAA4B,CAAC,oDAAoD;AAAA,EACnG;AACA,SAAO,OAAO,QAAQ,QAAQ,MAAM;AACtC;AAIA,SAAS,oBAAoB,SAAmB,WAAiD;AAC/F,QAAM,SAAqC;AAAA,IACzC,YAAY,CAAC;AAAA,IACb,OAAO,CAAC;AAAA,IACR,UAAU,CAAC;AAAA,EACb;AACA,QAAM,YAAY,IAAI,IAAI,SAAS;AACnC,aAAW,OAAO,SAAS;AACzB,QAAI,CAAC,UAAU,IAAI,IAAI,EAAE,EAAG;AAC5B,UAAM,eAAe,IAAI,OAAO;AAChC,QAAI,cAAc,WAAY,QAAO,WAAW,KAAK,GAAG,aAAa,UAAU;AAC/E,QAAI,cAAc,MAAO,QAAO,MAAM,KAAK,GAAG,aAAa,KAAK;AAChE,QAAI,cAAc,SAAU,QAAO,SAAS,KAAK,GAAG,aAAa,QAAQ;AAAA,EAC3E;AACA,SAAO,aAAa,MAAM,KAAK,IAAI,IAAI,OAAO,UAAU,CAAC;AACzD,SAAO,QAAQ,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK,CAAC;AAC/C,SAAO,WAAW,MAAM,KAAK,IAAI,IAAI,OAAO,QAAQ,CAAC;AACrD,SAAO;AACT;AAEA,eAAe,eACb,IACA,MACA,UACsB;AACtB,QAAM,mBAAmB,kBAAkB,YAAY,IAAI,KAAK;AAChE,MAAI,OAAO,MAAM,GAAG,QAAQ,MAAM,EAAE,MAAM,UAAU,iBAAiB,CAAC;AACtE,MAAI,CAAC,QAAQ,qBAAqB,MAAM;AACtC,WAAO,MAAM,GAAG,QAAQ,MAAM,EAAE,MAAM,UAAU,KAAK,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AAEA,eAAe,iBACb,IACA,MACA,UACA,UACA,UAAsC,CAAC,GACvC;AACA,QAAM,WAAW,MAAM,GAAG,QAAQ,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,MAAI,CAAC,UAAU;AACb,UAAM,MAAM,GAAG,OAAO,SAAS;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,cAAc,CAAC,CAAC,QAAQ;AAAA,MACxB,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC;AACD,UAAM,GAAG,gBAAgB,GAAG;AAC5B;AAAA,EACF;AACA,QAAM,kBAAkB,MAAM,QAAQ,SAAS,YAAY,IAAI,SAAS,eAAe,CAAC;AACxF,QAAM,SAAS,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,iBAAiB,GAAG,QAAQ,CAAC,CAAC;AACpE,QAAM,UACJ,OAAO,WAAW,gBAAgB,UAClC,OAAO,KAAK,CAAC,OAAO,UAAU,UAAU,gBAAgB,KAAK,CAAC;AAChE,MAAI,QAAS,UAAS,eAAe;AACrC,MAAI,QAAQ,gBAAgB,CAAC,SAAS,cAAc;AAClD,aAAS,eAAe;AAAA,EAC1B;AACA,MAAI,WAAW,QAAQ,cAAc;AACnC,UAAM,GAAG,gBAAgB,QAAQ;AAAA,EACnC;AACF;AAEA,eAAe,yBACb,IACA,UACA,WACA;AACA,QAAM,UAAU,WAAW;AAC3B,QAAM,eAAe,oBAAoB,SAAS,SAAS;AAC3D,MAAI,CAAC,aAAa,WAAW,UAAU,CAAC,aAAa,MAAM,UAAU,CAAC,aAAa,SAAS,QAAQ;AAClG;AAAA,EACF;AACA,QAAM,mBAAmB,kBAAkB,QAAQ,KAAK;AACxD,QAAM,CAAC,gBAAgB,WAAW,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClE,eAAe,IAAI,cAAc,gBAAgB;AAAA,IACjD,eAAe,IAAI,SAAS,gBAAgB;AAAA,IAC5C,eAAe,IAAI,YAAY,gBAAgB;AAAA,EACjD,CAAC;AACD,MAAI,kBAAkB,aAAa,WAAW,QAAQ;AACpD,UAAM,iBAAiB,IAAI,gBAAgB,UAAU,aAAa,YAAY,EAAE,cAAc,KAAK,CAAC;AAAA,EACtG;AACA,MAAI,aAAa,aAAa,MAAM,QAAQ;AAC1C,UAAM,iBAAiB,IAAI,WAAW,UAAU,aAAa,KAAK;AAAA,EACpE;AACA,MAAI,gBAAgB,aAAa,SAAS,QAAQ;AAChD,UAAM,iBAAiB,IAAI,cAAc,UAAU,aAAa,QAAQ;AAAA,EAC1E;AACF;AAEA,eAAe,WAAc,OAAe,QAA6C;AACvF,MAAI;AACF,WAAO,MAAM,OAAO;AAAA,EACtB,SAAS,OAAO;AACd,YAAQ,KAAK,8BAA8B,KAAK,kCAAkC,KAAK;AACvF,WAAO;AAAA,EACT;AACF;AAEO,MAAM,iBAA4C;AAAA,EACvD;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,MAAM,IAAI,KAAK;AACb,YAAM,eAAe,MAAM;AAAA,QAAW;AAAA,QAAoB,MACxD,OAAO,8CAA8C;AAAA,MACvD;AACA,UAAI,CAAC,cAAc,0BAA2B;AAC9C,YAAM,aAAa;AAAA,QACjB,IAAI;AAAA,QACJ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAAA,QAC7D,IAAI;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,MAAM,IAAI,KAAK;AACb,YAAM,aAAa,MAAM;AAAA,QAAW;AAAA,QAAkB,MACpD,OAAO,gDAAgD;AAAA,MACzD;AACA,UAAI,CAAC,YAAY,kBAAmB;AACpC,YAAM,WAAW,kBAAkB,IAAI,IAAI,IAAI,WAAW;AAAA,QACxD,UAAU,IAAI;AAAA,QACd,gBAAgB,IAAI;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,MAAM,IAAI,KAAK;AACb,YAAM,kBAAkB,MAAM;AAAA,QAAW;AAAA,QAAuB,MAC9D,OAAO,iDAAiD;AAAA,MAC1D;AACA,YAAM,iBAAiB,MAAM;AAAA,QAAW;AAAA,QAAsB,MAC5D,OAAO,gDAAgD;AAAA,MACzD;AACA,YAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAI,iBAAiB,uBAAuB;AAC1C,cAAM,gBAAgB,sBAAsB,IAAI,IAAI,KAAK;AAAA,MAC3D;AACA,UAAI,gBAAgB,sBAAsB;AACxC,cAAM,eAAe,qBAAqB,IAAI,IAAI,KAAK;AAAA,MACzD;AACA,YAAM,yBAAyB,IAAI,IAAI,IAAI,UAAU,CAAC,cAAc,WAAW,CAAC;AAAA,IAClF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,MAAM,IAAI,KAAK;AACb,YAAM,eAAe,MAAM;AAAA,QAAW;AAAA,QAAoB,MACxD,OAAO,8CAA8C;AAAA,MACvD;AACA,YAAM,aAAa,MAAM;AAAA,QAAW;AAAA,QAAkB,MACpD,OAAO,4CAA4C;AAAA,MACrD;AACA,YAAM,iBAAiB,MAAM;AAAA,QAAW;AAAA,QAAsB,MAC5D,OAAO,gDAAgD;AAAA,MACzD;AACA,YAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAI,cAAc,wCAAwC;AACxD,cAAM,aAAa,uCAAuC,IAAI,IAAI,KAAK;AAAA,MACzE;AACA,UAAI,YAAY,uBAAuB;AACrC,cAAM,WAAW,sBAAsB,IAAI,IAAI,KAAK;AAAA,MACtD;AACA,UAAI,gBAAgB,+BAA+B;AACjD,cAAM,eAAe,8BAA8B,IAAI,IAAI,KAAK;AAAA,MAClE;AACA,YAAM,yBAAyB,IAAI,IAAI,IAAI,UAAU,CAAC,WAAW,SAAS,WAAW,CAAC;AAAA,IACxF;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,SAA4C;AAC7E,SAAO,eACJ,OAAO,CAAC,WAAW,gBAAgB,OAAO,SAAS,OAAO,KAAK,CAAC,EAChE,KAAK,CAAC,GAAG,MAAM,gBAAgB,EAAE,SAAS,EAAE,OAAO,KAAK,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AACrF;AAEO,SAAS,kBAAkB,UAAkB,YAAyD;AAC3G,QAAM,UAAU,mBAAmB,UAAU,EAAE,OAAO,CAAC,WAAW,OAAO,OAAO,QAAQ;AACxF,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,SAAO,QAAQ,QAAQ,SAAS,CAAC;AACnC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.2-canary-
|
|
3
|
+
"version": "0.4.2-canary-8a04af8836",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.2-canary-
|
|
210
|
+
"@open-mercato/shared": "0.4.2-canary-8a04af8836",
|
|
211
211
|
"@xyflow/react": "^12.6.0",
|
|
212
212
|
"date-fns": "^4.1.0",
|
|
213
213
|
"date-fns-tz": "^3.2.0"
|
|
@@ -16,6 +16,7 @@ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
|
16
16
|
|
|
17
17
|
const DEFAULT_ROLE_NAMES = ['employee', 'admin', 'superadmin'] as const
|
|
18
18
|
const DEMO_SUPERADMIN_EMAIL = 'superadmin@acme.com'
|
|
19
|
+
const DEFAULT_DERIVED_EMAIL_DOMAIN = DEMO_SUPERADMIN_EMAIL.split('@')[1] ?? 'acme.com'
|
|
19
20
|
|
|
20
21
|
export type EnsureRolesOptions = {
|
|
21
22
|
roleNames?: string[]
|
|
@@ -183,11 +184,10 @@ export async function setupInitialTenant(
|
|
|
183
184
|
{ email: primaryUser.email, roles: primaryRoles, name: resolvePrimaryName(primaryUser) },
|
|
184
185
|
]
|
|
185
186
|
if (includeDerivedUsers) {
|
|
186
|
-
const [, domain] = String(primaryUser.email).split('@')
|
|
187
187
|
const adminOverride = readEnvValue(DERIVED_EMAIL_ENV.admin)
|
|
188
188
|
const employeeOverride = readEnvValue(DERIVED_EMAIL_ENV.employee)
|
|
189
|
-
const adminEmail = adminOverride ??
|
|
190
|
-
const employeeEmail = employeeOverride ??
|
|
189
|
+
const adminEmail = adminOverride ?? `admin@${DEFAULT_DERIVED_EMAIL_DOMAIN}`
|
|
190
|
+
const employeeEmail = employeeOverride ?? `employee@${DEFAULT_DERIVED_EMAIL_DOMAIN}`
|
|
191
191
|
const adminPassword = readEnvValue('OM_INIT_ADMIN_PASSWORD') || 'secret'
|
|
192
192
|
const employeePassword = readEnvValue('OM_INIT_EMPLOYEE_PASSWORD') || 'secret'
|
|
193
193
|
const adminPasswordHash = adminPassword ? await resolvePasswordHash({ email: adminEmail, password: adminPassword }) : null
|
|
@@ -423,6 +423,12 @@ async function ensureDefaultRoleAcls(
|
|
|
423
423
|
if (roleFeatures.employee) employeeFeatures.push(...roleFeatures.employee)
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
+
console.log('✅ Seeded default role features', {
|
|
427
|
+
superadmin: superadminFeatures,
|
|
428
|
+
admin: adminFeatures,
|
|
429
|
+
employee: employeeFeatures,
|
|
430
|
+
})
|
|
431
|
+
|
|
426
432
|
if (includeSuperadminRole && superadminRole) {
|
|
427
433
|
await ensureRoleAclFor(em, superadminRole, tenantId, superadminFeatures, { isSuperAdmin: true })
|
|
428
434
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import type { AwilixContainer } from 'awilix'
|
|
2
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
2
|
import * as semver from 'semver'
|
|
3
|
+
import { getModules } from '@open-mercato/shared/lib/modules/registry'
|
|
4
|
+
import type { Module } from '@open-mercato/shared/modules/registry'
|
|
5
|
+
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { Role, RoleAcl } from '@open-mercato/core/modules/auth/data/entities'
|
|
7
|
+
import { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'
|
|
4
8
|
|
|
5
9
|
export type UpgradeActionContext = {
|
|
6
10
|
tenantId: string
|
|
7
11
|
organizationId: string
|
|
8
|
-
container:
|
|
12
|
+
container: AppContainer
|
|
9
13
|
em: EntityManager
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -37,7 +41,203 @@ export function compareVersions(a: string, b: string): number {
|
|
|
37
41
|
return semver.compare(cleanA, cleanB)
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
type RoleName = 'superadmin' | 'admin' | 'employee'
|
|
45
|
+
|
|
46
|
+
function collectRoleFeatures(modules: Module[], moduleIds: string[]): Record<RoleName, string[]> {
|
|
47
|
+
const result: Record<RoleName, string[]> = {
|
|
48
|
+
superadmin: [],
|
|
49
|
+
admin: [],
|
|
50
|
+
employee: [],
|
|
51
|
+
}
|
|
52
|
+
const targetIds = new Set(moduleIds)
|
|
53
|
+
for (const mod of modules) {
|
|
54
|
+
if (!targetIds.has(mod.id)) continue
|
|
55
|
+
const roleFeatures = mod.setup?.defaultRoleFeatures
|
|
56
|
+
if (roleFeatures?.superadmin) result.superadmin.push(...roleFeatures.superadmin)
|
|
57
|
+
if (roleFeatures?.admin) result.admin.push(...roleFeatures.admin)
|
|
58
|
+
if (roleFeatures?.employee) result.employee.push(...roleFeatures.employee)
|
|
59
|
+
}
|
|
60
|
+
result.superadmin = Array.from(new Set(result.superadmin))
|
|
61
|
+
result.admin = Array.from(new Set(result.admin))
|
|
62
|
+
result.employee = Array.from(new Set(result.employee))
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function findRoleByName(
|
|
67
|
+
em: EntityManager,
|
|
68
|
+
name: string,
|
|
69
|
+
tenantId: string | null,
|
|
70
|
+
): Promise<Role | null> {
|
|
71
|
+
const normalizedTenant = normalizeTenantId(tenantId ?? null) ?? null
|
|
72
|
+
let role = await em.findOne(Role, { name, tenantId: normalizedTenant })
|
|
73
|
+
if (!role && normalizedTenant !== null) {
|
|
74
|
+
role = await em.findOne(Role, { name, tenantId: null })
|
|
75
|
+
}
|
|
76
|
+
return role
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function ensureRoleAclFor(
|
|
80
|
+
em: EntityManager,
|
|
81
|
+
role: Role,
|
|
82
|
+
tenantId: string,
|
|
83
|
+
features: string[],
|
|
84
|
+
options: { isSuperAdmin?: boolean } = {},
|
|
85
|
+
) {
|
|
86
|
+
const existing = await em.findOne(RoleAcl, { role, tenantId })
|
|
87
|
+
if (!existing) {
|
|
88
|
+
const acl = em.create(RoleAcl, {
|
|
89
|
+
role,
|
|
90
|
+
tenantId,
|
|
91
|
+
featuresJson: features,
|
|
92
|
+
isSuperAdmin: !!options.isSuperAdmin,
|
|
93
|
+
createdAt: new Date(),
|
|
94
|
+
})
|
|
95
|
+
await em.persistAndFlush(acl)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
const currentFeatures = Array.isArray(existing.featuresJson) ? existing.featuresJson : []
|
|
99
|
+
const merged = Array.from(new Set([...currentFeatures, ...features]))
|
|
100
|
+
const changed =
|
|
101
|
+
merged.length !== currentFeatures.length ||
|
|
102
|
+
merged.some((value, index) => value !== currentFeatures[index])
|
|
103
|
+
if (changed) existing.featuresJson = merged
|
|
104
|
+
if (options.isSuperAdmin && !existing.isSuperAdmin) {
|
|
105
|
+
existing.isSuperAdmin = true
|
|
106
|
+
}
|
|
107
|
+
if (changed || options.isSuperAdmin) {
|
|
108
|
+
await em.persistAndFlush(existing)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function ensureRoleAclsForModules(
|
|
113
|
+
em: EntityManager,
|
|
114
|
+
tenantId: string,
|
|
115
|
+
moduleIds: string[],
|
|
116
|
+
) {
|
|
117
|
+
const modules = getModules()
|
|
118
|
+
const roleFeatures = collectRoleFeatures(modules, moduleIds)
|
|
119
|
+
if (!roleFeatures.superadmin.length && !roleFeatures.admin.length && !roleFeatures.employee.length) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
const normalizedTenant = normalizeTenantId(tenantId) ?? null
|
|
123
|
+
const [superadminRole, adminRole, employeeRole] = await Promise.all([
|
|
124
|
+
findRoleByName(em, 'superadmin', normalizedTenant),
|
|
125
|
+
findRoleByName(em, 'admin', normalizedTenant),
|
|
126
|
+
findRoleByName(em, 'employee', normalizedTenant),
|
|
127
|
+
])
|
|
128
|
+
if (superadminRole && roleFeatures.superadmin.length) {
|
|
129
|
+
await ensureRoleAclFor(em, superadminRole, tenantId, roleFeatures.superadmin, { isSuperAdmin: true })
|
|
130
|
+
}
|
|
131
|
+
if (adminRole && roleFeatures.admin.length) {
|
|
132
|
+
await ensureRoleAclFor(em, adminRole, tenantId, roleFeatures.admin)
|
|
133
|
+
}
|
|
134
|
+
if (employeeRole && roleFeatures.employee.length) {
|
|
135
|
+
await ensureRoleAclFor(em, employeeRole, tenantId, roleFeatures.employee)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function safeImport<T>(label: string, loader: () => Promise<T>): Promise<T | null> {
|
|
140
|
+
try {
|
|
141
|
+
return await loader()
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.warn(`[upgrade-actions] Skipping ${label} because module import failed.`, error)
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const upgradeActions: UpgradeActionDefinition[] = [
|
|
149
|
+
{
|
|
150
|
+
id: 'configs.upgrades.catalog.examples',
|
|
151
|
+
version: '0.3.4',
|
|
152
|
+
messageKey: 'upgrades.v034.message',
|
|
153
|
+
ctaKey: 'upgrades.v034.cta',
|
|
154
|
+
successKey: 'upgrades.v034.success',
|
|
155
|
+
loadingKey: 'upgrades.v034.loading',
|
|
156
|
+
async run(ctx) {
|
|
157
|
+
const catalogSeeds = await safeImport('catalog examples', () =>
|
|
158
|
+
import('@open-mercato/core/modules/catalog/lib/seeds')
|
|
159
|
+
)
|
|
160
|
+
if (!catalogSeeds?.installExampleCatalogData) return
|
|
161
|
+
await catalogSeeds.installExampleCatalogData(
|
|
162
|
+
ctx.container,
|
|
163
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
164
|
+
ctx.em,
|
|
165
|
+
)
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'configs.upgrades.sales.examples',
|
|
170
|
+
version: '0.3.6',
|
|
171
|
+
messageKey: 'upgrades.v036.message',
|
|
172
|
+
ctaKey: 'upgrades.v036.cta',
|
|
173
|
+
successKey: 'upgrades.v036.success',
|
|
174
|
+
loadingKey: 'upgrades.v036.loading',
|
|
175
|
+
async run(ctx) {
|
|
176
|
+
const salesSeeds = await safeImport('sales examples', () =>
|
|
177
|
+
import('@open-mercato/core/modules/sales/seed/examples')
|
|
178
|
+
)
|
|
179
|
+
if (!salesSeeds?.seedSalesExamples) return
|
|
180
|
+
await salesSeeds.seedSalesExamples(ctx.em, ctx.container, {
|
|
181
|
+
tenantId: ctx.tenantId,
|
|
182
|
+
organizationId: ctx.organizationId,
|
|
183
|
+
})
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'configs.upgrades.examples.currencies_workflows',
|
|
188
|
+
version: '0.3.13',
|
|
189
|
+
messageKey: 'upgrades.v0313.message',
|
|
190
|
+
ctaKey: 'upgrades.v0313.cta',
|
|
191
|
+
successKey: 'upgrades.v0313.success',
|
|
192
|
+
loadingKey: 'upgrades.v0313.loading',
|
|
193
|
+
async run(ctx) {
|
|
194
|
+
const currenciesSeeds = await safeImport('currencies examples', () =>
|
|
195
|
+
import('@open-mercato/core/modules/currencies/lib/seeds')
|
|
196
|
+
)
|
|
197
|
+
const workflowsSeeds = await safeImport('workflows examples', () =>
|
|
198
|
+
import('@open-mercato/core/modules/workflows/lib/seeds')
|
|
199
|
+
)
|
|
200
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
201
|
+
if (currenciesSeeds?.seedExampleCurrencies) {
|
|
202
|
+
await currenciesSeeds.seedExampleCurrencies(ctx.em, scope)
|
|
203
|
+
}
|
|
204
|
+
if (workflowsSeeds?.seedExampleWorkflows) {
|
|
205
|
+
await workflowsSeeds.seedExampleWorkflows(ctx.em, scope)
|
|
206
|
+
}
|
|
207
|
+
await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['currencies', 'workflows'])
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'configs.upgrades.examples.planner_staff_resources',
|
|
212
|
+
version: '0.4.1',
|
|
213
|
+
messageKey: 'upgrades.v041.message',
|
|
214
|
+
ctaKey: 'upgrades.v041.cta',
|
|
215
|
+
successKey: 'upgrades.v041.success',
|
|
216
|
+
loadingKey: 'upgrades.v041.loading',
|
|
217
|
+
async run(ctx) {
|
|
218
|
+
const plannerSeeds = await safeImport('planner examples', () =>
|
|
219
|
+
import('@open-mercato/core/modules/planner/lib/seeds')
|
|
220
|
+
)
|
|
221
|
+
const staffSeeds = await safeImport('staff examples', () =>
|
|
222
|
+
import('@open-mercato/core/modules/staff/lib/seeds')
|
|
223
|
+
)
|
|
224
|
+
const resourcesSeeds = await safeImport('resources examples', () =>
|
|
225
|
+
import('@open-mercato/core/modules/resources/lib/seeds')
|
|
226
|
+
)
|
|
227
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
228
|
+
if (plannerSeeds?.seedPlannerAvailabilityRuleSetDefaults) {
|
|
229
|
+
await plannerSeeds.seedPlannerAvailabilityRuleSetDefaults(ctx.em, scope)
|
|
230
|
+
}
|
|
231
|
+
if (staffSeeds?.seedStaffTeamExamples) {
|
|
232
|
+
await staffSeeds.seedStaffTeamExamples(ctx.em, scope)
|
|
233
|
+
}
|
|
234
|
+
if (resourcesSeeds?.seedResourcesResourceExamples) {
|
|
235
|
+
await resourcesSeeds.seedResourcesResourceExamples(ctx.em, scope)
|
|
236
|
+
}
|
|
237
|
+
await ensureRoleAclsForModules(ctx.em, ctx.tenantId, ['planner', 'staff', 'resources'])
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
]
|
|
41
241
|
|
|
42
242
|
export function actionsUpToVersion(version: string): UpgradeActionDefinition[] {
|
|
43
243
|
return upgradeActions
|
|
@@ -799,6 +799,8 @@
|
|
|
799
799
|
"staff.teams.tabs.members": "Teammitglieder",
|
|
800
800
|
"staff.notifications.leaveRequest.pending.title": "Urlaubsantrag ausstehend",
|
|
801
801
|
"staff.notifications.leaveRequest.pending.body": "{memberName} hat Urlaub vom {startDate} bis {endDate} beantragt",
|
|
802
|
+
"staff.notifications.leaveRequest.approved.title": "Urlaubsantrag genehmigt",
|
|
803
|
+
"staff.notifications.leaveRequest.approved.body": "Dein Urlaubsantrag vom {startDate} bis {endDate} wurde genehmigt",
|
|
802
804
|
"staff.notifications.leaveRequest.actions.approve": "Genehmigen",
|
|
803
805
|
"staff.notifications.leaveRequest.actions.reject": "Ablehnen",
|
|
804
806
|
"staff.leaveRequests.page.title": "Urlaubsantr\u00e4ge",
|
|
@@ -799,6 +799,8 @@
|
|
|
799
799
|
"staff.teams.tabs.members": "Miembros del equipo",
|
|
800
800
|
"staff.notifications.leaveRequest.pending.title": "Solicitud de ausencia pendiente",
|
|
801
801
|
"staff.notifications.leaveRequest.pending.body": "{memberName} ha solicitado ausencia del {startDate} al {endDate}",
|
|
802
|
+
"staff.notifications.leaveRequest.approved.title": "Solicitud de ausencia aprobada",
|
|
803
|
+
"staff.notifications.leaveRequest.approved.body": "Tu solicitud de ausencia del {startDate} al {endDate} ha sido aprobada",
|
|
802
804
|
"staff.notifications.leaveRequest.actions.approve": "Aprobar",
|
|
803
805
|
"staff.notifications.leaveRequest.actions.reject": "Rechazar",
|
|
804
806
|
"staff.leaveRequests.page.title": "Solicitudes de ausencia",
|
|
@@ -799,6 +799,8 @@
|
|
|
799
799
|
"staff.teams.tabs.members": "Cz\u0142onkowie zespo\u0142u",
|
|
800
800
|
"staff.notifications.leaveRequest.pending.title": "Wniosek urlopowy oczekuje",
|
|
801
801
|
"staff.notifications.leaveRequest.pending.body": "{memberName} zło\u017cył wniosek o urlop od {startDate} do {endDate}",
|
|
802
|
+
"staff.notifications.leaveRequest.approved.title": "Wniosek urlopowy zatwierdzony",
|
|
803
|
+
"staff.notifications.leaveRequest.approved.body": "Tw\u00f3j wniosek urlopowy od {startDate} do {endDate} zosta\u0142 zatwierdzony",
|
|
802
804
|
"staff.notifications.leaveRequest.actions.approve": "Zatwierd\u017a",
|
|
803
805
|
"staff.notifications.leaveRequest.actions.reject": "Odrzu\u0107",
|
|
804
806
|
"staff.leaveRequests.page.title": "Wnioski urlopowe",
|
|
@@ -746,7 +746,8 @@ describe('Transition Handler (Unit Tests)', () => {
|
|
|
746
746
|
fromStepId: 'step-1',
|
|
747
747
|
toStepId: 'step-2',
|
|
748
748
|
}),
|
|
749
|
-
})
|
|
749
|
+
}),
|
|
750
|
+
expect.objectContaining({ eventBus: undefined })
|
|
750
751
|
)
|
|
751
752
|
})
|
|
752
753
|
|
|
@@ -790,7 +791,8 @@ describe('Transition Handler (Unit Tests)', () => {
|
|
|
790
791
|
expect.objectContaining({
|
|
791
792
|
entityType: 'workflow:simple-approval:transition',
|
|
792
793
|
eventType: 'post_transition',
|
|
793
|
-
})
|
|
794
|
+
}),
|
|
795
|
+
expect.objectContaining({ eventBus: undefined })
|
|
794
796
|
)
|
|
795
797
|
})
|
|
796
798
|
|
|
@@ -838,7 +840,8 @@ describe('Transition Handler (Unit Tests)', () => {
|
|
|
838
840
|
triggerData,
|
|
839
841
|
}),
|
|
840
842
|
executedBy: 'user-123',
|
|
841
|
-
})
|
|
843
|
+
}),
|
|
844
|
+
expect.objectContaining({ eventBus: undefined })
|
|
842
845
|
)
|
|
843
846
|
})
|
|
844
847
|
})
|