@open-mercato/core 0.5.1-develop.2638.59e6e26f46 → 0.5.1-develop.2657.a01847a9fa
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +26 -0
- package/dist/modules/auth/lib/backendChrome.js +3 -1
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +8 -2
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-confirm.js +7 -0
- package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/nav.js +77 -0
- package/dist/modules/customer_accounts/api/portal/nav.js.map +7 -0
- package/dist/modules/customer_accounts/api/signup.js +20 -8
- package/dist/modules/customer_accounts/api/signup.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerSessionService.js +32 -0
- package/dist/modules/customer_accounts/services/customerSessionService.js.map +2 -2
- package/dist/modules/directory/api/organizations/route.js +10 -0
- package/dist/modules/directory/api/organizations/route.js.map +3 -3
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +13 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/create/page.js +12 -2
- package/dist/modules/directory/backend/directory/organizations/create/page.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +4 -3
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.meta.js +17 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.meta.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.meta.js +11 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.meta.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.meta.js +11 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.meta.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.meta.js +17 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.meta.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.meta.js +11 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.meta.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/verify/page.meta.js +11 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/verify/page.meta.js.map +7 -0
- package/dist/modules/workflows/lib/activity-executor.js +25 -16
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/package.json +3 -3
- package/src/modules/auth/lib/backendChrome.tsx +3 -1
- package/src/modules/auth/services/rbacService.ts +8 -2
- package/src/modules/customer_accounts/api/password/reset-confirm.ts +9 -0
- package/src/modules/customer_accounts/api/portal/nav.ts +87 -0
- package/src/modules/customer_accounts/api/signup.ts +23 -7
- package/src/modules/customer_accounts/services/customerSessionService.ts +39 -0
- package/src/modules/directory/api/organizations/route.ts +11 -0
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -3
- package/src/modules/directory/backend/directory/organizations/create/page.tsx +15 -3
- package/src/modules/directory/i18n/de.json +2 -0
- package/src/modules/directory/i18n/en.json +2 -0
- package/src/modules/directory/i18n/es.json +2 -0
- package/src/modules/directory/i18n/pl.json +2 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +4 -3
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.meta.ts +15 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.meta.ts +9 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/page.meta.ts +9 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.meta.ts +15 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.meta.ts +9 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/verify/page.meta.ts +9 -0
- package/src/modules/workflows/lib/activity-executor.ts +52 -24
|
@@ -24,6 +24,7 @@ type OrganizationResponse = {
|
|
|
24
24
|
items: Array<{
|
|
25
25
|
id: string
|
|
26
26
|
name: string
|
|
27
|
+
slug?: string | null
|
|
27
28
|
tenantId: string
|
|
28
29
|
tenantName?: string | null
|
|
29
30
|
parentId: string | null
|
|
@@ -131,6 +132,7 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
|
|
|
131
132
|
setInitialValues({
|
|
132
133
|
id: record.id,
|
|
133
134
|
name: record.name,
|
|
135
|
+
slug: record.slug ?? '',
|
|
134
136
|
parentId: record.parentId || '',
|
|
135
137
|
isActive: record.isActive,
|
|
136
138
|
tenantId: resolvedTenantId,
|
|
@@ -194,6 +196,12 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
|
|
|
194
196
|
} as CrudField,
|
|
195
197
|
] : []),
|
|
196
198
|
{ id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },
|
|
199
|
+
{
|
|
200
|
+
id: 'slug',
|
|
201
|
+
label: t('directory.organizations.form.field.slug', 'Slug'),
|
|
202
|
+
type: 'text',
|
|
203
|
+
description: t('directory.organizations.form.field.slug.description', 'URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores).'),
|
|
204
|
+
},
|
|
197
205
|
{
|
|
198
206
|
id: 'parentId',
|
|
199
207
|
label: t('directory.organizations.form.field.parent', 'Parent'),
|
|
@@ -237,8 +245,8 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
|
|
|
237
245
|
|
|
238
246
|
const detailFields = React.useMemo(() => (
|
|
239
247
|
actorIsSuperAdmin
|
|
240
|
-
? ['tenantId', 'name', 'parentId', 'childrenInfo', 'isActive']
|
|
241
|
-
: ['name', 'parentId', 'childrenInfo', 'isActive']
|
|
248
|
+
? ['tenantId', 'name', 'slug', 'parentId', 'childrenInfo', 'isActive']
|
|
249
|
+
: ['name', 'slug', 'parentId', 'childrenInfo', 'isActive']
|
|
242
250
|
), [actorIsSuperAdmin])
|
|
243
251
|
|
|
244
252
|
const groups: CrudFormGroup[] = React.useMemo(() => ([
|
|
@@ -278,7 +286,7 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
|
|
|
278
286
|
fields={fields}
|
|
279
287
|
groups={groups}
|
|
280
288
|
entityId={E.directory.organization}
|
|
281
|
-
initialValues={initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: '', parentId: '', isActive: true, childIds: [] }}
|
|
289
|
+
initialValues={initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: '', slug: '', parentId: '', isActive: true, childIds: [] }}
|
|
282
290
|
isLoading={loading}
|
|
283
291
|
loadingMessage={t('directory.organizations.form.loading', 'Loading organization...')}
|
|
284
292
|
submitLabel={t('directory.organizations.form.action.save', 'Save')}
|
|
@@ -315,6 +323,7 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
|
|
|
315
323
|
type UpdateOrganizationPayload = {
|
|
316
324
|
id: string
|
|
317
325
|
name: string
|
|
326
|
+
slug?: string | null
|
|
318
327
|
isActive: boolean
|
|
319
328
|
parentId: string | null
|
|
320
329
|
childIds: string[]
|
|
@@ -371,6 +380,11 @@ export async function submitUpdateOrganization(options: {
|
|
|
371
380
|
childIds: originalChildIds,
|
|
372
381
|
}
|
|
373
382
|
|
|
383
|
+
if (typeof values.slug === 'string') {
|
|
384
|
+
const trimmedSlug = values.slug.trim()
|
|
385
|
+
payload.slug = trimmedSlug.length ? trimmedSlug : null
|
|
386
|
+
}
|
|
387
|
+
|
|
374
388
|
if (submittedTenantId !== undefined && submittedTenantId !== null) {
|
|
375
389
|
payload.tenantId = submittedTenantId
|
|
376
390
|
}
|
|
@@ -132,6 +132,12 @@ export default function CreateOrganizationPage() {
|
|
|
132
132
|
} as CrudField,
|
|
133
133
|
] : []),
|
|
134
134
|
{ id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },
|
|
135
|
+
{
|
|
136
|
+
id: 'slug',
|
|
137
|
+
label: t('directory.organizations.form.field.slug', 'Slug'),
|
|
138
|
+
type: 'text',
|
|
139
|
+
description: t('directory.organizations.form.field.slug.description', 'URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores). Generated from the name when left blank.'),
|
|
140
|
+
},
|
|
135
141
|
{
|
|
136
142
|
id: 'parentId',
|
|
137
143
|
label: t('directory.organizations.form.field.parent', 'Parent'),
|
|
@@ -166,8 +172,8 @@ export default function CreateOrganizationPage() {
|
|
|
166
172
|
|
|
167
173
|
const detailFields = React.useMemo(() => (
|
|
168
174
|
actorIsSuperAdmin
|
|
169
|
-
? ['tenantId', 'name', 'parentId', 'childIds', 'isActive']
|
|
170
|
-
: ['name', 'parentId', 'childIds', 'isActive']
|
|
175
|
+
? ['tenantId', 'name', 'slug', 'parentId', 'childIds', 'isActive']
|
|
176
|
+
: ['name', 'slug', 'parentId', 'childIds', 'isActive']
|
|
171
177
|
), [actorIsSuperAdmin])
|
|
172
178
|
|
|
173
179
|
const groups: CrudFormGroup[] = React.useMemo(() => ([
|
|
@@ -186,7 +192,7 @@ export default function CreateOrganizationPage() {
|
|
|
186
192
|
fields={fields}
|
|
187
193
|
groups={groups}
|
|
188
194
|
entityId={E.directory.organization}
|
|
189
|
-
initialValues={{ tenantId: selectedTenantId ?? null, name: '', parentId: '', childIds: [], isActive: true }}
|
|
195
|
+
initialValues={{ tenantId: selectedTenantId ?? null, name: '', slug: '', parentId: '', childIds: [], isActive: true }}
|
|
190
196
|
submitLabel={t('directory.organizations.form.action.create', 'Create')}
|
|
191
197
|
cancelHref="/backend/directory/organizations"
|
|
192
198
|
successRedirect={`/backend/directory/organizations?flash=${successMessage}&type=success`}
|
|
@@ -208,6 +214,7 @@ export default function CreateOrganizationPage() {
|
|
|
208
214
|
|
|
209
215
|
type CreateOrganizationPayload = {
|
|
210
216
|
name: string
|
|
217
|
+
slug?: string | null
|
|
211
218
|
isActive: boolean
|
|
212
219
|
parentId: string | null
|
|
213
220
|
childIds: string[]
|
|
@@ -261,6 +268,11 @@ export async function submitCreateOrganization(options: {
|
|
|
261
268
|
childIds: Array.isArray(values.childIds) ? values.childIds.filter((id): id is string => typeof id === 'string') : [],
|
|
262
269
|
}
|
|
263
270
|
|
|
271
|
+
if (typeof values.slug === 'string') {
|
|
272
|
+
const trimmedSlug = values.slug.trim()
|
|
273
|
+
if (trimmedSlug.length) payload.slug = trimmedSlug
|
|
274
|
+
}
|
|
275
|
+
|
|
264
276
|
if (tenantValue) payload.tenantId = tenantValue
|
|
265
277
|
if (Object.keys(customFields).length > 0) payload.customFields = customFields
|
|
266
278
|
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"directory.organizations.form.field.isActive": "Aktiv",
|
|
28
28
|
"directory.organizations.form.field.name": "Name",
|
|
29
29
|
"directory.organizations.form.field.parent": "Übergeordnete Organisation",
|
|
30
|
+
"directory.organizations.form.field.slug": "Slug",
|
|
31
|
+
"directory.organizations.form.field.slug.description": "URL-sicherer Bezeichner für das Kundenportal (Kleinbuchstaben, Zahlen, Bindestriche, Unterstriche).",
|
|
30
32
|
"directory.organizations.form.field.tenant": "Mandant",
|
|
31
33
|
"directory.organizations.form.group.customFields": "Benutzerdefinierte Daten",
|
|
32
34
|
"directory.organizations.form.group.details": "Details",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"directory.organizations.form.field.isActive": "Active",
|
|
28
28
|
"directory.organizations.form.field.name": "Name",
|
|
29
29
|
"directory.organizations.form.field.parent": "Parent",
|
|
30
|
+
"directory.organizations.form.field.slug": "Slug",
|
|
31
|
+
"directory.organizations.form.field.slug.description": "URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores).",
|
|
30
32
|
"directory.organizations.form.field.tenant": "Tenant",
|
|
31
33
|
"directory.organizations.form.group.customFields": "Custom Data",
|
|
32
34
|
"directory.organizations.form.group.details": "Details",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"directory.organizations.form.field.isActive": "Activo",
|
|
28
28
|
"directory.organizations.form.field.name": "Nombre",
|
|
29
29
|
"directory.organizations.form.field.parent": "Padre",
|
|
30
|
+
"directory.organizations.form.field.slug": "Slug",
|
|
31
|
+
"directory.organizations.form.field.slug.description": "Identificador seguro para URL usado en el portal de clientes (minúsculas, números, guiones, guiones bajos).",
|
|
30
32
|
"directory.organizations.form.field.tenant": "Inquilino",
|
|
31
33
|
"directory.organizations.form.group.customFields": "Datos personalizados",
|
|
32
34
|
"directory.organizations.form.group.details": "Detalles",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"directory.organizations.form.field.isActive": "Aktywna",
|
|
28
28
|
"directory.organizations.form.field.name": "Nazwa",
|
|
29
29
|
"directory.organizations.form.field.parent": "Organizacja nadrzędna",
|
|
30
|
+
"directory.organizations.form.field.slug": "Identyfikator URL",
|
|
31
|
+
"directory.organizations.form.field.slug.description": "Identyfikator używany w adresie portalu klienta (małe litery, cyfry, myślniki, podkreślenia).",
|
|
30
32
|
"directory.organizations.form.field.tenant": "Najemca",
|
|
31
33
|
"directory.organizations.form.group.customFields": "Dane niestandardowe",
|
|
32
34
|
"directory.organizations.form.group.details": "Szczegóły",
|
|
@@ -23,8 +23,9 @@ export function useMessageDetails(id: string) {
|
|
|
23
23
|
queryClient,
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const invalidateMessageQueries = React.useCallback(
|
|
27
27
|
(payload: Record<string, unknown>) => {
|
|
28
|
+
void queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })
|
|
28
29
|
void queryClient.invalidateQueries({ queryKey: ['messages', 'detail', id] })
|
|
29
30
|
const messageId = typeof payload.messageId === 'string' ? payload.messageId : null
|
|
30
31
|
if (messageId && messageId !== id) {
|
|
@@ -37,9 +38,9 @@ export function useMessageDetails(id: string) {
|
|
|
37
38
|
useAppEvent(
|
|
38
39
|
'messages.message.*',
|
|
39
40
|
(evt) => {
|
|
40
|
-
|
|
41
|
+
invalidateMessageQueries((evt.payload ?? {}) as Record<string, unknown>)
|
|
41
42
|
},
|
|
42
|
-
[
|
|
43
|
+
[invalidateMessageQueries],
|
|
43
44
|
)
|
|
44
45
|
|
|
45
46
|
useAppEvent(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PageMetadata } from '@open-mercato/shared/modules/registry'
|
|
2
|
+
|
|
3
|
+
export const metadata: PageMetadata = {
|
|
4
|
+
requireCustomerAuth: true,
|
|
5
|
+
titleKey: 'portal.dashboard.title',
|
|
6
|
+
title: 'Dashboard',
|
|
7
|
+
nav: {
|
|
8
|
+
label: 'Dashboard',
|
|
9
|
+
labelKey: 'portal.nav.dashboard',
|
|
10
|
+
group: 'main',
|
|
11
|
+
order: 10,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default metadata
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PageMetadata } from '@open-mercato/shared/modules/registry'
|
|
2
|
+
|
|
3
|
+
export const metadata: PageMetadata = {
|
|
4
|
+
requireCustomerAuth: true,
|
|
5
|
+
titleKey: 'portal.nav.profile',
|
|
6
|
+
title: 'Profile',
|
|
7
|
+
nav: {
|
|
8
|
+
label: 'Profile',
|
|
9
|
+
labelKey: 'portal.nav.profile',
|
|
10
|
+
group: 'account',
|
|
11
|
+
order: 10,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default metadata
|
|
@@ -788,10 +788,13 @@ export async function executeCallApi(
|
|
|
788
788
|
// SECURITY.md changelog entry for this fix.
|
|
789
789
|
//
|
|
790
790
|
// The resolution strategy is:
|
|
791
|
-
// 1. Use the workflow instance's `
|
|
792
|
-
// started the instance), when available.
|
|
793
|
-
//
|
|
794
|
-
// the
|
|
791
|
+
// 1. Use the workflow instance's `metadata.initiatedBy` user (whoever
|
|
792
|
+
// manually started the instance), when available. Only this user's
|
|
793
|
+
// current active roles are used — we never fall back to the author
|
|
794
|
+
// when the initiator is known, because that would escalate the
|
|
795
|
+
// initiator's privileges.
|
|
796
|
+
// 2. Fall back to the workflow definition's `createdBy` (author) only
|
|
797
|
+
// when the instance was started by an event trigger with no user.
|
|
795
798
|
// 3. If no traceable principal exists, the activity refuses to run —
|
|
796
799
|
// there is no "system" fallback that bypasses RBAC.
|
|
797
800
|
const resolvedRoleIds = await resolveCallApiRoleIds(apiKeyEm, context.workflowInstance)
|
|
@@ -885,38 +888,28 @@ export type CallApiInstanceLike = {
|
|
|
885
888
|
tenantId: string
|
|
886
889
|
organizationId: string
|
|
887
890
|
definitionId: string
|
|
891
|
+
metadata?: { initiatedBy?: string | null } | null
|
|
888
892
|
}
|
|
889
893
|
|
|
890
|
-
|
|
894
|
+
async function resolveActiveRoleIdsForUser(
|
|
891
895
|
em: any,
|
|
892
|
-
|
|
896
|
+
userId: string,
|
|
897
|
+
scope: { tenantId: string; organizationId: string },
|
|
893
898
|
): Promise<string[]> {
|
|
894
|
-
if (!instance.definitionId) return []
|
|
895
|
-
|
|
896
899
|
const { findOneWithDecryption, findWithDecryption } = await import('@open-mercato/shared/lib/encryption/find')
|
|
897
900
|
const { User, UserRole, Role } = await import('../../auth/data/entities')
|
|
898
|
-
const { WorkflowDefinition } = await import('../data/entities')
|
|
899
901
|
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
id: instance.definitionId,
|
|
904
|
-
tenantId: instance.tenantId,
|
|
905
|
-
}, {}, scope)
|
|
906
|
-
const authorUserId = definition?.createdBy
|
|
907
|
-
if (!authorUserId) return []
|
|
908
|
-
|
|
909
|
-
const author = await findOneWithDecryption(em, User, {
|
|
910
|
-
id: authorUserId,
|
|
911
|
-
tenantId: instance.tenantId,
|
|
902
|
+
const user = await findOneWithDecryption(em, User, {
|
|
903
|
+
id: userId,
|
|
904
|
+
tenantId: scope.tenantId,
|
|
912
905
|
deletedAt: null,
|
|
913
906
|
}, {}, scope)
|
|
914
|
-
if (!
|
|
907
|
+
if (!user) return []
|
|
915
908
|
|
|
916
909
|
const userRoles = await findWithDecryption(
|
|
917
910
|
em,
|
|
918
911
|
UserRole,
|
|
919
|
-
{ user:
|
|
912
|
+
{ user: user.id, deletedAt: null },
|
|
920
913
|
{ populate: ['role'] },
|
|
921
914
|
scope,
|
|
922
915
|
)
|
|
@@ -928,12 +921,47 @@ export async function resolveCallApiRoleIds(
|
|
|
928
921
|
|
|
929
922
|
const scopedRoles = await findWithDecryption(em, Role, {
|
|
930
923
|
id: { $in: roleIds },
|
|
931
|
-
tenantId:
|
|
924
|
+
tenantId: scope.tenantId,
|
|
932
925
|
deletedAt: null,
|
|
933
926
|
}, {}, scope)
|
|
934
927
|
return scopedRoles.map((r: any) => r.id as string)
|
|
935
928
|
}
|
|
936
929
|
|
|
930
|
+
export async function resolveCallApiRoleIds(
|
|
931
|
+
em: any,
|
|
932
|
+
instance: CallApiInstanceLike
|
|
933
|
+
): Promise<string[]> {
|
|
934
|
+
if (!instance.definitionId) return []
|
|
935
|
+
|
|
936
|
+
const { findOneWithDecryption } = await import('@open-mercato/shared/lib/encryption/find')
|
|
937
|
+
const { WorkflowDefinition } = await import('../data/entities')
|
|
938
|
+
|
|
939
|
+
const scope = { tenantId: instance.tenantId, organizationId: instance.organizationId }
|
|
940
|
+
|
|
941
|
+
// 1. Prefer the triggering user (whoever manually started this instance).
|
|
942
|
+
// WorkflowInstance.metadata.initiatedBy is the canonical record of that
|
|
943
|
+
// principal for user-started instances; use their current role set so
|
|
944
|
+
// CALL_API never exceeds the initiator's permissions. Refuse if the
|
|
945
|
+
// initiator has no active scoped roles — do not fall back to the
|
|
946
|
+
// definition author, which would escalate the initiator's privileges.
|
|
947
|
+
const initiatorUserId = instance.metadata?.initiatedBy ?? null
|
|
948
|
+
if (initiatorUserId) {
|
|
949
|
+
return resolveActiveRoleIdsForUser(em, initiatorUserId, scope)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// 2. Event-triggered instance with no human initiator: fall back to the
|
|
953
|
+
// definition author. Soft-deleted definitions must not mint keys.
|
|
954
|
+
const definition = await findOneWithDecryption(em, WorkflowDefinition, {
|
|
955
|
+
id: instance.definitionId,
|
|
956
|
+
tenantId: instance.tenantId,
|
|
957
|
+
deletedAt: null,
|
|
958
|
+
}, {}, scope)
|
|
959
|
+
const authorUserId = definition?.createdBy
|
|
960
|
+
if (!authorUserId) return []
|
|
961
|
+
|
|
962
|
+
return resolveActiveRoleIdsForUser(em, authorUserId, scope)
|
|
963
|
+
}
|
|
964
|
+
|
|
937
965
|
/**
|
|
938
966
|
* Build full API URL from endpoint
|
|
939
967
|
* - Relative paths (/api/...) → prepend APP_URL
|