@open-mercato/core 0.6.6-develop.6522.1.adb260d33a → 0.6.6-develop.6525.1.e9a4b56963
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/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +5 -5
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id]/send-reset-link.js +4 -4
- package/dist/modules/customer_accounts/api/admin/users/[id]/send-reset-link.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js +20 -8
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id].js +16 -9
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customer_accounts/services/customerUserService.js +22 -5
- package/dist/modules/customer_accounts/services/customerUserService.js.map +2 -2
- package/dist/modules/workflows/lib/graph-utils.js +3 -3
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +5 -5
- package/src/modules/customer_accounts/api/admin/users/[id]/send-reset-link.ts +4 -4
- package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +20 -8
- package/src/modules/customer_accounts/api/admin/users/[id].ts +16 -9
- package/src/modules/customer_accounts/services/customerUserService.ts +29 -5
- package/src/modules/workflows/lib/graph-utils.ts +9 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -29,7 +29,7 @@ async function POST(req, { params }) {
|
|
|
29
29
|
const customerUserService = container.resolve("customerUserService");
|
|
30
30
|
const customerSessionService = container.resolve("customerSessionService");
|
|
31
31
|
const em = container.resolve("em");
|
|
32
|
-
const user = await customerUserService.findById(params.id, auth.tenantId);
|
|
32
|
+
const user = await customerUserService.findById(params.id, auth.tenantId, auth.orgId);
|
|
33
33
|
if (!user) {
|
|
34
34
|
return NextResponse.json({ ok: false, error: "User not found" }, { status: 404 });
|
|
35
35
|
}
|
|
@@ -41,14 +41,14 @@ async function POST(req, { params }) {
|
|
|
41
41
|
id: user.id,
|
|
42
42
|
recipientUserId: user.id,
|
|
43
43
|
email: user.email,
|
|
44
|
-
tenantId:
|
|
45
|
-
organizationId:
|
|
44
|
+
tenantId: user.tenantId,
|
|
45
|
+
organizationId: user.organizationId,
|
|
46
46
|
resetBy: auth.sub
|
|
47
47
|
}).catch(() => void 0);
|
|
48
48
|
void emitCustomerAccountsEvent("customer_accounts.password.changed", {
|
|
49
49
|
userId: user.id,
|
|
50
|
-
tenantId:
|
|
51
|
-
organizationId:
|
|
50
|
+
tenantId: user.tenantId,
|
|
51
|
+
organizationId: user.organizationId,
|
|
52
52
|
changedBy: "admin",
|
|
53
53
|
changedById: auth.sub,
|
|
54
54
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customer_accounts/api/admin/users/%5Bid%5D/reset-password.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { adminResetPasswordSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = adminResetPasswordSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const em = container.resolve('em') as EntityManager\n const user = await customerUserService.findById(params.id, auth.tenantId
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAGlB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAIvC,SAAS,gCAAgC;AACzC,SAAS,iCAAiC;AAEnC,MAAM,WAAW,CAAC;AAEzB,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA+B;AAC/E,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,cAAc,UAAU,QAAQ,aAAa;AACnD,QAAM,YAAY,MAAM,YAAY,mBAAmB,KAAK,KAAK,CAAC,0BAA0B,GAAG,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM,CAAC;AACtJ,MAAI,CAAC,WAAW;AACd,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxF;AAEA,QAAM,SAAS,yBAAyB,UAAU,IAAI;AACtD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,qBAAqB,SAAS,OAAO,MAAM,QAAQ,EAAE,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClI;AAEA,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,yBAAyB,UAAU,QAAQ,wBAAwB;AACzE,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,OAAO,MAAM,oBAAoB,SAAS,OAAO,IAAI,KAAK,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { adminResetPasswordSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = adminResetPasswordSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n const em = container.resolve('em') as EntityManager\n const user = await customerUserService.findById(params.id, auth.tenantId!, auth.orgId)\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n await em.transactional(async (trx) => {\n await customerUserService.updatePassword(user, parsed.data.newPassword, trx)\n await customerSessionService.revokeAllUserSessions(user.id, trx)\n })\n\n void emitCustomerAccountsEvent('customer_accounts.password.reset', {\n id: user.id,\n recipientUserId: user.id,\n email: user.email,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n resetBy: auth.sub,\n }).catch(() => undefined)\n\n void emitCustomerAccountsEvent('customer_accounts.password.changed', {\n userId: user.id,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n changedBy: 'admin',\n changedById: auth.sub,\n at: new Date().toISOString(),\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Reset customer user password (admin)',\n description: 'Allows staff to set a new password for a customer user.',\n tags: ['Customer Accounts Admin'],\n requestBody: { schema: adminResetPasswordSchema },\n responses: [{ status: 200, description: 'Password reset', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Reset customer user password (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,19 +16,19 @@ async function POST(req, { params }) {
|
|
|
16
16
|
return NextResponse.json({ ok: false, error: "Insufficient permissions" }, { status: 403 });
|
|
17
17
|
}
|
|
18
18
|
const customerUserService = container.resolve("customerUserService");
|
|
19
|
-
const user = await customerUserService.findById(params.id, auth.tenantId);
|
|
19
|
+
const user = await customerUserService.findById(params.id, auth.tenantId, auth.orgId);
|
|
20
20
|
if (!user) {
|
|
21
21
|
return NextResponse.json({ ok: false, error: "User not found" }, { status: 404 });
|
|
22
22
|
}
|
|
23
23
|
const customerTokenService = container.resolve("customerTokenService");
|
|
24
|
-
const rawToken = await customerTokenService.createPasswordReset(user.id,
|
|
24
|
+
const rawToken = await customerTokenService.createPasswordReset(user.id, user.tenantId);
|
|
25
25
|
const resetLink = `/portal/reset-password?token=${rawToken}`;
|
|
26
26
|
void emitCustomerAccountsEvent("customer_accounts.password.reset", {
|
|
27
27
|
id: user.id,
|
|
28
28
|
recipientUserId: user.id,
|
|
29
29
|
email: user.email,
|
|
30
|
-
tenantId:
|
|
31
|
-
organizationId:
|
|
30
|
+
tenantId: user.tenantId,
|
|
31
|
+
organizationId: user.organizationId,
|
|
32
32
|
resetBy: auth.sub
|
|
33
33
|
}).catch(() => void 0);
|
|
34
34
|
return NextResponse.json({ ok: true, resetLink });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customer_accounts/api/admin/users/%5Bid%5D/send-reset-link.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const user = await customerUserService.findById(params.id, auth.tenantId
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAIvC,SAAS,iCAAiC;AAEnC,MAAM,WAAW,CAAC;AAEzB,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA+B;AAC/E,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,cAAc,UAAU,QAAQ,aAAa;AACnD,QAAM,YAAY,MAAM,YAAY,mBAAmB,KAAK,KAAK,CAAC,0BAA0B,GAAG,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM,CAAC;AACtJ,MAAI,CAAC,WAAW;AACd,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,OAAO,MAAM,oBAAoB,SAAS,OAAO,IAAI,KAAK,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const user = await customerUserService.findById(params.id, auth.tenantId!, auth.orgId)\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n const rawToken = await customerTokenService.createPasswordReset(user.id, user.tenantId)\n\n const resetLink = `/portal/reset-password?token=${rawToken}`\n\n void emitCustomerAccountsEvent('customer_accounts.password.reset', {\n id: user.id,\n recipientUserId: user.id,\n email: user.email,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n resetBy: auth.sub,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true, resetLink })\n}\n\nconst successSchema = z.object({ ok: z.literal(true), resetLink: z.string() })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Send password reset link for customer user (admin)',\n description: 'Creates a password reset token for a customer user and returns a reset link URL. The admin must prepend the appropriate portal domain/slug to the relative URL.',\n tags: ['Customer Accounts Admin'],\n responses: [{ status: 200, description: 'Reset link generated', schema: successSchema }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Send password reset link for customer user (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAIvC,SAAS,iCAAiC;AAEnC,MAAM,WAAW,CAAC;AAEzB,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA+B;AAC/E,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,cAAc,UAAU,QAAQ,aAAa;AACnD,QAAM,YAAY,MAAM,YAAY,mBAAmB,KAAK,KAAK,CAAC,0BAA0B,GAAG,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM,CAAC;AACtJ,MAAI,CAAC,WAAW;AACd,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,sBAAsB,UAAU,QAAQ,qBAAqB;AACnE,QAAM,OAAO,MAAM,oBAAoB,SAAS,OAAO,IAAI,KAAK,UAAW,KAAK,KAAK;AACrF,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClF;AAEA,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AACrE,QAAM,WAAW,MAAM,qBAAqB,oBAAoB,KAAK,IAAI,KAAK,QAAQ;AAEtF,QAAM,YAAY,gCAAgC,QAAQ;AAE1D,OAAK,0BAA0B,oCAAoC;AAAA,IACjE,IAAI,KAAK;AAAA,IACT,iBAAiB,KAAK;AAAA,IACtB,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,IACrB,SAAS,KAAK;AAAA,EAChB,CAAC,EAAE,MAAM,MAAM,MAAS;AAExB,SAAO,aAAa,KAAK,EAAE,IAAI,MAAM,UAAU,CAAC;AAClD;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,GAAG,WAAW,EAAE,OAAO,EAAE,CAAC;AAC7E,MAAM,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;AAExE,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,yBAAyB;AAAA,EAChC,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,cAAc,CAAC;AAAA,EACvF,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,YAAY;AAAA,IACrE,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,YAAY;AAAA,IAC5E,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,YAAY;AAAA,EACpE;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,EAC9C,SAAS,EAAE,MAAM,UAAU;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,7 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { CustomerUser } from "@open-mercato/core/modules/customer_accounts/data/entities";
|
|
6
6
|
import { emitCustomerAccountsEvent } from "@open-mercato/core/modules/customer_accounts/events";
|
|
7
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
7
8
|
const metadata = {};
|
|
8
9
|
async function POST(req, { params }) {
|
|
9
10
|
const auth = await getAuthFromRequest(req);
|
|
@@ -17,24 +18,35 @@ async function POST(req, { params }) {
|
|
|
17
18
|
return NextResponse.json({ ok: false, error: "Insufficient permissions" }, { status: 403 });
|
|
18
19
|
}
|
|
19
20
|
const em = container.resolve("em");
|
|
20
|
-
const user = await
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const user = await findOneWithDecryption(
|
|
22
|
+
em,
|
|
23
|
+
CustomerUser,
|
|
24
|
+
{
|
|
25
|
+
id: params.id,
|
|
26
|
+
tenantId: auth.tenantId,
|
|
27
|
+
organizationId: auth.orgId,
|
|
28
|
+
deletedAt: null
|
|
29
|
+
},
|
|
30
|
+
void 0,
|
|
31
|
+
{ tenantId: auth.tenantId, organizationId: auth.orgId }
|
|
32
|
+
);
|
|
25
33
|
if (!user) {
|
|
26
34
|
return NextResponse.json({ ok: false, error: "User not found" }, { status: 404 });
|
|
27
35
|
}
|
|
28
36
|
if (user.emailVerifiedAt) {
|
|
29
37
|
return NextResponse.json({ ok: true, alreadyVerified: true });
|
|
30
38
|
}
|
|
31
|
-
await em.nativeUpdate(CustomerUser, {
|
|
39
|
+
await em.nativeUpdate(CustomerUser, {
|
|
40
|
+
id: user.id,
|
|
41
|
+
tenantId: user.tenantId,
|
|
42
|
+
organizationId: user.organizationId
|
|
43
|
+
}, { emailVerifiedAt: /* @__PURE__ */ new Date() });
|
|
32
44
|
void emitCustomerAccountsEvent("customer_accounts.email.verified", {
|
|
33
45
|
id: user.id,
|
|
34
46
|
recipientUserId: user.id,
|
|
35
47
|
email: user.email,
|
|
36
|
-
tenantId:
|
|
37
|
-
organizationId:
|
|
48
|
+
tenantId: user.tenantId,
|
|
49
|
+
organizationId: user.organizationId,
|
|
38
50
|
verifiedBy: auth.sub
|
|
39
51
|
}).catch(() => void 0);
|
|
40
52
|
return NextResponse.json({ ok: true });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customer_accounts/api/admin/users/%5Bid%5D/verify-email.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await em
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,oBAAoB;AAC7B,SAAS,iCAAiC;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const metadata = {}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n {\n id: params.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n if (user.emailVerifiedAt) {\n return NextResponse.json({ ok: true, alreadyVerified: true })\n }\n\n await em.nativeUpdate(CustomerUser, {\n id: user.id,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n }, { emailVerifiedAt: new Date() })\n\n void emitCustomerAccountsEvent('customer_accounts.email.verified', {\n id: user.id,\n recipientUserId: user.id,\n email: user.email,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n verifiedBy: auth.sub,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true), alreadyVerified: z.boolean().optional() })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Verify customer user email (admin)',\n description: 'Allows staff to manually mark a customer user email as verified.',\n tags: ['Customer Accounts Admin'],\n responses: [{ status: 200, description: 'Email verified', schema: successSchema }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Verify customer user email (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -29,7 +29,7 @@ async function GET(req, { params }) {
|
|
|
29
29
|
const user = await findOneWithDecryption(
|
|
30
30
|
em,
|
|
31
31
|
CustomerUser,
|
|
32
|
-
{ id: params.id, tenantId: auth.tenantId, deletedAt: null },
|
|
32
|
+
{ id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null },
|
|
33
33
|
void 0,
|
|
34
34
|
{ tenantId: auth.tenantId, organizationId: auth.orgId }
|
|
35
35
|
);
|
|
@@ -109,7 +109,7 @@ async function PUT(req, { params }) {
|
|
|
109
109
|
const user = await findOneWithDecryption(
|
|
110
110
|
em,
|
|
111
111
|
CustomerUser,
|
|
112
|
-
{ id: params.id, tenantId: auth.tenantId, deletedAt: null },
|
|
112
|
+
{ id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null },
|
|
113
113
|
void 0,
|
|
114
114
|
{ tenantId: auth.tenantId, organizationId: auth.orgId }
|
|
115
115
|
);
|
|
@@ -143,7 +143,11 @@ async function PUT(req, { params }) {
|
|
|
143
143
|
if (parsed.data.lockedUntil !== void 0) updates.lockedUntil = parsed.data.lockedUntil ? new Date(parsed.data.lockedUntil) : null;
|
|
144
144
|
if (parsed.data.personEntityId !== void 0) updates.personEntityId = parsed.data.personEntityId;
|
|
145
145
|
if (parsed.data.customerEntityId !== void 0) updates.customerEntityId = parsed.data.customerEntityId;
|
|
146
|
-
await em.nativeUpdate(CustomerUser, {
|
|
146
|
+
await em.nativeUpdate(CustomerUser, {
|
|
147
|
+
id: user.id,
|
|
148
|
+
tenantId: user.tenantId,
|
|
149
|
+
organizationId: user.organizationId
|
|
150
|
+
}, updates);
|
|
147
151
|
let rolesChanged = false;
|
|
148
152
|
if (parsed.data.roleIds !== void 0) {
|
|
149
153
|
const requestedRoleIds = parsed.data.roleIds;
|
|
@@ -184,8 +188,8 @@ async function PUT(req, { params }) {
|
|
|
184
188
|
id: user.id,
|
|
185
189
|
recipientUserId: user.id,
|
|
186
190
|
email: user.email,
|
|
187
|
-
tenantId:
|
|
188
|
-
organizationId:
|
|
191
|
+
tenantId: user.tenantId,
|
|
192
|
+
organizationId: user.organizationId,
|
|
189
193
|
updatedBy: auth.sub
|
|
190
194
|
}).catch(() => void 0);
|
|
191
195
|
return NextResponse.json({ ok: true, updatedAt: nextUpdatedAt.toISOString() });
|
|
@@ -205,7 +209,7 @@ async function DELETE(req, { params }) {
|
|
|
205
209
|
const user = await findOneWithDecryption(
|
|
206
210
|
em,
|
|
207
211
|
CustomerUser,
|
|
208
|
-
{ id: params.id, tenantId: auth.tenantId, deletedAt: null },
|
|
212
|
+
{ id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null },
|
|
209
213
|
void 0,
|
|
210
214
|
{ tenantId: auth.tenantId, organizationId: auth.orgId }
|
|
211
215
|
);
|
|
@@ -225,13 +229,16 @@ async function DELETE(req, { params }) {
|
|
|
225
229
|
}
|
|
226
230
|
const customerUserService = container.resolve("customerUserService");
|
|
227
231
|
const customerSessionService = container.resolve("customerSessionService");
|
|
228
|
-
await customerUserService.softDelete(user.id
|
|
232
|
+
await customerUserService.softDelete(user.id, {
|
|
233
|
+
tenantId: user.tenantId,
|
|
234
|
+
organizationId: user.organizationId
|
|
235
|
+
});
|
|
229
236
|
await customerSessionService.revokeAllUserSessions(user.id);
|
|
230
237
|
void emitCustomerAccountsEvent("customer_accounts.user.deleted", {
|
|
231
238
|
id: user.id,
|
|
232
239
|
email: user.email,
|
|
233
|
-
tenantId:
|
|
234
|
-
organizationId:
|
|
240
|
+
tenantId: user.tenantId,
|
|
241
|
+
organizationId: user.organizationId,
|
|
235
242
|
deletedBy: auth.sub
|
|
236
243
|
}).catch(() => void 0);
|
|
237
244
|
return NextResponse.json({ ok: true });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customer_accounts/api/admin/users/%5Bid%5D.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUser, CustomerUserRole, CustomerRole, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { adminUpdateUserSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOwnedCompanyEntity } from '@open-mercato/core/modules/customer_accounts/lib/customerEntityOwnership'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\nexport const metadata = {}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n if (!UUID_RE.test(params.id)) {\n return NextResponse.json({ ok: false, error: 'Invalid user ID' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.view'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n { id: params.id, tenantId: auth.tenantId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n const userRoles = await findWithDecryption(\n em,\n CustomerUserRole,\n { user: user.id as any, deletedAt: null } as any,\n { populate: ['role'] },\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n const roles = userRoles.map((ur) => ({\n id: (ur.role as any).id,\n name: (ur.role as any).name,\n slug: (ur.role as any).slug,\n }))\n\n const activeSessions = await findWithDecryption(\n em,\n CustomerUserSession,\n {\n user: user.id as any,\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n } as any,\n { orderBy: { lastUsedAt: 'DESC' } },\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n\n const sessions = activeSessions.map((session) => ({\n id: session.id,\n ipAddress: (session as any).ipAddress || null,\n userAgent: (session as any).userAgent || null,\n lastUsedAt: (session as any).lastUsedAt || null,\n createdAt: session.createdAt,\n expiresAt: session.expiresAt,\n }))\n\n return NextResponse.json({\n ok: true,\n id: user.id,\n email: user.email,\n displayName: user.displayName,\n emailVerifiedAt: user.emailVerifiedAt || null,\n isActive: user.isActive,\n lockedUntil: user.lockedUntil || null,\n lastLoginAt: user.lastLoginAt || null,\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt || null,\n roles,\n sessions,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = adminUpdateUserSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n { id: params.id, tenantId: auth.tenantId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n // Optimistic lock: refuse a stale overwrite so two admins editing the same\n // customer user in parallel cannot silently clobber each other (#2055). The\n // check is strictly additive \u2014 a no-op when the client sends no expected-version header.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.user',\n resourceId: user.id,\n current: user.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n // Reject a customerEntityId the caller does not own before persisting it.\n // Without this check a mislinked company FK cross-links the user into another\n // org/company's portal context and persists indefinitely (#2693). A null value\n // (unlink) needs no ownership check.\n if (parsed.data.customerEntityId) {\n const owned = await isOwnedCompanyEntity(em, parsed.data.customerEntityId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!owned) {\n return NextResponse.json({ ok: false, error: 'Company not found' }, { status: 400 })\n }\n }\n\n // Always bump updated_at so the optimistic-lock version advances on every save.\n // `nativeUpdate` bypasses MikroORM's `onUpdate` hook, so set it explicitly \u2014 without\n // this the version never changes and concurrent edits cannot be detected (#2055).\n const nextUpdatedAt = new Date()\n const updates: Record<string, unknown> = { updatedAt: nextUpdatedAt }\n if (parsed.data.displayName !== undefined) updates.displayName = parsed.data.displayName\n if (parsed.data.isActive !== undefined) updates.isActive = parsed.data.isActive\n if (parsed.data.lockedUntil !== undefined) updates.lockedUntil = parsed.data.lockedUntil ? new Date(parsed.data.lockedUntil) : null\n if (parsed.data.personEntityId !== undefined) updates.personEntityId = parsed.data.personEntityId\n if (parsed.data.customerEntityId !== undefined) updates.customerEntityId = parsed.data.customerEntityId\n\n await em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n\n let rolesChanged = false\n if (parsed.data.roleIds !== undefined) {\n const requestedRoleIds = parsed.data.roleIds\n // Scope role resolution to the caller's organization too \u2014 CustomerRole is\n // org-scoped, so a tenant-only filter would let an admin link roles from\n // another org in the same tenant (#2693).\n const validRoles = requestedRoleIds.length > 0\n ? await findWithDecryption(\n em,\n CustomerRole,\n {\n id: { $in: requestedRoleIds } as any,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n : []\n if (validRoles.length !== requestedRoleIds.length) {\n const foundIds = new Set(validRoles.map((role) => role.id))\n const missingId = requestedRoleIds.find((roleId) => !foundIds.has(roleId))\n return NextResponse.json({ ok: false, error: `Role ${missingId} not found` }, { status: 400 })\n }\n\n await em.nativeDelete(CustomerUserRole, { user: user.id } as Record<string, unknown>)\n\n for (const role of validRoles) {\n const userRole = em.create(CustomerUserRole, {\n user,\n role,\n createdAt: new Date(),\n } as any)\n em.persist(userRole)\n }\n await em.flush()\n rolesChanged = true\n }\n\n if (rolesChanged) {\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n await customerRbacService.invalidateUserCache(user.id)\n }\n\n void emitCustomerAccountsEvent('customer_accounts.user.updated', {\n id: user.id,\n recipientUserId: user.id,\n email: user.email,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n updatedBy: auth.sub,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true, updatedAt: nextUpdatedAt.toISOString() })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n { id: params.id, tenantId: auth.tenantId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n // Optimistic lock: refuse a stale delete (e.g. deleting a record another admin\n // already modified). Strictly additive \u2014 a no-op without the expected-version header.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.user',\n resourceId: user.id,\n current: user.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n\n await customerUserService.softDelete(user.id)\n await customerSessionService.revokeAllUserSessions(user.id)\n\n void emitCustomerAccountsEvent('customer_accounts.user.deleted', {\n id: user.id,\n email: user.email,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedBy: auth.sub,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst roleSchema = z.object({ id: z.string().uuid(), name: z.string(), slug: z.string() })\nconst userDetailSchema = z.object({\n id: z.string().uuid(),\n email: z.string(),\n displayName: z.string(),\n emailVerified: z.boolean(),\n isActive: z.boolean(),\n lockedUntil: z.string().datetime().nullable(),\n lastLoginAt: z.string().datetime().nullable(),\n failedLoginAttempts: z.number(),\n customerEntityId: z.string().uuid().nullable(),\n personEntityId: z.string().uuid().nullable(),\n createdAt: z.string().datetime(),\n updatedAt: z.string().datetime().nullable(),\n roles: z.array(roleSchema),\n activeSessionCount: z.number(),\n})\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst getMethodDoc: OpenApiMethodDoc = {\n summary: 'Get customer user detail (admin)',\n description: 'Returns full customer user details including CRM links, roles, and active session count.',\n tags: ['Customer Accounts Admin'],\n responses: [{\n status: 200,\n description: 'User detail',\n schema: z.object({ ok: z.literal(true), user: userDetailSchema }),\n }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nconst putMethodDoc: OpenApiMethodDoc = {\n summary: 'Update customer user (admin)',\n description: 'Updates a customer user. Staff can update status, lock, CRM links, and roles. Role assignment bypasses customer_assignable check.',\n tags: ['Customer Accounts Admin'],\n requestBody: { schema: adminUpdateUserSchema },\n responses: [{ status: 200, description: 'User updated', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed or role not found', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nconst deleteMethodDoc: OpenApiMethodDoc = {\n summary: 'Delete customer user (admin)',\n description: 'Soft deletes a customer user and revokes all their active sessions.',\n tags: ['Customer Accounts Admin'],\n responses: [{ status: 200, description: 'User deleted', schema: successSchema }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer user detail management (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: getMethodDoc,\n PUT: putMethodDoc,\n DELETE: deleteMethodDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,cAAc,kBAAkB,cAAc,2BAA2B;AAIlF,SAAS,6BAA6B;AACtC,SAAS,iCAAiC;AAC1C,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,4BAA4B;AACrC,SAAS,8CAA8C;AACvD,SAAS,uBAAuB;AAEzB,MAAM,WAAW,CAAC;AAEzB,MAAM,UAAU;AAEhB,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA+B;AAC9E,MAAI,CAAC,QAAQ,KAAK,OAAO,EAAE,GAAG;AAC5B,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnF;AAEA,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,cAAc,UAAU,QAAQ,aAAa;AACnD,QAAM,YAAY,MAAM,YAAY,mBAAmB,KAAK,KAAK,CAAC,wBAAwB,GAAG,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM,CAAC;AACpJ,MAAI,CAAC,WAAW;AACd,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,KAAK,UAAU,QAAQ,IAAI;AAEjC,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,OAAO,IAAI,UAAU,KAAK,UAAU,WAAW,KAAK;AAAA,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerUser, CustomerUserRole, CustomerRole, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { adminUpdateUserSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOwnedCompanyEntity } from '@open-mercato/core/modules/customer_accounts/lib/customerEntityOwnership'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\nexport const metadata = {}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n if (!UUID_RE.test(params.id)) {\n return NextResponse.json({ ok: false, error: 'Invalid user ID' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.view'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n { id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n const userRoles = await findWithDecryption(\n em,\n CustomerUserRole,\n { user: user.id as any, deletedAt: null } as any,\n { populate: ['role'] },\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n const roles = userRoles.map((ur) => ({\n id: (ur.role as any).id,\n name: (ur.role as any).name,\n slug: (ur.role as any).slug,\n }))\n\n const activeSessions = await findWithDecryption(\n em,\n CustomerUserSession,\n {\n user: user.id as any,\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n } as any,\n { orderBy: { lastUsedAt: 'DESC' } },\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n\n const sessions = activeSessions.map((session) => ({\n id: session.id,\n ipAddress: (session as any).ipAddress || null,\n userAgent: (session as any).userAgent || null,\n lastUsedAt: (session as any).lastUsedAt || null,\n createdAt: session.createdAt,\n expiresAt: session.expiresAt,\n }))\n\n return NextResponse.json({\n ok: true,\n id: user.id,\n email: user.email,\n displayName: user.displayName,\n emailVerifiedAt: user.emailVerifiedAt || null,\n isActive: user.isActive,\n lockedUntil: user.lockedUntil || null,\n lastLoginAt: user.lastLoginAt || null,\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt || null,\n roles,\n sessions,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = adminUpdateUserSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n { id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n // Optimistic lock: refuse a stale overwrite so two admins editing the same\n // customer user in parallel cannot silently clobber each other (#2055). The\n // check is strictly additive \u2014 a no-op when the client sends no expected-version header.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.user',\n resourceId: user.id,\n current: user.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n // Reject a customerEntityId the caller does not own before persisting it.\n // Without this check a mislinked company FK cross-links the user into another\n // org/company's portal context and persists indefinitely (#2693). A null value\n // (unlink) needs no ownership check.\n if (parsed.data.customerEntityId) {\n const owned = await isOwnedCompanyEntity(em, parsed.data.customerEntityId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!owned) {\n return NextResponse.json({ ok: false, error: 'Company not found' }, { status: 400 })\n }\n }\n\n // Always bump updated_at so the optimistic-lock version advances on every save.\n // `nativeUpdate` bypasses MikroORM's `onUpdate` hook, so set it explicitly \u2014 without\n // this the version never changes and concurrent edits cannot be detected (#2055).\n const nextUpdatedAt = new Date()\n const updates: Record<string, unknown> = { updatedAt: nextUpdatedAt }\n if (parsed.data.displayName !== undefined) updates.displayName = parsed.data.displayName\n if (parsed.data.isActive !== undefined) updates.isActive = parsed.data.isActive\n if (parsed.data.lockedUntil !== undefined) updates.lockedUntil = parsed.data.lockedUntil ? new Date(parsed.data.lockedUntil) : null\n if (parsed.data.personEntityId !== undefined) updates.personEntityId = parsed.data.personEntityId\n if (parsed.data.customerEntityId !== undefined) updates.customerEntityId = parsed.data.customerEntityId\n\n await em.nativeUpdate(CustomerUser, {\n id: user.id,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n }, updates)\n\n let rolesChanged = false\n if (parsed.data.roleIds !== undefined) {\n const requestedRoleIds = parsed.data.roleIds\n // Scope role resolution to the caller's organization too \u2014 CustomerRole is\n // org-scoped, so a tenant-only filter would let an admin link roles from\n // another org in the same tenant (#2693).\n const validRoles = requestedRoleIds.length > 0\n ? await findWithDecryption(\n em,\n CustomerRole,\n {\n id: { $in: requestedRoleIds } as any,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n : []\n if (validRoles.length !== requestedRoleIds.length) {\n const foundIds = new Set(validRoles.map((role) => role.id))\n const missingId = requestedRoleIds.find((roleId) => !foundIds.has(roleId))\n return NextResponse.json({ ok: false, error: `Role ${missingId} not found` }, { status: 400 })\n }\n\n await em.nativeDelete(CustomerUserRole, { user: user.id } as Record<string, unknown>)\n\n for (const role of validRoles) {\n const userRole = em.create(CustomerUserRole, {\n user,\n role,\n createdAt: new Date(),\n } as any)\n em.persist(userRole)\n }\n await em.flush()\n rolesChanged = true\n }\n\n if (rolesChanged) {\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n await customerRbacService.invalidateUserCache(user.id)\n }\n\n void emitCustomerAccountsEvent('customer_accounts.user.updated', {\n id: user.id,\n recipientUserId: user.id,\n email: user.email,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n updatedBy: auth.sub,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true, updatedAt: nextUpdatedAt.toISOString() })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n { id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!user) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n // Optimistic lock: refuse a stale delete (e.g. deleting a record another admin\n // already modified). Strictly additive \u2014 a no-op without the expected-version header.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.user',\n resourceId: user.id,\n current: user.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const customerUserService = container.resolve('customerUserService') as CustomerUserService\n const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService\n\n await customerUserService.softDelete(user.id, {\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n })\n await customerSessionService.revokeAllUserSessions(user.id)\n\n void emitCustomerAccountsEvent('customer_accounts.user.deleted', {\n id: user.id,\n email: user.email,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n deletedBy: auth.sub,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst roleSchema = z.object({ id: z.string().uuid(), name: z.string(), slug: z.string() })\nconst userDetailSchema = z.object({\n id: z.string().uuid(),\n email: z.string(),\n displayName: z.string(),\n emailVerified: z.boolean(),\n isActive: z.boolean(),\n lockedUntil: z.string().datetime().nullable(),\n lastLoginAt: z.string().datetime().nullable(),\n failedLoginAttempts: z.number(),\n customerEntityId: z.string().uuid().nullable(),\n personEntityId: z.string().uuid().nullable(),\n createdAt: z.string().datetime(),\n updatedAt: z.string().datetime().nullable(),\n roles: z.array(roleSchema),\n activeSessionCount: z.number(),\n})\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst getMethodDoc: OpenApiMethodDoc = {\n summary: 'Get customer user detail (admin)',\n description: 'Returns full customer user details including CRM links, roles, and active session count.',\n tags: ['Customer Accounts Admin'],\n responses: [{\n status: 200,\n description: 'User detail',\n schema: z.object({ ok: z.literal(true), user: userDetailSchema }),\n }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nconst putMethodDoc: OpenApiMethodDoc = {\n summary: 'Update customer user (admin)',\n description: 'Updates a customer user. Staff can update status, lock, CRM links, and roles. Role assignment bypasses customer_assignable check.',\n tags: ['Customer Accounts Admin'],\n requestBody: { schema: adminUpdateUserSchema },\n responses: [{ status: 200, description: 'User updated', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed or role not found', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nconst deleteMethodDoc: OpenApiMethodDoc = {\n summary: 'Delete customer user (admin)',\n description: 'Soft deletes a customer user and revokes all their active sessions.',\n tags: ['Customer Accounts Admin'],\n responses: [{ status: 200, description: 'User deleted', schema: successSchema }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer user detail management (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: getMethodDoc,\n PUT: putMethodDoc,\n DELETE: deleteMethodDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -38,8 +38,16 @@ class CustomerUserService {
|
|
|
38
38
|
{ tenantId }
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
|
-
async findById(id, tenantId) {
|
|
42
|
-
|
|
41
|
+
async findById(id, tenantId, organizationId) {
|
|
42
|
+
const where = { id, tenantId, deletedAt: null };
|
|
43
|
+
if (organizationId !== void 0) where.organizationId = organizationId;
|
|
44
|
+
return findOneWithDecryption(
|
|
45
|
+
this.em,
|
|
46
|
+
CustomerUser,
|
|
47
|
+
where,
|
|
48
|
+
void 0,
|
|
49
|
+
{ tenantId, organizationId }
|
|
50
|
+
);
|
|
43
51
|
}
|
|
44
52
|
async verifyPassword(user, password) {
|
|
45
53
|
if (!user.passwordHash) return false;
|
|
@@ -75,7 +83,11 @@ class CustomerUserService {
|
|
|
75
83
|
}
|
|
76
84
|
async updatePassword(user, newPassword, em) {
|
|
77
85
|
const passwordHash = await hash(newPassword, BCRYPT_COST);
|
|
78
|
-
await (em ?? this.em).nativeUpdate(CustomerUser, {
|
|
86
|
+
await (em ?? this.em).nativeUpdate(CustomerUser, {
|
|
87
|
+
id: user.id,
|
|
88
|
+
tenantId: user.tenantId,
|
|
89
|
+
organizationId: user.organizationId
|
|
90
|
+
}, { passwordHash });
|
|
79
91
|
user.passwordHash = passwordHash;
|
|
80
92
|
}
|
|
81
93
|
async updateProfile(user, data) {
|
|
@@ -85,8 +97,13 @@ class CustomerUserService {
|
|
|
85
97
|
await this.em.nativeUpdate(CustomerUser, { id: user.id }, updates);
|
|
86
98
|
if (data.displayName !== void 0) user.displayName = data.displayName;
|
|
87
99
|
}
|
|
88
|
-
async softDelete(userId) {
|
|
89
|
-
|
|
100
|
+
async softDelete(userId, scope) {
|
|
101
|
+
const where = { id: userId };
|
|
102
|
+
if (scope) {
|
|
103
|
+
where.tenantId = scope.tenantId;
|
|
104
|
+
where.organizationId = scope.organizationId;
|
|
105
|
+
}
|
|
106
|
+
await this.em.nativeUpdate(CustomerUser, where, {
|
|
90
107
|
deletedAt: /* @__PURE__ */ new Date(),
|
|
91
108
|
isActive: false
|
|
92
109
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customer_accounts/services/customerUserService.ts"],
|
|
4
|
-
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { hash, compare } from 'bcryptjs'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { hashForLookup, lookupHashCandidates } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nconst BCRYPT_COST = 10\nconst MAX_FAILED_ATTEMPTS = 5\nconst LOCKOUT_DURATION_MS = 15 * 60 * 1000 // 15 minutes\n\nexport class CustomerUserService {\n constructor(private em: EntityManager) {}\n\n async createUser(\n email: string,\n password: string,\n displayName: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<CustomerUser> {\n const passwordHash = await hash(password, BCRYPT_COST)\n const emailHash = hashForLookup(email)\n const user = this.em.create(CustomerUser, {\n email: email.toLowerCase().trim(),\n emailHash,\n passwordHash,\n displayName,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isActive: true,\n failedLoginAttempts: 0,\n createdAt: new Date(),\n } as any)\n return user as CustomerUser\n }\n\n async findByEmail(email: string, tenantId: string): Promise<CustomerUser | null> {\n return findOneWithDecryption(\n this.em,\n CustomerUser,\n {\n emailHash: { $in: lookupHashCandidates(email) },\n tenantId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId },\n )\n }\n\n async findById(id: string
|
|
5
|
-
"mappings": "AACA,SAAS,MAAM,eAAe;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,eAAe,4BAA4B;AACpD,SAAS,6BAA6B;AAEtC,MAAM,cAAc;AACpB,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB,KAAK,KAAK;AAE/B,MAAM,oBAAoB;AAAA,EAC/B,YAAoB,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAExC,MAAM,WACJ,OACA,UACA,aACA,OACuB;AACvB,UAAM,eAAe,MAAM,KAAK,UAAU,WAAW;AACrD,UAAM,YAAY,cAAc,KAAK;AACrC,UAAM,OAAO,KAAK,GAAG,OAAO,cAAc;AAAA,MACxC,OAAO,MAAM,YAAY,EAAE,KAAK;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,UAAU;AAAA,MACV,qBAAqB;AAAA,MACrB,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAQ;AACR,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,OAAe,UAAgD;AAC/E,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,QACE,WAAW,EAAE,KAAK,qBAAqB,KAAK,EAAE;AAAA,QAC9C;AAAA,QACA,WAAW;AAAA,MACb;AAAA,MACA;AAAA,MACA,EAAE,SAAS;AAAA,IACb;AAAA,EACF;AAAA,EAEA,MAAM,
|
|
4
|
+
"sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { hash, compare } from 'bcryptjs'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { hashForLookup, lookupHashCandidates } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nconst BCRYPT_COST = 10\nconst MAX_FAILED_ATTEMPTS = 5\nconst LOCKOUT_DURATION_MS = 15 * 60 * 1000 // 15 minutes\n\nexport class CustomerUserService {\n constructor(private em: EntityManager) {}\n\n async createUser(\n email: string,\n password: string,\n displayName: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<CustomerUser> {\n const passwordHash = await hash(password, BCRYPT_COST)\n const emailHash = hashForLookup(email)\n const user = this.em.create(CustomerUser, {\n email: email.toLowerCase().trim(),\n emailHash,\n passwordHash,\n displayName,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isActive: true,\n failedLoginAttempts: 0,\n createdAt: new Date(),\n } as any)\n return user as CustomerUser\n }\n\n async findByEmail(email: string, tenantId: string): Promise<CustomerUser | null> {\n return findOneWithDecryption(\n this.em,\n CustomerUser,\n {\n emailHash: { $in: lookupHashCandidates(email) },\n tenantId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId },\n )\n }\n\n async findById(\n id: string,\n tenantId: string,\n organizationId?: string | null,\n ): Promise<CustomerUser | null> {\n const where: Record<string, unknown> = { id, tenantId, deletedAt: null }\n if (organizationId !== undefined) where.organizationId = organizationId\n return findOneWithDecryption(\n this.em,\n CustomerUser,\n where as any,\n undefined,\n { tenantId, organizationId },\n )\n }\n\n async verifyPassword(user: CustomerUser, password: string): Promise<boolean> {\n if (!user.passwordHash) return false\n return compare(password, user.passwordHash)\n }\n\n async updateLastLoginAt(user: CustomerUser): Promise<void> {\n const now = new Date()\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, { lastLoginAt: now })\n user.lastLoginAt = now\n }\n\n checkLockout(user: CustomerUser): boolean {\n if (!user.lockedUntil) return false\n if (user.lockedUntil.getTime() > Date.now()) return true\n return false\n }\n\n async incrementFailedAttempts(user: CustomerUser): Promise<void> {\n const newCount = (user.failedLoginAttempts || 0) + 1\n const updates: Record<string, unknown> = { failedLoginAttempts: newCount }\n if (newCount >= MAX_FAILED_ATTEMPTS) {\n updates.lockedUntil = new Date(Date.now() + LOCKOUT_DURATION_MS)\n }\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n user.failedLoginAttempts = newCount\n if (updates.lockedUntil) user.lockedUntil = updates.lockedUntil as Date\n }\n\n async resetFailedAttempts(user: CustomerUser): Promise<void> {\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, {\n failedLoginAttempts: 0,\n lockedUntil: null,\n })\n user.failedLoginAttempts = 0\n user.lockedUntil = null\n }\n\n async updatePassword(user: CustomerUser, newPassword: string, em?: EntityManager): Promise<void> {\n const passwordHash = await hash(newPassword, BCRYPT_COST)\n await (em ?? this.em).nativeUpdate(CustomerUser, {\n id: user.id,\n tenantId: user.tenantId,\n organizationId: user.organizationId,\n }, { passwordHash })\n user.passwordHash = passwordHash\n }\n\n async updateProfile(user: CustomerUser, data: { displayName?: string }): Promise<void> {\n const updates: Record<string, unknown> = {}\n if (data.displayName !== undefined) updates.displayName = data.displayName\n if (Object.keys(updates).length === 0) return\n await this.em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n if (data.displayName !== undefined) user.displayName = data.displayName\n }\n\n async softDelete(\n userId: string,\n scope?: { tenantId: string; organizationId: string | null },\n ): Promise<void> {\n const where: Record<string, unknown> = { id: userId }\n if (scope) {\n where.tenantId = scope.tenantId\n where.organizationId = scope.organizationId\n }\n await this.em.nativeUpdate(CustomerUser, where, {\n deletedAt: new Date(),\n isActive: false,\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -240,9 +240,9 @@ function calculateSmartLayout(steps, transitions, spacing) {
|
|
|
240
240
|
while (queue.length > 0) {
|
|
241
241
|
const { id, level } = queue.shift();
|
|
242
242
|
const currentLevel = levels.get(id);
|
|
243
|
-
if (currentLevel
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
if (currentLevel !== void 0 && level <= currentLevel) continue;
|
|
244
|
+
levels.set(id, level);
|
|
245
|
+
if (level > steps.length) continue;
|
|
246
246
|
const children = outgoing.get(id) || [];
|
|
247
247
|
for (const child of children) {
|
|
248
248
|
queue.push({ id: child, level: level + 1 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/workflows/lib/graph-utils.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Node, Edge } from '@xyflow/react'\nimport type { WorkflowDefinition } from '../data/entities'\n\n/**\n * Graph Utilities for Visual Workflow Editor\n *\n * Converts between ReactFlow graph representation and workflow definition JSON\n */\n\nexport interface GraphToDefinitionOptions {\n includePositions?: boolean\n}\n\nexport interface DefinitionToGraphOptions {\n autoLayout?: boolean\n layoutSpacing?: { vertical: number; horizontal: number }\n}\n\n/**\n * Convert ReactFlow graph (nodes + edges) to workflow definition JSON\n */\nexport function graphToDefinition(\n nodes: Node[],\n edges: Edge[],\n options: GraphToDefinitionOptions = {}\n): WorkflowDefinition['definition'] {\n // Extract steps from nodes\n const steps = nodes.map((node) => {\n const step: any = {\n stepId: node.id,\n stepName: node.data.label || node.id,\n stepType: mapNodeTypeToStepType(node.type || 'automated'),\n }\n\n // Add step-specific configuration\n if (node.data.description) {\n step.description = node.data.description\n }\n\n // Add timeout if present\n if (node.data.timeout) {\n step.timeout = node.data.timeout\n }\n\n // Add retryPolicy if present\n if (node.data.retryPolicy) {\n step.retryPolicy = node.data.retryPolicy\n }\n\n // Add generic config if present\n if (node.data.config) {\n step.config = node.data.config\n }\n\n // User task configuration\n if (node.type === 'userTask' && node.data) {\n step.userTaskConfig = {\n assignedTo: node.data.assignedTo,\n assignedToRoles: node.data.assignedToRoles || [],\n formKey: node.data.formKey,\n allowedActions: node.data.allowedActions || ['complete', 'cancel'],\n }\n\n // Add form schema if present\n if ((node.data as any).formSchema || (node.data as any).userTaskConfig?.formSchema) {\n step.userTaskConfig.formSchema = (node.data as any).formSchema || (node.data as any).userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if ((node.data as any).assignmentRule || (node.data as any).userTaskConfig?.assignmentRule) {\n step.userTaskConfig.assignmentRule = (node.data as any).assignmentRule || (node.data as any).userTaskConfig.assignmentRule\n }\n\n if ((node.data as any).slaDuration || (node.data as any).userTaskConfig?.slaDuration) {\n step.userTaskConfig.slaDuration = (node.data as any).slaDuration || (node.data as any).userTaskConfig.slaDuration\n }\n\n if ((node.data as any).escalationRules || (node.data as any).userTaskConfig?.escalationRules) {\n step.userTaskConfig.escalationRules = (node.data as any).escalationRules || (node.data as any).userTaskConfig.escalationRules\n }\n }\n\n // Wait for signal configuration\n if (node.type === 'waitForSignal' && node.data.signalConfig) {\n step.signalConfig = node.data.signalConfig\n }\n\n // Step activities (for AUTOMATED steps)\n if (node.type === 'automated' && node.data.activities) {\n step.activities = node.data.activities\n }\n\n // Pre-conditions (for START steps)\n if (node.type === 'start' && (node.data as any).preConditions && (node.data as any).preConditions.length > 0) {\n step.preConditions = (node.data as any).preConditions\n }\n\n // Store position for visual editor\n if (options.includePositions && node.position) {\n step._editorPosition = {\n x: node.position.x,\n y: node.position.y,\n }\n }\n\n return step\n })\n\n // Extract transitions from edges\n const transitions = edges.map((edge) => {\n const edgeData = edge.data as any\n const transition: any = {\n transitionId: edge.id,\n fromStepId: edge.source,\n toStepId: edge.target,\n trigger: edgeData?.trigger || 'auto',\n }\n\n // Add transition name if present\n if (edgeData?.transitionName) {\n transition.transitionName = edgeData.transitionName\n }\n\n // Add priority if present (default 0)\n if (edgeData?.priority !== undefined) {\n transition.priority = edgeData.priority\n }\n\n // Add continueOnActivityFailure if present (default false)\n if (edgeData?.continueOnActivityFailure !== undefined) {\n transition.continueOnActivityFailure = edgeData.continueOnActivityFailure\n }\n\n // Add conditions if present\n if (edgeData?.preConditions && edgeData.preConditions.length > 0) {\n transition.preConditions = edgeData.preConditions\n }\n\n if (edgeData?.postConditions && edgeData.postConditions.length > 0) {\n transition.postConditions = edgeData.postConditions\n }\n\n // Add activities if present in edge data\n if (edgeData?.activities && edgeData.activities.length > 0) {\n transition.activities = edgeData.activities.map((activity: any) => ({\n activityId: activity.activityId,\n activityName: activity.activityName,\n activityType: activity.activityType,\n config: activity.config || {},\n // Include all optional fields\n ...(activity.async !== undefined && { async: activity.async }),\n ...(activity.timeout && { timeout: activity.timeout }),\n ...(activity.retryPolicy && { retryPolicy: activity.retryPolicy }),\n ...(activity.compensate !== undefined && { compensate: activity.compensate }),\n }))\n } else {\n // Check if source node is automated and has activity data\n // If so, place the activity in this transition\n const sourceNode = nodes.find(n => n.id === edge.source)\n if (sourceNode && sourceNode.type === 'automated' && sourceNode.data) {\n if (sourceNode.data.activityType || sourceNode.data.activityId) {\n const activity: any = {\n activityId: sourceNode.data.activityId || `activity_${sourceNode.id}`,\n activityName: sourceNode.data.activityName || sourceNode.data.label || 'Automated Activity',\n activityType: sourceNode.data.activityType || 'CALL_API',\n config: sourceNode.data.activityConfig || {},\n }\n // Include optional activity fields from node data\n if ((sourceNode.data as any).activityAsync !== undefined) {\n activity.async = (sourceNode.data as any).activityAsync\n }\n if ((sourceNode.data as any).activityTimeout) {\n activity.timeout = (sourceNode.data as any).activityTimeout\n }\n if ((sourceNode.data as any).activityRetryPolicy) {\n activity.retryPolicy = (sourceNode.data as any).activityRetryPolicy\n }\n if ((sourceNode.data as any).activityCompensate !== undefined) {\n activity.compensate = (sourceNode.data as any).activityCompensate\n }\n transition.activities = [activity]\n }\n }\n }\n\n // Add label if present (legacy field, transitionName is preferred)\n if (edgeData?.label && !transition.transitionName) {\n transition.transitionName = edgeData.label\n }\n\n return transition\n })\n\n return {\n steps,\n transitions,\n activities: [], // Global activities can be added later\n }\n}\n\n/**\n * Convert workflow definition JSON to ReactFlow graph (nodes + edges)\n */\nexport function definitionToGraph(\n definition: WorkflowDefinition['definition'],\n options: DefinitionToGraphOptions = {}\n): { nodes: Node[]; edges: Edge[] } {\n const { autoLayout = true, layoutSpacing = { vertical: 200, horizontal: 300 } } = options\n\n // Build step map for quick lookup\n const stepMap = new Map(definition.steps.map(step => [step.stepId, step]))\n\n // Calculate smart layout positions if autoLayout is enabled\n const positions = autoLayout\n ? calculateSmartLayout(definition.steps, definition.transitions, layoutSpacing)\n : null\n\n // Convert steps to nodes\n const nodes: Node[] = definition.steps.map((step, index) => {\n // Determine position\n let position = positions?.get(step.stepId) || { x: 250, y: 50 + index * layoutSpacing.vertical }\n\n // Use stored position if available and not auto-layouting\n if (!autoLayout && (step as any)._editorPosition) {\n position = (step as any)._editorPosition\n }\n\n // Map step type to node type\n const nodeType = mapStepTypeToNodeType(step.stepType)\n\n // Build node data\n const nodeData: any = {\n label: step.stepName,\n description: (step as any).description,\n stepNumber: index > 0 ? index : undefined,\n }\n\n // Add timeout if present\n if ((step as any).timeout) {\n nodeData.timeout = (step as any).timeout\n }\n\n // Add retryPolicy if present\n if ((step as any).retryPolicy) {\n nodeData.retryPolicy = (step as any).retryPolicy\n }\n\n // Add generic config if present\n if ((step as any).config) {\n nodeData.config = (step as any).config\n }\n\n // Add user task data\n if (step.stepType === 'USER_TASK' && step.userTaskConfig) {\n nodeData.assignedTo = step.userTaskConfig.assignedTo\n nodeData.assignedToRoles = step.userTaskConfig.assignedToRoles || []\n nodeData.formKey = step.userTaskConfig.formKey\n nodeData.allowedActions = step.userTaskConfig.allowedActions\n\n // Store full userTaskConfig for advanced fields\n nodeData.userTaskConfig = step.userTaskConfig\n\n // Add form schema if present\n if (step.userTaskConfig.formSchema) {\n nodeData.formSchema = step.userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if (step.userTaskConfig.assignmentRule) {\n nodeData.assignmentRule = step.userTaskConfig.assignmentRule\n }\n\n if (step.userTaskConfig.slaDuration) {\n nodeData.slaDuration = step.userTaskConfig.slaDuration\n }\n\n if (step.userTaskConfig.escalationRules) {\n nodeData.escalationRules = step.userTaskConfig.escalationRules\n }\n }\n\n // Add wait for signal data\n if (step.stepType === 'WAIT_FOR_SIGNAL' && (step as any).signalConfig) {\n nodeData.signalConfig = (step as any).signalConfig\n }\n\n // Add step activities data (for AUTOMATED steps)\n if (step.stepType === 'AUTOMATED' && (step as any).activities) {\n nodeData.activities = (step as any).activities\n }\n\n // Add pre-conditions data (for START steps)\n if (step.stepType === 'START' && (step as any).preConditions) {\n nodeData.preConditions = (step as any).preConditions\n }\n\n // Set badge based on type\n nodeData.badge = getBadgeForNodeType(nodeType)\n\n // Default status is pending\n nodeData.status = 'pending'\n\n return {\n id: step.stepId,\n type: nodeType,\n position,\n data: nodeData,\n }\n })\n\n // Convert transitions to edges\n const edges: Edge[] = definition.transitions.map((transition) => {\n return {\n id: transition.transitionId,\n source: transition.fromStepId,\n target: transition.toStepId,\n type: 'workflowTransition',\n data: {\n trigger: transition.trigger,\n transitionName: (transition as any).transitionName,\n priority: (transition as any).priority !== undefined ? (transition as any).priority : 0,\n continueOnActivityFailure: (transition as any).continueOnActivityFailure !== undefined\n ? (transition as any).continueOnActivityFailure\n : false,\n preConditions: transition.preConditions || [],\n postConditions: transition.postConditions || [],\n activities: transition.activities || [],\n label: (transition as any).transitionName || (transition as any).label, // Backward compat\n state: (transition as any).state || 'pending', // Default edge state\n },\n }\n })\n\n return { nodes, edges }\n}\n\n/**\n * Calculate smart layout positions for workflow nodes\n * Uses a layered/hierarchical layout algorithm that:\n * 1. Assigns levels (ranks) to nodes based on graph topology\n * 2. Spreads sibling nodes horizontally at the same level\n * 3. Centers merge points below their incoming nodes\n */\nfunction calculateSmartLayout(\n steps: any[],\n transitions: any[],\n spacing: { vertical: number; horizontal: number }\n): Map<string, { x: number; y: number }> {\n const positions = new Map<string, { x: number; y: number }>()\n\n if (steps.length === 0) return positions\n\n // Build adjacency lists\n const outgoing = new Map<string, string[]>() // node -> children\n const incoming = new Map<string, string[]>() // node -> parents\n\n for (const step of steps) {\n outgoing.set(step.stepId, [])\n incoming.set(step.stepId, [])\n }\n\n for (const t of transitions) {\n const children = outgoing.get(t.fromStepId) || []\n children.push(t.toStepId)\n outgoing.set(t.fromStepId, children)\n\n const parents = incoming.get(t.toStepId) || []\n parents.push(t.fromStepId)\n incoming.set(t.toStepId, parents)\n }\n\n // Find start node(s) - nodes with no incoming edges\n const startNodes = steps.filter(s => (incoming.get(s.stepId) || []).length === 0)\n if (startNodes.length === 0) {\n // Fallback: use first step as start\n startNodes.push(steps[0])\n }\n\n // Assign levels using BFS (longest path from start)\n const levels = new Map<string, number>()\n const queue: Array<{ id: string; level: number }> = []\n\n for (const start of startNodes) {\n queue.push({ id: start.stepId, level: 0 })\n }\n\n while (queue.length > 0) {\n const { id, level } = queue.shift()!\n const currentLevel = levels.get(id)\n\n // Take the maximum level (longest path)\n if (currentLevel === undefined || level > currentLevel) {\n levels.set(id, level)\n }\n\n const children = outgoing.get(id) || []\n for (const child of children) {\n queue.push({ id: child, level: level + 1 })\n }\n }\n\n // Group nodes by level\n const nodesByLevel = new Map<number, string[]>()\n for (const [nodeId, level] of levels) {\n const nodesAtLevel = nodesByLevel.get(level) || []\n nodesAtLevel.push(nodeId)\n nodesByLevel.set(level, nodesAtLevel)\n }\n\n // Calculate positions\n const centerX = 400 // Center line for the graph\n const startY = 50\n\n for (const [level, nodeIds] of nodesByLevel) {\n const count = nodeIds.length\n const y = startY + level * spacing.vertical\n\n if (count === 1) {\n // Single node at this level - center it\n positions.set(nodeIds[0], { x: centerX, y })\n } else {\n // Multiple nodes at this level - spread them horizontally\n const totalWidth = (count - 1) * spacing.horizontal\n const startX = centerX - totalWidth / 2\n\n // Sort nodes by their parent's position for consistent ordering\n nodeIds.sort((a, b) => {\n const parentsA = incoming.get(a) || []\n const parentsB = incoming.get(b) || []\n const parentPosA = parentsA.length > 0 ? (positions.get(parentsA[0])?.x || 0) : 0\n const parentPosB = parentsB.length > 0 ? (positions.get(parentsB[0])?.x || 0) : 0\n return parentPosA - parentPosB\n })\n\n nodeIds.forEach((nodeId, idx) => {\n positions.set(nodeId, { x: startX + idx * spacing.horizontal, y })\n })\n }\n }\n\n return positions\n}\n\n/**\n * Map node type to step type (for graph \u2192 definition)\n */\nfunction mapNodeTypeToStepType(nodeType: string): string {\n const mapping: Record<string, string> = {\n start: 'START',\n end: 'END',\n userTask: 'USER_TASK',\n automated: 'AUTOMATED',\n decision: 'DECISION',\n waitForSignal: 'WAIT_FOR_SIGNAL',\n waitForTimer: 'WAIT_FOR_TIMER',\n }\n return mapping[nodeType] || 'AUTOMATED'\n}\n\n/**\n * Map step type to node type (for definition \u2192 graph)\n */\nfunction mapStepTypeToNodeType(stepType: string): string {\n const mapping: Record<string, string> = {\n START: 'start',\n END: 'end',\n USER_TASK: 'userTask',\n AUTOMATED: 'automated',\n DECISION: 'decision',\n WAIT_FOR_SIGNAL: 'waitForSignal',\n WAIT_FOR_TIMER: 'waitForTimer',\n }\n return mapping[stepType] || 'automated'\n}\n\n/**\n * Get badge text for node type\n */\nfunction getBadgeForNodeType(nodeType: string): string {\n const badges: Record<string, string> = {\n start: 'Start',\n end: 'End',\n userTask: 'User Task',\n automated: 'Automated',\n decision: 'Decision',\n waitForSignal: 'Wait for Signal',\n waitForTimer: 'Wait for Timer',\n }\n return badges[nodeType] || 'Task'\n}\n\n/**\n * Validate workflow graph\n */\nexport interface ValidationError {\n type: 'error' | 'warning'\n message: string\n nodeId?: string\n edgeId?: string\n}\n\nexport function validateWorkflowGraph(nodes: Node[], edges: Edge[]): ValidationError[] {\n const errors: ValidationError[] = []\n\n // Check for at least one start node\n const startNodes = nodes.filter((n) => n.type === 'start')\n if (startNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one START node',\n })\n }\n if (startNodes.length > 1) {\n errors.push({\n type: 'warning',\n message: 'Workflow has multiple START nodes',\n })\n }\n\n // Check for at least one end node\n const endNodes = nodes.filter((n) => n.type === 'end')\n if (endNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one END node',\n })\n }\n\n // Check for orphan nodes (no incoming or outgoing edges)\n for (const node of nodes) {\n if (node.type === 'start') continue // Start nodes don't need incoming edges\n if (node.type === 'end') continue // End nodes don't need outgoing edges\n\n const hasIncoming = edges.some((e) => e.target === node.id)\n const hasOutgoing = edges.some((e) => e.source === node.id)\n\n if (!hasIncoming && !hasOutgoing) {\n errors.push({\n type: 'error',\n message: `Node \"${node.data.label}\" is disconnected`,\n nodeId: node.id,\n })\n } else if (!hasIncoming) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no incoming connections`,\n nodeId: node.id,\n })\n } else if (!hasOutgoing) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no outgoing connections`,\n nodeId: node.id,\n })\n }\n }\n\n // Check for cycles (simple detection)\n const hasCycle = detectCycle(nodes, edges)\n if (hasCycle) {\n errors.push({\n type: 'warning',\n message: 'Workflow contains cycles (loops)',\n })\n }\n\n // Check for duplicate step IDs\n const stepIds = new Set<string>()\n for (const node of nodes) {\n if (stepIds.has(node.id)) {\n errors.push({\n type: 'error',\n message: `Duplicate step ID: ${node.id}`,\n nodeId: node.id,\n })\n }\n stepIds.add(node.id)\n }\n\n return errors\n}\n\n/**\n * Simple cycle detection using DFS\n */\nfunction detectCycle(nodes: Node[], edges: Edge[]): boolean {\n const adjList = new Map<string, string[]>()\n\n // Build adjacency list\n for (const node of nodes) {\n adjList.set(node.id, [])\n }\n for (const edge of edges) {\n const neighbors = adjList.get(edge.source) || []\n neighbors.push(edge.target)\n adjList.set(edge.source, neighbors)\n }\n\n const visited = new Set<string>()\n const recStack = new Set<string>()\n\n function dfs(nodeId: string): boolean {\n visited.add(nodeId)\n recStack.add(nodeId)\n\n const neighbors = adjList.get(nodeId) || []\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n if (dfs(neighbor)) return true\n } else if (recStack.has(neighbor)) {\n return true // Cycle detected\n }\n }\n\n recStack.delete(nodeId)\n return false\n }\n\n for (const node of nodes) {\n if (!visited.has(node.id)) {\n if (dfs(node.id)) return true\n }\n }\n\n return false\n}\n\n/**\n * Sanitize ID to match schema regex: /^[a-z0-9_-]+$/\n * Converts to lowercase, replaces invalid characters with underscores\n */\nexport function sanitizeId(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '_')\n .replace(/_{2,}/g, '_') // Replace multiple underscores with single\n .replace(/(?:^_|_$)/g, '') // Remove leading/trailing underscores\n}\n\n/**\n * Validate ID matches schema regex: /^[a-z0-9_-]+$/\n */\nexport function validateId(id: string): boolean {\n return /^[a-z0-9_-]+$/.test(id)\n}\n\n/**\n * Generate unique step ID\n */\nexport function generateStepId(prefix: string = 'step'): string {\n const id = `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n return sanitizeId(id)\n}\n\n/**\n * Generate unique transition ID\n */\nexport function generateTransitionId(fromStepId: string, toStepId: string): string {\n const id = `e_${fromStepId}_${toStepId}`\n return sanitizeId(id)\n}\n\n/**\n * Append a new edge to the list, skipping duplicate connections.\n *\n * A plain-data replacement for React Flow's `addEdge` so the visual editor\n * page does not pull the `@xyflow/react` runtime out of its lazy boundary\n * (#3169). Mirrors `addEdge`'s dedup rule: an edge is dropped when one with\n * the same source/target endpoints (and handles) already exists.\n */\nexport function appendWorkflowEdge(edges: Edge[], edge: Edge): Edge[] {\n const isDuplicate = edges.some(\n (existing) =>\n existing.source === edge.source &&\n existing.target === edge.target &&\n // Match addEdge: empty-string and nullish handles are equivalent.\n (existing.sourceHandle || null) === (edge.sourceHandle || null) &&\n (existing.targetHandle || null) === (edge.targetHandle || null),\n )\n return isDuplicate ? edges : [...edges, edge]\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { Node, Edge } from '@xyflow/react'\nimport type { WorkflowDefinition } from '../data/entities'\n\n/**\n * Graph Utilities for Visual Workflow Editor\n *\n * Converts between ReactFlow graph representation and workflow definition JSON\n */\n\nexport interface GraphToDefinitionOptions {\n includePositions?: boolean\n}\n\nexport interface DefinitionToGraphOptions {\n autoLayout?: boolean\n layoutSpacing?: { vertical: number; horizontal: number }\n}\n\n/**\n * Convert ReactFlow graph (nodes + edges) to workflow definition JSON\n */\nexport function graphToDefinition(\n nodes: Node[],\n edges: Edge[],\n options: GraphToDefinitionOptions = {}\n): WorkflowDefinition['definition'] {\n // Extract steps from nodes\n const steps = nodes.map((node) => {\n const step: any = {\n stepId: node.id,\n stepName: node.data.label || node.id,\n stepType: mapNodeTypeToStepType(node.type || 'automated'),\n }\n\n // Add step-specific configuration\n if (node.data.description) {\n step.description = node.data.description\n }\n\n // Add timeout if present\n if (node.data.timeout) {\n step.timeout = node.data.timeout\n }\n\n // Add retryPolicy if present\n if (node.data.retryPolicy) {\n step.retryPolicy = node.data.retryPolicy\n }\n\n // Add generic config if present\n if (node.data.config) {\n step.config = node.data.config\n }\n\n // User task configuration\n if (node.type === 'userTask' && node.data) {\n step.userTaskConfig = {\n assignedTo: node.data.assignedTo,\n assignedToRoles: node.data.assignedToRoles || [],\n formKey: node.data.formKey,\n allowedActions: node.data.allowedActions || ['complete', 'cancel'],\n }\n\n // Add form schema if present\n if ((node.data as any).formSchema || (node.data as any).userTaskConfig?.formSchema) {\n step.userTaskConfig.formSchema = (node.data as any).formSchema || (node.data as any).userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if ((node.data as any).assignmentRule || (node.data as any).userTaskConfig?.assignmentRule) {\n step.userTaskConfig.assignmentRule = (node.data as any).assignmentRule || (node.data as any).userTaskConfig.assignmentRule\n }\n\n if ((node.data as any).slaDuration || (node.data as any).userTaskConfig?.slaDuration) {\n step.userTaskConfig.slaDuration = (node.data as any).slaDuration || (node.data as any).userTaskConfig.slaDuration\n }\n\n if ((node.data as any).escalationRules || (node.data as any).userTaskConfig?.escalationRules) {\n step.userTaskConfig.escalationRules = (node.data as any).escalationRules || (node.data as any).userTaskConfig.escalationRules\n }\n }\n\n // Wait for signal configuration\n if (node.type === 'waitForSignal' && node.data.signalConfig) {\n step.signalConfig = node.data.signalConfig\n }\n\n // Step activities (for AUTOMATED steps)\n if (node.type === 'automated' && node.data.activities) {\n step.activities = node.data.activities\n }\n\n // Pre-conditions (for START steps)\n if (node.type === 'start' && (node.data as any).preConditions && (node.data as any).preConditions.length > 0) {\n step.preConditions = (node.data as any).preConditions\n }\n\n // Store position for visual editor\n if (options.includePositions && node.position) {\n step._editorPosition = {\n x: node.position.x,\n y: node.position.y,\n }\n }\n\n return step\n })\n\n // Extract transitions from edges\n const transitions = edges.map((edge) => {\n const edgeData = edge.data as any\n const transition: any = {\n transitionId: edge.id,\n fromStepId: edge.source,\n toStepId: edge.target,\n trigger: edgeData?.trigger || 'auto',\n }\n\n // Add transition name if present\n if (edgeData?.transitionName) {\n transition.transitionName = edgeData.transitionName\n }\n\n // Add priority if present (default 0)\n if (edgeData?.priority !== undefined) {\n transition.priority = edgeData.priority\n }\n\n // Add continueOnActivityFailure if present (default false)\n if (edgeData?.continueOnActivityFailure !== undefined) {\n transition.continueOnActivityFailure = edgeData.continueOnActivityFailure\n }\n\n // Add conditions if present\n if (edgeData?.preConditions && edgeData.preConditions.length > 0) {\n transition.preConditions = edgeData.preConditions\n }\n\n if (edgeData?.postConditions && edgeData.postConditions.length > 0) {\n transition.postConditions = edgeData.postConditions\n }\n\n // Add activities if present in edge data\n if (edgeData?.activities && edgeData.activities.length > 0) {\n transition.activities = edgeData.activities.map((activity: any) => ({\n activityId: activity.activityId,\n activityName: activity.activityName,\n activityType: activity.activityType,\n config: activity.config || {},\n // Include all optional fields\n ...(activity.async !== undefined && { async: activity.async }),\n ...(activity.timeout && { timeout: activity.timeout }),\n ...(activity.retryPolicy && { retryPolicy: activity.retryPolicy }),\n ...(activity.compensate !== undefined && { compensate: activity.compensate }),\n }))\n } else {\n // Check if source node is automated and has activity data\n // If so, place the activity in this transition\n const sourceNode = nodes.find(n => n.id === edge.source)\n if (sourceNode && sourceNode.type === 'automated' && sourceNode.data) {\n if (sourceNode.data.activityType || sourceNode.data.activityId) {\n const activity: any = {\n activityId: sourceNode.data.activityId || `activity_${sourceNode.id}`,\n activityName: sourceNode.data.activityName || sourceNode.data.label || 'Automated Activity',\n activityType: sourceNode.data.activityType || 'CALL_API',\n config: sourceNode.data.activityConfig || {},\n }\n // Include optional activity fields from node data\n if ((sourceNode.data as any).activityAsync !== undefined) {\n activity.async = (sourceNode.data as any).activityAsync\n }\n if ((sourceNode.data as any).activityTimeout) {\n activity.timeout = (sourceNode.data as any).activityTimeout\n }\n if ((sourceNode.data as any).activityRetryPolicy) {\n activity.retryPolicy = (sourceNode.data as any).activityRetryPolicy\n }\n if ((sourceNode.data as any).activityCompensate !== undefined) {\n activity.compensate = (sourceNode.data as any).activityCompensate\n }\n transition.activities = [activity]\n }\n }\n }\n\n // Add label if present (legacy field, transitionName is preferred)\n if (edgeData?.label && !transition.transitionName) {\n transition.transitionName = edgeData.label\n }\n\n return transition\n })\n\n return {\n steps,\n transitions,\n activities: [], // Global activities can be added later\n }\n}\n\n/**\n * Convert workflow definition JSON to ReactFlow graph (nodes + edges)\n */\nexport function definitionToGraph(\n definition: WorkflowDefinition['definition'],\n options: DefinitionToGraphOptions = {}\n): { nodes: Node[]; edges: Edge[] } {\n const { autoLayout = true, layoutSpacing = { vertical: 200, horizontal: 300 } } = options\n\n // Build step map for quick lookup\n const stepMap = new Map(definition.steps.map(step => [step.stepId, step]))\n\n // Calculate smart layout positions if autoLayout is enabled\n const positions = autoLayout\n ? calculateSmartLayout(definition.steps, definition.transitions, layoutSpacing)\n : null\n\n // Convert steps to nodes\n const nodes: Node[] = definition.steps.map((step, index) => {\n // Determine position\n let position = positions?.get(step.stepId) || { x: 250, y: 50 + index * layoutSpacing.vertical }\n\n // Use stored position if available and not auto-layouting\n if (!autoLayout && (step as any)._editorPosition) {\n position = (step as any)._editorPosition\n }\n\n // Map step type to node type\n const nodeType = mapStepTypeToNodeType(step.stepType)\n\n // Build node data\n const nodeData: any = {\n label: step.stepName,\n description: (step as any).description,\n stepNumber: index > 0 ? index : undefined,\n }\n\n // Add timeout if present\n if ((step as any).timeout) {\n nodeData.timeout = (step as any).timeout\n }\n\n // Add retryPolicy if present\n if ((step as any).retryPolicy) {\n nodeData.retryPolicy = (step as any).retryPolicy\n }\n\n // Add generic config if present\n if ((step as any).config) {\n nodeData.config = (step as any).config\n }\n\n // Add user task data\n if (step.stepType === 'USER_TASK' && step.userTaskConfig) {\n nodeData.assignedTo = step.userTaskConfig.assignedTo\n nodeData.assignedToRoles = step.userTaskConfig.assignedToRoles || []\n nodeData.formKey = step.userTaskConfig.formKey\n nodeData.allowedActions = step.userTaskConfig.allowedActions\n\n // Store full userTaskConfig for advanced fields\n nodeData.userTaskConfig = step.userTaskConfig\n\n // Add form schema if present\n if (step.userTaskConfig.formSchema) {\n nodeData.formSchema = step.userTaskConfig.formSchema\n }\n\n // Add advanced fields if present\n if (step.userTaskConfig.assignmentRule) {\n nodeData.assignmentRule = step.userTaskConfig.assignmentRule\n }\n\n if (step.userTaskConfig.slaDuration) {\n nodeData.slaDuration = step.userTaskConfig.slaDuration\n }\n\n if (step.userTaskConfig.escalationRules) {\n nodeData.escalationRules = step.userTaskConfig.escalationRules\n }\n }\n\n // Add wait for signal data\n if (step.stepType === 'WAIT_FOR_SIGNAL' && (step as any).signalConfig) {\n nodeData.signalConfig = (step as any).signalConfig\n }\n\n // Add step activities data (for AUTOMATED steps)\n if (step.stepType === 'AUTOMATED' && (step as any).activities) {\n nodeData.activities = (step as any).activities\n }\n\n // Add pre-conditions data (for START steps)\n if (step.stepType === 'START' && (step as any).preConditions) {\n nodeData.preConditions = (step as any).preConditions\n }\n\n // Set badge based on type\n nodeData.badge = getBadgeForNodeType(nodeType)\n\n // Default status is pending\n nodeData.status = 'pending'\n\n return {\n id: step.stepId,\n type: nodeType,\n position,\n data: nodeData,\n }\n })\n\n // Convert transitions to edges\n const edges: Edge[] = definition.transitions.map((transition) => {\n return {\n id: transition.transitionId,\n source: transition.fromStepId,\n target: transition.toStepId,\n type: 'workflowTransition',\n data: {\n trigger: transition.trigger,\n transitionName: (transition as any).transitionName,\n priority: (transition as any).priority !== undefined ? (transition as any).priority : 0,\n continueOnActivityFailure: (transition as any).continueOnActivityFailure !== undefined\n ? (transition as any).continueOnActivityFailure\n : false,\n preConditions: transition.preConditions || [],\n postConditions: transition.postConditions || [],\n activities: transition.activities || [],\n label: (transition as any).transitionName || (transition as any).label, // Backward compat\n state: (transition as any).state || 'pending', // Default edge state\n },\n }\n })\n\n return { nodes, edges }\n}\n\n/**\n * Calculate smart layout positions for workflow nodes\n * Uses a layered/hierarchical layout algorithm that:\n * 1. Assigns levels (ranks) to nodes based on graph topology\n * 2. Spreads sibling nodes horizontally at the same level\n * 3. Centers merge points below their incoming nodes\n */\nfunction calculateSmartLayout(\n steps: any[],\n transitions: any[],\n spacing: { vertical: number; horizontal: number }\n): Map<string, { x: number; y: number }> {\n const positions = new Map<string, { x: number; y: number }>()\n\n if (steps.length === 0) return positions\n\n // Build adjacency lists\n const outgoing = new Map<string, string[]>() // node -> children\n const incoming = new Map<string, string[]>() // node -> parents\n\n for (const step of steps) {\n outgoing.set(step.stepId, [])\n incoming.set(step.stepId, [])\n }\n\n for (const t of transitions) {\n const children = outgoing.get(t.fromStepId) || []\n children.push(t.toStepId)\n outgoing.set(t.fromStepId, children)\n\n const parents = incoming.get(t.toStepId) || []\n parents.push(t.fromStepId)\n incoming.set(t.toStepId, parents)\n }\n\n // Find start node(s) - nodes with no incoming edges\n const startNodes = steps.filter(s => (incoming.get(s.stepId) || []).length === 0)\n if (startNodes.length === 0) {\n // Fallback: use first step as start\n startNodes.push(steps[0])\n }\n\n // Assign levels using BFS (longest path from start)\n const levels = new Map<string, number>()\n const queue: Array<{ id: string; level: number }> = []\n\n for (const start of startNodes) {\n queue.push({ id: start.stepId, level: 0 })\n }\n\n while (queue.length > 0) {\n const { id, level } = queue.shift()!\n const currentLevel = levels.get(id)\n\n // Only propagate when this path improves the node's level (longest path).\n // Without this guard a cyclic graph (renegotiation/revision loops) keeps\n // re-enqueuing children forever and freezes the browser (script timeout).\n if (currentLevel !== undefined && level <= currentLevel) continue\n levels.set(id, level)\n\n // Safety cap: in a DAG a node's level is bounded by the node count; a\n // higher value means we are walking a cycle, so stop descending.\n if (level > steps.length) continue\n\n const children = outgoing.get(id) || []\n for (const child of children) {\n queue.push({ id: child, level: level + 1 })\n }\n }\n\n // Group nodes by level\n const nodesByLevel = new Map<number, string[]>()\n for (const [nodeId, level] of levels) {\n const nodesAtLevel = nodesByLevel.get(level) || []\n nodesAtLevel.push(nodeId)\n nodesByLevel.set(level, nodesAtLevel)\n }\n\n // Calculate positions\n const centerX = 400 // Center line for the graph\n const startY = 50\n\n for (const [level, nodeIds] of nodesByLevel) {\n const count = nodeIds.length\n const y = startY + level * spacing.vertical\n\n if (count === 1) {\n // Single node at this level - center it\n positions.set(nodeIds[0], { x: centerX, y })\n } else {\n // Multiple nodes at this level - spread them horizontally\n const totalWidth = (count - 1) * spacing.horizontal\n const startX = centerX - totalWidth / 2\n\n // Sort nodes by their parent's position for consistent ordering\n nodeIds.sort((a, b) => {\n const parentsA = incoming.get(a) || []\n const parentsB = incoming.get(b) || []\n const parentPosA = parentsA.length > 0 ? (positions.get(parentsA[0])?.x || 0) : 0\n const parentPosB = parentsB.length > 0 ? (positions.get(parentsB[0])?.x || 0) : 0\n return parentPosA - parentPosB\n })\n\n nodeIds.forEach((nodeId, idx) => {\n positions.set(nodeId, { x: startX + idx * spacing.horizontal, y })\n })\n }\n }\n\n return positions\n}\n\n/**\n * Map node type to step type (for graph \u2192 definition)\n */\nfunction mapNodeTypeToStepType(nodeType: string): string {\n const mapping: Record<string, string> = {\n start: 'START',\n end: 'END',\n userTask: 'USER_TASK',\n automated: 'AUTOMATED',\n decision: 'DECISION',\n waitForSignal: 'WAIT_FOR_SIGNAL',\n waitForTimer: 'WAIT_FOR_TIMER',\n }\n return mapping[nodeType] || 'AUTOMATED'\n}\n\n/**\n * Map step type to node type (for definition \u2192 graph)\n */\nfunction mapStepTypeToNodeType(stepType: string): string {\n const mapping: Record<string, string> = {\n START: 'start',\n END: 'end',\n USER_TASK: 'userTask',\n AUTOMATED: 'automated',\n DECISION: 'decision',\n WAIT_FOR_SIGNAL: 'waitForSignal',\n WAIT_FOR_TIMER: 'waitForTimer',\n }\n return mapping[stepType] || 'automated'\n}\n\n/**\n * Get badge text for node type\n */\nfunction getBadgeForNodeType(nodeType: string): string {\n const badges: Record<string, string> = {\n start: 'Start',\n end: 'End',\n userTask: 'User Task',\n automated: 'Automated',\n decision: 'Decision',\n waitForSignal: 'Wait for Signal',\n waitForTimer: 'Wait for Timer',\n }\n return badges[nodeType] || 'Task'\n}\n\n/**\n * Validate workflow graph\n */\nexport interface ValidationError {\n type: 'error' | 'warning'\n message: string\n nodeId?: string\n edgeId?: string\n}\n\nexport function validateWorkflowGraph(nodes: Node[], edges: Edge[]): ValidationError[] {\n const errors: ValidationError[] = []\n\n // Check for at least one start node\n const startNodes = nodes.filter((n) => n.type === 'start')\n if (startNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one START node',\n })\n }\n if (startNodes.length > 1) {\n errors.push({\n type: 'warning',\n message: 'Workflow has multiple START nodes',\n })\n }\n\n // Check for at least one end node\n const endNodes = nodes.filter((n) => n.type === 'end')\n if (endNodes.length === 0) {\n errors.push({\n type: 'error',\n message: 'Workflow must have at least one END node',\n })\n }\n\n // Check for orphan nodes (no incoming or outgoing edges)\n for (const node of nodes) {\n if (node.type === 'start') continue // Start nodes don't need incoming edges\n if (node.type === 'end') continue // End nodes don't need outgoing edges\n\n const hasIncoming = edges.some((e) => e.target === node.id)\n const hasOutgoing = edges.some((e) => e.source === node.id)\n\n if (!hasIncoming && !hasOutgoing) {\n errors.push({\n type: 'error',\n message: `Node \"${node.data.label}\" is disconnected`,\n nodeId: node.id,\n })\n } else if (!hasIncoming) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no incoming connections`,\n nodeId: node.id,\n })\n } else if (!hasOutgoing) {\n errors.push({\n type: 'warning',\n message: `Node \"${node.data.label}\" has no outgoing connections`,\n nodeId: node.id,\n })\n }\n }\n\n // Check for cycles (simple detection)\n const hasCycle = detectCycle(nodes, edges)\n if (hasCycle) {\n errors.push({\n type: 'warning',\n message: 'Workflow contains cycles (loops)',\n })\n }\n\n // Check for duplicate step IDs\n const stepIds = new Set<string>()\n for (const node of nodes) {\n if (stepIds.has(node.id)) {\n errors.push({\n type: 'error',\n message: `Duplicate step ID: ${node.id}`,\n nodeId: node.id,\n })\n }\n stepIds.add(node.id)\n }\n\n return errors\n}\n\n/**\n * Simple cycle detection using DFS\n */\nfunction detectCycle(nodes: Node[], edges: Edge[]): boolean {\n const adjList = new Map<string, string[]>()\n\n // Build adjacency list\n for (const node of nodes) {\n adjList.set(node.id, [])\n }\n for (const edge of edges) {\n const neighbors = adjList.get(edge.source) || []\n neighbors.push(edge.target)\n adjList.set(edge.source, neighbors)\n }\n\n const visited = new Set<string>()\n const recStack = new Set<string>()\n\n function dfs(nodeId: string): boolean {\n visited.add(nodeId)\n recStack.add(nodeId)\n\n const neighbors = adjList.get(nodeId) || []\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n if (dfs(neighbor)) return true\n } else if (recStack.has(neighbor)) {\n return true // Cycle detected\n }\n }\n\n recStack.delete(nodeId)\n return false\n }\n\n for (const node of nodes) {\n if (!visited.has(node.id)) {\n if (dfs(node.id)) return true\n }\n }\n\n return false\n}\n\n/**\n * Sanitize ID to match schema regex: /^[a-z0-9_-]+$/\n * Converts to lowercase, replaces invalid characters with underscores\n */\nexport function sanitizeId(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '_')\n .replace(/_{2,}/g, '_') // Replace multiple underscores with single\n .replace(/(?:^_|_$)/g, '') // Remove leading/trailing underscores\n}\n\n/**\n * Validate ID matches schema regex: /^[a-z0-9_-]+$/\n */\nexport function validateId(id: string): boolean {\n return /^[a-z0-9_-]+$/.test(id)\n}\n\n/**\n * Generate unique step ID\n */\nexport function generateStepId(prefix: string = 'step'): string {\n const id = `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n return sanitizeId(id)\n}\n\n/**\n * Generate unique transition ID\n */\nexport function generateTransitionId(fromStepId: string, toStepId: string): string {\n const id = `e_${fromStepId}_${toStepId}`\n return sanitizeId(id)\n}\n\n/**\n * Append a new edge to the list, skipping duplicate connections.\n *\n * A plain-data replacement for React Flow's `addEdge` so the visual editor\n * page does not pull the `@xyflow/react` runtime out of its lazy boundary\n * (#3169). Mirrors `addEdge`'s dedup rule: an edge is dropped when one with\n * the same source/target endpoints (and handles) already exists.\n */\nexport function appendWorkflowEdge(edges: Edge[], edge: Edge): Edge[] {\n const isDuplicate = edges.some(\n (existing) =>\n existing.source === edge.source &&\n existing.target === edge.target &&\n // Match addEdge: empty-string and nullish handles are equivalent.\n (existing.sourceHandle || null) === (edge.sourceHandle || null) &&\n (existing.targetHandle || null) === (edge.targetHandle || null),\n )\n return isDuplicate ? edges : [...edges, edge]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6525.1.e9a4b56963",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -253,16 +253,16 @@
|
|
|
253
253
|
"zod": "^4.4.3"
|
|
254
254
|
},
|
|
255
255
|
"peerDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6525.1.e9a4b56963",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6525.1.e9a4b56963",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6525.1.e9a4b56963",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
264
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
265
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6525.1.e9a4b56963",
|
|
264
|
+
"@open-mercato/shared": "0.6.6-develop.6525.1.e9a4b56963",
|
|
265
|
+
"@open-mercato/ui": "0.6.6-develop.6525.1.e9a4b56963",
|
|
266
266
|
"@testing-library/dom": "^10.4.1",
|
|
267
267
|
"@testing-library/jest-dom": "^6.9.1",
|
|
268
268
|
"@testing-library/react": "^16.3.1",
|
|
@@ -40,7 +40,7 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
40
40
|
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
41
41
|
const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
|
|
42
42
|
const em = container.resolve('em') as EntityManager
|
|
43
|
-
const user = await customerUserService.findById(params.id, auth.tenantId
|
|
43
|
+
const user = await customerUserService.findById(params.id, auth.tenantId!, auth.orgId)
|
|
44
44
|
if (!user) {
|
|
45
45
|
return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
|
|
46
46
|
}
|
|
@@ -54,15 +54,15 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
54
54
|
id: user.id,
|
|
55
55
|
recipientUserId: user.id,
|
|
56
56
|
email: user.email,
|
|
57
|
-
tenantId:
|
|
58
|
-
organizationId:
|
|
57
|
+
tenantId: user.tenantId,
|
|
58
|
+
organizationId: user.organizationId,
|
|
59
59
|
resetBy: auth.sub,
|
|
60
60
|
}).catch(() => undefined)
|
|
61
61
|
|
|
62
62
|
void emitCustomerAccountsEvent('customer_accounts.password.changed', {
|
|
63
63
|
userId: user.id,
|
|
64
|
-
tenantId:
|
|
65
|
-
organizationId:
|
|
64
|
+
tenantId: user.tenantId,
|
|
65
|
+
organizationId: user.organizationId,
|
|
66
66
|
changedBy: 'admin',
|
|
67
67
|
changedById: auth.sub,
|
|
68
68
|
at: new Date().toISOString(),
|
|
@@ -24,13 +24,13 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
27
|
-
const user = await customerUserService.findById(params.id, auth.tenantId
|
|
27
|
+
const user = await customerUserService.findById(params.id, auth.tenantId!, auth.orgId)
|
|
28
28
|
if (!user) {
|
|
29
29
|
return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService
|
|
33
|
-
const rawToken = await customerTokenService.createPasswordReset(user.id,
|
|
33
|
+
const rawToken = await customerTokenService.createPasswordReset(user.id, user.tenantId)
|
|
34
34
|
|
|
35
35
|
const resetLink = `/portal/reset-password?token=${rawToken}`
|
|
36
36
|
|
|
@@ -38,8 +38,8 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
38
38
|
id: user.id,
|
|
39
39
|
recipientUserId: user.id,
|
|
40
40
|
email: user.email,
|
|
41
|
-
tenantId:
|
|
42
|
-
organizationId:
|
|
41
|
+
tenantId: user.tenantId,
|
|
42
|
+
organizationId: user.organizationId,
|
|
43
43
|
resetBy: auth.sub,
|
|
44
44
|
}).catch(() => undefined)
|
|
45
45
|
|
|
@@ -6,6 +6,7 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
|
6
6
|
import { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
7
7
|
import { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
8
8
|
import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
|
|
9
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
10
|
|
|
10
11
|
export const metadata = {}
|
|
11
12
|
|
|
@@ -24,11 +25,18 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
24
25
|
|
|
25
26
|
const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager
|
|
26
27
|
|
|
27
|
-
const user = await
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
const user = await findOneWithDecryption(
|
|
29
|
+
em,
|
|
30
|
+
CustomerUser,
|
|
31
|
+
{
|
|
32
|
+
id: params.id,
|
|
33
|
+
tenantId: auth.tenantId,
|
|
34
|
+
organizationId: auth.orgId,
|
|
35
|
+
deletedAt: null,
|
|
36
|
+
} as any,
|
|
37
|
+
undefined,
|
|
38
|
+
{ tenantId: auth.tenantId, organizationId: auth.orgId },
|
|
39
|
+
)
|
|
32
40
|
if (!user) {
|
|
33
41
|
return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })
|
|
34
42
|
}
|
|
@@ -37,14 +45,18 @@ export async function POST(req: Request, { params }: { params: { id: string } })
|
|
|
37
45
|
return NextResponse.json({ ok: true, alreadyVerified: true })
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
await em.nativeUpdate(CustomerUser, {
|
|
48
|
+
await em.nativeUpdate(CustomerUser, {
|
|
49
|
+
id: user.id,
|
|
50
|
+
tenantId: user.tenantId,
|
|
51
|
+
organizationId: user.organizationId,
|
|
52
|
+
}, { emailVerifiedAt: new Date() })
|
|
41
53
|
|
|
42
54
|
void emitCustomerAccountsEvent('customer_accounts.email.verified', {
|
|
43
55
|
id: user.id,
|
|
44
56
|
recipientUserId: user.id,
|
|
45
57
|
email: user.email,
|
|
46
|
-
tenantId:
|
|
47
|
-
organizationId:
|
|
58
|
+
tenantId: user.tenantId,
|
|
59
|
+
organizationId: user.organizationId,
|
|
48
60
|
verifiedBy: auth.sub,
|
|
49
61
|
}).catch(() => undefined)
|
|
50
62
|
|
|
@@ -41,7 +41,7 @@ export async function GET(req: Request, { params }: { params: { id: string } })
|
|
|
41
41
|
const user = await findOneWithDecryption(
|
|
42
42
|
em,
|
|
43
43
|
CustomerUser,
|
|
44
|
-
{ id: params.id, tenantId: auth.tenantId, deletedAt: null } as any,
|
|
44
|
+
{ id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null } as any,
|
|
45
45
|
undefined,
|
|
46
46
|
{ tenantId: auth.tenantId, organizationId: auth.orgId },
|
|
47
47
|
)
|
|
@@ -131,7 +131,7 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
|
|
|
131
131
|
const user = await findOneWithDecryption(
|
|
132
132
|
em,
|
|
133
133
|
CustomerUser,
|
|
134
|
-
{ id: params.id, tenantId: auth.tenantId, deletedAt: null } as any,
|
|
134
|
+
{ id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null } as any,
|
|
135
135
|
undefined,
|
|
136
136
|
{ tenantId: auth.tenantId, organizationId: auth.orgId },
|
|
137
137
|
)
|
|
@@ -179,7 +179,11 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
|
|
|
179
179
|
if (parsed.data.personEntityId !== undefined) updates.personEntityId = parsed.data.personEntityId
|
|
180
180
|
if (parsed.data.customerEntityId !== undefined) updates.customerEntityId = parsed.data.customerEntityId
|
|
181
181
|
|
|
182
|
-
await em.nativeUpdate(CustomerUser, {
|
|
182
|
+
await em.nativeUpdate(CustomerUser, {
|
|
183
|
+
id: user.id,
|
|
184
|
+
tenantId: user.tenantId,
|
|
185
|
+
organizationId: user.organizationId,
|
|
186
|
+
}, updates)
|
|
183
187
|
|
|
184
188
|
let rolesChanged = false
|
|
185
189
|
if (parsed.data.roleIds !== undefined) {
|
|
@@ -230,8 +234,8 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
|
|
|
230
234
|
id: user.id,
|
|
231
235
|
recipientUserId: user.id,
|
|
232
236
|
email: user.email,
|
|
233
|
-
tenantId:
|
|
234
|
-
organizationId:
|
|
237
|
+
tenantId: user.tenantId,
|
|
238
|
+
organizationId: user.organizationId,
|
|
235
239
|
updatedBy: auth.sub,
|
|
236
240
|
}).catch(() => undefined)
|
|
237
241
|
|
|
@@ -256,7 +260,7 @@ export async function DELETE(req: Request, { params }: { params: { id: string }
|
|
|
256
260
|
const user = await findOneWithDecryption(
|
|
257
261
|
em,
|
|
258
262
|
CustomerUser,
|
|
259
|
-
{ id: params.id, tenantId: auth.tenantId, deletedAt: null } as any,
|
|
263
|
+
{ id: params.id, tenantId: auth.tenantId, organizationId: auth.orgId, deletedAt: null } as any,
|
|
260
264
|
undefined,
|
|
261
265
|
{ tenantId: auth.tenantId, organizationId: auth.orgId },
|
|
262
266
|
)
|
|
@@ -281,14 +285,17 @@ export async function DELETE(req: Request, { params }: { params: { id: string }
|
|
|
281
285
|
const customerUserService = container.resolve('customerUserService') as CustomerUserService
|
|
282
286
|
const customerSessionService = container.resolve('customerSessionService') as CustomerSessionService
|
|
283
287
|
|
|
284
|
-
await customerUserService.softDelete(user.id
|
|
288
|
+
await customerUserService.softDelete(user.id, {
|
|
289
|
+
tenantId: user.tenantId,
|
|
290
|
+
organizationId: user.organizationId,
|
|
291
|
+
})
|
|
285
292
|
await customerSessionService.revokeAllUserSessions(user.id)
|
|
286
293
|
|
|
287
294
|
void emitCustomerAccountsEvent('customer_accounts.user.deleted', {
|
|
288
295
|
id: user.id,
|
|
289
296
|
email: user.email,
|
|
290
|
-
tenantId:
|
|
291
|
-
organizationId:
|
|
297
|
+
tenantId: user.tenantId,
|
|
298
|
+
organizationId: user.organizationId,
|
|
292
299
|
deletedBy: auth.sub,
|
|
293
300
|
}).catch(() => undefined)
|
|
294
301
|
|
|
@@ -47,8 +47,20 @@ export class CustomerUserService {
|
|
|
47
47
|
)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
async findById(
|
|
51
|
-
|
|
50
|
+
async findById(
|
|
51
|
+
id: string,
|
|
52
|
+
tenantId: string,
|
|
53
|
+
organizationId?: string | null,
|
|
54
|
+
): Promise<CustomerUser | null> {
|
|
55
|
+
const where: Record<string, unknown> = { id, tenantId, deletedAt: null }
|
|
56
|
+
if (organizationId !== undefined) where.organizationId = organizationId
|
|
57
|
+
return findOneWithDecryption(
|
|
58
|
+
this.em,
|
|
59
|
+
CustomerUser,
|
|
60
|
+
where as any,
|
|
61
|
+
undefined,
|
|
62
|
+
{ tenantId, organizationId },
|
|
63
|
+
)
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
async verifyPassword(user: CustomerUser, password: string): Promise<boolean> {
|
|
@@ -90,7 +102,11 @@ export class CustomerUserService {
|
|
|
90
102
|
|
|
91
103
|
async updatePassword(user: CustomerUser, newPassword: string, em?: EntityManager): Promise<void> {
|
|
92
104
|
const passwordHash = await hash(newPassword, BCRYPT_COST)
|
|
93
|
-
await (em ?? this.em).nativeUpdate(CustomerUser, {
|
|
105
|
+
await (em ?? this.em).nativeUpdate(CustomerUser, {
|
|
106
|
+
id: user.id,
|
|
107
|
+
tenantId: user.tenantId,
|
|
108
|
+
organizationId: user.organizationId,
|
|
109
|
+
}, { passwordHash })
|
|
94
110
|
user.passwordHash = passwordHash
|
|
95
111
|
}
|
|
96
112
|
|
|
@@ -102,8 +118,16 @@ export class CustomerUserService {
|
|
|
102
118
|
if (data.displayName !== undefined) user.displayName = data.displayName
|
|
103
119
|
}
|
|
104
120
|
|
|
105
|
-
async softDelete(
|
|
106
|
-
|
|
121
|
+
async softDelete(
|
|
122
|
+
userId: string,
|
|
123
|
+
scope?: { tenantId: string; organizationId: string | null },
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
const where: Record<string, unknown> = { id: userId }
|
|
126
|
+
if (scope) {
|
|
127
|
+
where.tenantId = scope.tenantId
|
|
128
|
+
where.organizationId = scope.organizationId
|
|
129
|
+
}
|
|
130
|
+
await this.em.nativeUpdate(CustomerUser, where, {
|
|
107
131
|
deletedAt: new Date(),
|
|
108
132
|
isActive: false,
|
|
109
133
|
})
|
|
@@ -388,10 +388,15 @@ function calculateSmartLayout(
|
|
|
388
388
|
const { id, level } = queue.shift()!
|
|
389
389
|
const currentLevel = levels.get(id)
|
|
390
390
|
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
391
|
+
// Only propagate when this path improves the node's level (longest path).
|
|
392
|
+
// Without this guard a cyclic graph (renegotiation/revision loops) keeps
|
|
393
|
+
// re-enqueuing children forever and freezes the browser (script timeout).
|
|
394
|
+
if (currentLevel !== undefined && level <= currentLevel) continue
|
|
395
|
+
levels.set(id, level)
|
|
396
|
+
|
|
397
|
+
// Safety cap: in a DAG a node's level is bounded by the node count; a
|
|
398
|
+
// higher value means we are walking a cycle, so stop descending.
|
|
399
|
+
if (level > steps.length) continue
|
|
395
400
|
|
|
396
401
|
const children = outgoing.get(id) || []
|
|
397
402
|
for (const child of children) {
|