@open-mercato/core 0.6.4-develop.4133.1.48fc6c8f7b → 0.6.4-develop.4161.1.g7895993543
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/auth/migrations/Migration20260601120000.js +17 -0
- package/dist/modules/auth/migrations/Migration20260601120000.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +1 -0
- 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 +1 -0
- 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 +1 -0
- 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 +1 -0
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/email/verify.js +1 -0
- package/dist/modules/customer_accounts/api/email/verify.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/events/stream.js +20 -2
- package/dist/modules/customer_accounts/api/portal/events/stream.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -13
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/planner/components/availabilityRulesEditorState.js +10 -1
- package/dist/modules/planner/components/availabilityRulesEditorState.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/migrations/.snapshot-open-mercato.json +32 -0
- package/src/modules/auth/migrations/Migration20260601120000.ts +23 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +1 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/send-reset-link.ts +1 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +1 -0
- package/src/modules/customer_accounts/api/admin/users/[id].ts +1 -0
- package/src/modules/customer_accounts/api/email/verify.ts +1 -0
- package/src/modules/customer_accounts/api/portal/events/stream.ts +23 -2
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +20 -13
- package/src/modules/planner/components/availabilityRulesEditorState.ts +21 -0
- package/src/modules/planner/i18n/de.json +3 -3
- package/src/modules/planner/i18n/en.json +3 -3
- package/src/modules/planner/i18n/es.json +3 -3
- package/src/modules/planner/i18n/pl.json +3 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260601120000 extends Migration {
|
|
3
|
+
up() {
|
|
4
|
+
this.addSql(`alter table "users" add column if not exists "updated_at" timestamptz null;`);
|
|
5
|
+
this.addSql(`update "users" set "updated_at" = "created_at" where "updated_at" is null;`);
|
|
6
|
+
this.addSql(`alter table "roles" add column if not exists "updated_at" timestamptz null;`);
|
|
7
|
+
this.addSql(`update "roles" set "updated_at" = "created_at" where "updated_at" is null;`);
|
|
8
|
+
}
|
|
9
|
+
down() {
|
|
10
|
+
this.addSql(`alter table "users" drop column if exists "updated_at";`);
|
|
11
|
+
this.addSql(`alter table "roles" drop column if exists "updated_at";`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
Migration20260601120000
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=Migration20260601120000.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/auth/migrations/Migration20260601120000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\n// Add `updated_at` to `users` and `roles` so user/role edits (and the ACL grants\n// keyed off them) participate in OSS optimistic locking (#2055). The column is\n// nullable to keep the migration online; existing rows are backfilled from\n// `created_at` so they carry a version immediately instead of being unprotected\n// until their first edit.\nexport class Migration20260601120000 extends Migration {\n\n override up(): void | Promise<void> {\n this.addSql(`alter table \"users\" add column if not exists \"updated_at\" timestamptz null;`);\n this.addSql(`update \"users\" set \"updated_at\" = \"created_at\" where \"updated_at\" is null;`);\n\n this.addSql(`alter table \"roles\" add column if not exists \"updated_at\" timestamptz null;`);\n this.addSql(`update \"roles\" set \"updated_at\" = \"created_at\" where \"updated_at\" is null;`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`alter table \"users\" drop column if exists \"updated_at\";`);\n this.addSql(`alter table \"roles\" drop column if exists \"updated_at\";`);\n }\n\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAOnB,MAAM,gCAAgC,UAAU;AAAA,EAE5C,KAA2B;AAClC,SAAK,OAAO,6EAA6E;AACzF,SAAK,OAAO,4EAA4E;AAExF,SAAK,OAAO,6EAA6E;AACzF,SAAK,OAAO,4EAA4E;AAAA,EAC1F;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,yDAAyD;AACrE,SAAK,OAAO,yDAAyD;AAAA,EACvE;AAEF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -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!)\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 email: user.email,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n resetBy: auth.sub,\n }).catch(() => undefined)\n\n void emitCustomerAccountsEvent('customer_accounts.password.changed', {\n userId: user.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\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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",
|
|
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!)\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: auth.tenantId,\n organizationId: auth.orgId,\n resetBy: auth.sub,\n }).catch(() => undefined)\n\n void emitCustomerAccountsEvent('customer_accounts.password.changed', {\n userId: user.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\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
|
}
|
|
@@ -25,6 +25,7 @@ async function POST(req, { params }) {
|
|
|
25
25
|
const resetLink = `/portal/reset-password?token=${rawToken}`;
|
|
26
26
|
void emitCustomerAccountsEvent("customer_accounts.password.reset", {
|
|
27
27
|
id: user.id,
|
|
28
|
+
recipientUserId: user.id,
|
|
28
29
|
email: user.email,
|
|
29
30
|
tenantId: auth.tenantId,
|
|
30
31
|
organizationId: auth.orgId,
|
|
@@ -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!)\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, auth.tenantId!)\n\n const resetLink = `/portal/reset-password?token=${rawToken}`\n\n void emitCustomerAccountsEvent('customer_accounts.password.reset', {\n id: user.id,\n email: user.email,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\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,QAAS;AACzE,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,QAAS;AAEvF,QAAM,YAAY,gCAAgC,QAAQ;AAE1D,OAAK,0BAA0B,oCAAoC;AAAA,IACjE,IAAI,KAAK;AAAA,IACT,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;",
|
|
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!)\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, auth.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: auth.tenantId,\n organizationId: auth.orgId,\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,QAAS;AACzE,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,QAAS;AAEvF,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
|
}
|
|
@@ -31,6 +31,7 @@ async function POST(req, { params }) {
|
|
|
31
31
|
await em.nativeUpdate(CustomerUser, { id: user.id }, { emailVerifiedAt: /* @__PURE__ */ new Date() });
|
|
32
32
|
void emitCustomerAccountsEvent("customer_accounts.email.verified", {
|
|
33
33
|
id: user.id,
|
|
34
|
+
recipientUserId: user.id,
|
|
34
35
|
email: user.email,
|
|
35
36
|
tenantId: auth.tenantId,
|
|
36
37
|
organizationId: auth.orgId,
|
|
@@ -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.findOne(CustomerUser, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\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, { id: user.id }, { emailVerifiedAt: new Date() })\n\n void emitCustomerAccountsEvent('customer_accounts.email.verified', {\n id: user.id,\n email: user.email,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\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": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,oBAAoB;AAC7B,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,KAAK,UAAU,QAAQ,IAAI;AAEjC,QAAM,OAAO,MAAM,GAAG,QAAQ,cAAc;AAAA,IAC1C,IAAI,OAAO;AAAA,IACX,UAAU,KAAK;AAAA,IACf,WAAW;AAAA,EACb,CAAC;AACD,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClF;AAEA,MAAI,KAAK,iBAAiB;AACxB,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,iBAAiB,KAAK,CAAC;AAAA,EAC9D;AAEA,QAAM,GAAG,aAAa,cAAc,EAAE,IAAI,KAAK,GAAG,GAAG,EAAE,iBAAiB,oBAAI,KAAK,EAAE,CAAC;AAEpF,OAAK,0BAA0B,oCAAoC;AAAA,IACjE,IAAI,KAAK;AAAA,IACT,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,IACrB,YAAY,KAAK;AAAA,EACnB,CAAC,EAAE,MAAM,MAAM,MAAS;AAExB,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,GAAG,iBAAiB,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAC/F,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,kBAAkB,QAAQ,cAAc,CAAC;AAAA,EACjF,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;",
|
|
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.findOne(CustomerUser, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\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, { id: user.id }, { 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: auth.tenantId,\n organizationId: auth.orgId,\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": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,oBAAoB;AAC7B,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,KAAK,UAAU,QAAQ,IAAI;AAEjC,QAAM,OAAO,MAAM,GAAG,QAAQ,cAAc;AAAA,IAC1C,IAAI,OAAO;AAAA,IACX,UAAU,KAAK;AAAA,IACf,WAAW;AAAA,EACb,CAAC;AACD,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClF;AAEA,MAAI,KAAK,iBAAiB;AACxB,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,iBAAiB,KAAK,CAAC;AAAA,EAC9D;AAEA,QAAM,GAAG,aAAa,cAAc,EAAE,IAAI,KAAK,GAAG,GAAG,EAAE,iBAAiB,oBAAI,KAAK,EAAE,CAAC;AAEpF,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,YAAY,KAAK;AAAA,EACnB,CAAC,EAAE,MAAM,MAAM,MAAS;AAExB,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,GAAG,iBAAiB,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAC/F,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,kBAAkB,QAAQ,cAAc,CAAC;AAAA,EACjF,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
|
}
|
|
@@ -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'\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 const updates: Record<string, unknown> = {}\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 if (Object.keys(updates).length > 0) {\n await em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n }\n\n let rolesChanged = false\n if (parsed.data.roleIds !== undefined) {\n const requestedRoleIds = parsed.data.roleIds\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 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 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 })\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 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": "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",
|
|
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'\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 const updates: Record<string, unknown> = {}\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 if (Object.keys(updates).length > 0) {\n await em.nativeUpdate(CustomerUser, { id: user.id }, updates)\n }\n\n let rolesChanged = false\n if (parsed.data.roleIds !== undefined) {\n const requestedRoleIds = parsed.data.roleIds\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 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 })\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 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": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -26,6 +26,7 @@ async function POST(req) {
|
|
|
26
26
|
await em.nativeUpdate(CustomerUser, { id: result.userId, tenantId: result.tenantId }, { emailVerifiedAt: /* @__PURE__ */ new Date() });
|
|
27
27
|
void emitCustomerAccountsEvent("customer_accounts.email.verified", {
|
|
28
28
|
userId: result.userId,
|
|
29
|
+
recipientUserId: result.userId,
|
|
29
30
|
tenantId: result.tenantId
|
|
30
31
|
}).catch(() => void 0);
|
|
31
32
|
return NextResponse.json({ ok: true });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customer_accounts/api/email/verify.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { emailVerifySchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\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: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\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 = emailVerifySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid token' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const result = await customerTokenService.verifyEmailToken(parsed.data.token, 'email_verification')\n if (!result) {\n return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })\n }\n\n await em.nativeUpdate(CustomerUser, { id: result.userId, tenantId: result.tenantId }, { emailVerifiedAt: new Date() })\n\n void emitCustomerAccountsEvent('customer_accounts.email.verified', {\n userId: result.userId,\n tenantId: result.tenantId,\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: 'Verify customer email address',\n description: 'Validates the email verification token and marks the email as verified.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: emailVerifySchema,\n description: 'Email verification token.',\n },\n responses: [\n { status: 200, description: 'Email verified', schema: successSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid or expired token', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Verify customer email',\n description: 'Handles email verification for customer accounts.',\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,yBAAyB;AAClC,SAAS,8BAA8B;AAEvC,SAAS,oBAAoB;AAC7B,SAAS,iCAAiC;AAEnC,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,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,kBAAkB,UAAU,IAAI;AAC/C,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AACrE,QAAM,KAAK,UAAU,QAAQ,IAAI;AAEjC,QAAM,SAAS,MAAM,qBAAqB,iBAAiB,OAAO,KAAK,OAAO,oBAAoB;AAClG,MAAI,CAAC,QAAQ;AACX,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,GAAG,aAAa,cAAc,EAAE,IAAI,OAAO,QAAQ,UAAU,OAAO,SAAS,GAAG,EAAE,iBAAiB,oBAAI,KAAK,EAAE,CAAC;AAErH,OAAK,0BAA0B,oCAAoC;AAAA,IACjE,QAAQ,OAAO;AAAA,IACf,UAAU,OAAO;AAAA,EACnB,CAAC,EAAE,MAAM,MAAM,MAAS;AAExB,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AACtD,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,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,cAAc;AAAA,EACtE;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,YAAY;AAAA,EAC9E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,UAAU;AAC7B;",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { emailVerifySchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\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: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\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 = emailVerifySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid token' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const customerTokenService = container.resolve('customerTokenService') as CustomerTokenService\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const result = await customerTokenService.verifyEmailToken(parsed.data.token, 'email_verification')\n if (!result) {\n return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })\n }\n\n await em.nativeUpdate(CustomerUser, { id: result.userId, tenantId: result.tenantId }, { emailVerifiedAt: new Date() })\n\n void emitCustomerAccountsEvent('customer_accounts.email.verified', {\n userId: result.userId,\n recipientUserId: result.userId,\n tenantId: result.tenantId,\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: 'Verify customer email address',\n description: 'Validates the email verification token and marks the email as verified.',\n tags: ['Customer Authentication'],\n requestBody: {\n schema: emailVerifySchema,\n description: 'Email verification token.',\n },\n responses: [\n { status: 200, description: 'Email verified', schema: successSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid or expired token', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Verify customer email',\n description: 'Handles email verification for customer accounts.',\n methods: { POST: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,yBAAyB;AAClC,SAAS,8BAA8B;AAEvC,SAAS,oBAAoB;AAC7B,SAAS,iCAAiC;AAEnC,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,eAAsB,KAAK,KAAc;AACvC,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,kBAAkB,UAAU,IAAI;AAC/C,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,uBAAuB,UAAU,QAAQ,sBAAsB;AACrE,QAAM,KAAK,UAAU,QAAQ,IAAI;AAEjC,QAAM,SAAS,MAAM,qBAAqB,iBAAiB,OAAO,KAAK,OAAO,oBAAoB;AAClG,MAAI,CAAC,QAAQ;AACX,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5F;AAEA,QAAM,GAAG,aAAa,cAAc,EAAE,IAAI,OAAO,QAAQ,UAAU,OAAO,SAAS,GAAG,EAAE,iBAAiB,oBAAI,KAAK,EAAE,CAAC;AAErH,OAAK,0BAA0B,oCAAoC;AAAA,IACjE,QAAQ,OAAO;AAAA,IACf,iBAAiB,OAAO;AAAA,IACxB,UAAU,OAAO;AAAA,EACnB,CAAC,EAAE,MAAM,MAAM,MAAS;AAExB,SAAO,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AACvC;AAEA,MAAM,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;AACtD,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,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,cAAc;AAAA,EACtE;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,YAAY;AAAA,EAC9E;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,UAAU;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,7 +17,22 @@ function normalizeAudience(data) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
const recipientUserScopes = /* @__PURE__ */ new Set();
|
|
21
|
+
if (typeof data.recipientUserId === "string" && data.recipientUserId.trim().length > 0) {
|
|
22
|
+
recipientUserScopes.add(data.recipientUserId.trim());
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(data.recipientUserIds)) {
|
|
25
|
+
for (const userId of data.recipientUserIds) {
|
|
26
|
+
if (typeof userId === "string" && userId.trim().length > 0) {
|
|
27
|
+
recipientUserScopes.add(userId.trim());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
tenantId,
|
|
33
|
+
organizationScopes: Array.from(organizationScopes),
|
|
34
|
+
recipientUserScopes: Array.from(recipientUserScopes)
|
|
35
|
+
};
|
|
21
36
|
}
|
|
22
37
|
function matchesAudience(conn, audience) {
|
|
23
38
|
if (!audience.tenantId) return false;
|
|
@@ -25,6 +40,9 @@ function matchesAudience(conn, audience) {
|
|
|
25
40
|
if (audience.organizationScopes.length > 0) {
|
|
26
41
|
if (!audience.organizationScopes.includes(conn.organizationId)) return false;
|
|
27
42
|
}
|
|
43
|
+
if (audience.recipientUserScopes.length > 0 && !audience.recipientUserScopes.includes(conn.customerUserId)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
28
46
|
return true;
|
|
29
47
|
}
|
|
30
48
|
const portalConnections = /* @__PURE__ */ new Set();
|
|
@@ -141,7 +159,7 @@ async function GET(req) {
|
|
|
141
159
|
}
|
|
142
160
|
const methodDoc = {
|
|
143
161
|
summary: "Subscribe to portal events via SSE (Portal Event Bridge)",
|
|
144
|
-
description: "Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer's tenant and
|
|
162
|
+
description: "Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer's tenant, organization, and recipient user audience.",
|
|
145
163
|
tags: ["Customer Portal"],
|
|
146
164
|
responses: [
|
|
147
165
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customer_accounts/api/portal/events/stream.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Portal SSE Event Stream \u2014 Portal Event Bridge\n *\n * Server-Sent Events endpoint that bridges server-side events to the customer portal.\n * Only events with `portalBroadcast: true` in their EventDefinition are sent.\n * Events are scoped to the authenticated customer's tenant and organization.\n *\n * Uses customer JWT auth (cookie or Bearer token) instead of staff auth.\n *\n * Client consumer: `packages/ui/src/portal/hooks/usePortalEventBridge.ts`\n */\n\nimport { NextResponse } from 'next/server'\nimport { isPortalBroadcastEvent } from '@open-mercato/shared/modules/events'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nconst HEARTBEAT_INTERVAL_MS = 30_000\nconst MAX_PAYLOAD_BYTES = 4096\n\ntype PortalSseConnection = {\n tenantId: string\n organizationId: string\n customerUserId: string\n send: (data: string) => void\n close: () => void\n}\n\nfunction normalizeAudience(data: Record<string, unknown>): {\n tenantId: string | null\n organizationScopes: string[]\n} {\n const tenantId = typeof data.tenantId === 'string' ? data.tenantId : null\n const organizationScopes = new Set<string>()\n if (typeof data.organizationId === 'string' && data.organizationId.trim().length > 0) {\n organizationScopes.add(data.organizationId.trim())\n }\n if (Array.isArray(data.organizationIds)) {\n for (const orgId of data.organizationIds) {\n if (typeof orgId === 'string' && orgId.trim().length > 0) {\n organizationScopes.add(orgId.trim())\n }\n }\n }\n return {
|
|
5
|
-
"mappings": "AAYA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,kCAAkC;AAGpC,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,MAAM,wBAAwB;AAC9B,MAAM,oBAAoB;AAU1B,SAAS,kBAAkB,
|
|
4
|
+
"sourcesContent": ["/**\n * Portal SSE Event Stream \u2014 Portal Event Bridge\n *\n * Server-Sent Events endpoint that bridges server-side events to the customer portal.\n * Only events with `portalBroadcast: true` in their EventDefinition are sent.\n * Events are scoped to the authenticated customer's tenant and organization.\n *\n * Uses customer JWT auth (cookie or Bearer token) instead of staff auth.\n *\n * Client consumer: `packages/ui/src/portal/hooks/usePortalEventBridge.ts`\n */\n\nimport { NextResponse } from 'next/server'\nimport { isPortalBroadcastEvent } from '@open-mercato/shared/modules/events'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nconst HEARTBEAT_INTERVAL_MS = 30_000\nconst MAX_PAYLOAD_BYTES = 4096\n\ntype PortalSseConnection = {\n tenantId: string\n organizationId: string\n customerUserId: string\n send: (data: string) => void\n close: () => void\n}\n\nfunction normalizeAudience(data: Record<string, unknown>): {\n tenantId: string | null\n organizationScopes: string[]\n recipientUserScopes: string[]\n} {\n const tenantId = typeof data.tenantId === 'string' ? data.tenantId : null\n const organizationScopes = new Set<string>()\n if (typeof data.organizationId === 'string' && data.organizationId.trim().length > 0) {\n organizationScopes.add(data.organizationId.trim())\n }\n if (Array.isArray(data.organizationIds)) {\n for (const orgId of data.organizationIds) {\n if (typeof orgId === 'string' && orgId.trim().length > 0) {\n organizationScopes.add(orgId.trim())\n }\n }\n }\n\n const recipientUserScopes = new Set<string>()\n if (typeof data.recipientUserId === 'string' && data.recipientUserId.trim().length > 0) {\n recipientUserScopes.add(data.recipientUserId.trim())\n }\n if (Array.isArray(data.recipientUserIds)) {\n for (const userId of data.recipientUserIds) {\n if (typeof userId === 'string' && userId.trim().length > 0) {\n recipientUserScopes.add(userId.trim())\n }\n }\n }\n\n return {\n tenantId,\n organizationScopes: Array.from(organizationScopes),\n recipientUserScopes: Array.from(recipientUserScopes),\n }\n}\n\nfunction matchesAudience(conn: PortalSseConnection, audience: ReturnType<typeof normalizeAudience>): boolean {\n if (!audience.tenantId) return false\n if (conn.tenantId !== audience.tenantId) return false\n if (audience.organizationScopes.length > 0) {\n if (!audience.organizationScopes.includes(conn.organizationId)) return false\n }\n if (audience.recipientUserScopes.length > 0 && !audience.recipientUserScopes.includes(conn.customerUserId)) {\n return false\n }\n return true\n}\n\nconst portalConnections = new Set<PortalSseConnection>()\n\nlet portalTapRegistered = false\n\nasync function broadcastPortalEvent(eventName: string, payload: Record<string, unknown>): Promise<void> {\n if (!eventName || portalConnections.size === 0) return\n if (!isPortalBroadcastEvent(eventName)) return\n\n const data = payload ?? {}\n const audience = normalizeAudience(data)\n const organizationId = audience.organizationScopes[0] ?? ''\n\n let ssePayload = JSON.stringify({\n id: eventName,\n payload: data,\n timestamp: Date.now(),\n organizationId,\n })\n\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n const entityRef: Record<string, unknown> = { truncated: true }\n if (typeof data.id === 'string' && data.id.trim().length > 0) entityRef.id = data.id.trim()\n if (typeof data.entityId === 'string' && data.entityId.trim().length > 0) entityRef.entityId = data.entityId.trim()\n ssePayload = JSON.stringify({\n id: eventName,\n payload: entityRef,\n timestamp: Date.now(),\n organizationId,\n })\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n return\n }\n }\n\n for (const conn of portalConnections) {\n if (!matchesAudience(conn, audience)) continue\n try {\n conn.send(ssePayload)\n } catch {\n // Connection may have been closed\n }\n }\n}\n\nfunction ensurePortalTap(): void {\n if (portalTapRegistered) return\n portalTapRegistered = true\n\n // Dynamically import to avoid circular dependency \u2014 the events bus\n // registers a global tap that fires for every emitted event.\n import('@open-mercato/events/bus').then(({ registerGlobalEventTap, registerCrossProcessEventListener }) => {\n registerGlobalEventTap(async (eventName, payload) => {\n await broadcastPortalEvent(eventName, (payload ?? {}) as Record<string, unknown>)\n })\n\n registerCrossProcessEventListener(async (envelope) => {\n if (envelope.originPid === process.pid) return\n await broadcastPortalEvent(\n envelope.event,\n (envelope.payload ?? {}) as Record<string, unknown>,\n )\n })\n }).catch(() => {\n // Silently ignore if events package is not available\n portalTapRegistered = false\n })\n}\n\nexport async function GET(req: Request): Promise<Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n ensurePortalTap()\n\n const encoder = new TextEncoder()\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null\n let connection: PortalSseConnection | null = null\n const onAbort = () => cleanup()\n\n const stream = new ReadableStream({\n start(controller) {\n const send = (data: string) => {\n controller.enqueue(encoder.encode(`data: ${data}\\n\\n`))\n }\n\n connection = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n customerUserId: auth.sub,\n send,\n close: () => controller.close(),\n }\n portalConnections.add(connection)\n\n heartbeatTimer = setInterval(() => {\n try {\n controller.enqueue(encoder.encode(':heartbeat\\n\\n'))\n } catch {\n // Stream may have been closed\n }\n }, HEARTBEAT_INTERVAL_MS)\n },\n cancel() {\n cleanup()\n },\n })\n\n function cleanup() {\n if (heartbeatTimer) {\n clearInterval(heartbeatTimer)\n heartbeatTimer = null\n }\n if (connection) {\n portalConnections.delete(connection)\n connection = null\n }\n // Detach from the request signal so reconnect churn does not accumulate\n // listeners and closures on long-lived AbortSignals.\n req.signal.removeEventListener('abort', onAbort)\n }\n\n req.signal.addEventListener('abort', onAbort, { once: true })\n\n return new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n })\n}\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Subscribe to portal events via SSE (Portal Event Bridge)',\n description: 'Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer\\'s tenant, organization, and recipient user audience.',\n tags: ['Customer Portal'],\n responses: [\n {\n status: 200,\n description: 'Event stream (text/event-stream)',\n },\n ],\n errors: [\n { status: 401, description: 'Not authenticated' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Portal event stream',\n methods: { GET: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -29,7 +29,11 @@ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
|
29
29
|
import { parseAvailabilityRuleWindow } from "@open-mercato/core/modules/planner/lib/availabilitySchedule";
|
|
30
30
|
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
31
31
|
import { Calendar, Clock, List, PencilLine, Plus, Trash2 } from "lucide-react";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
resolveRuleSetSelectValue,
|
|
34
|
+
requiresResetConfirmation,
|
|
35
|
+
selectCustomRuleIdsToDelete
|
|
36
|
+
} from "./availabilityRulesEditorState.js";
|
|
33
37
|
const DAY_LABELS = [
|
|
34
38
|
{ code: "SU", short: "S", nameKey: "schedule.weekday.sunday", fallback: "Sunday" },
|
|
35
39
|
{ code: "MO", short: "M", nameKey: "schedule.weekday.monday", fallback: "Monday" },
|
|
@@ -365,7 +369,7 @@ function AvailabilityRulesEditor({
|
|
|
365
369
|
ruleSetPlaceholder: t(`${labelPrefix}.availability.ruleset.placeholder`, "Custom schedule"),
|
|
366
370
|
ruleSetCustomize: t(`${labelPrefix}.availability.ruleset.customize`, "Customize schedule"),
|
|
367
371
|
ruleSetReset: t(`${labelPrefix}.availability.ruleset.reset`, "Reset to schedule"),
|
|
368
|
-
ruleSetConfirm: t(`${labelPrefix}.availability.ruleset.confirm`, "
|
|
372
|
+
ruleSetConfirm: t(`${labelPrefix}.availability.ruleset.confirm`, "Resetting to the schedule will delete your custom hours. Continue?"),
|
|
369
373
|
ruleSetLoading: t(`${labelPrefix}.availability.ruleset.loading`, "Loading schedules..."),
|
|
370
374
|
ruleSetError: t(`${labelPrefix}.availability.ruleset.error`, "Failed to load schedules."),
|
|
371
375
|
ruleSetCreateLabel: t(`${labelPrefix}.availability.ruleset.create`, "New schedule"),
|
|
@@ -830,9 +834,17 @@ function AvailabilityRulesEditor({
|
|
|
830
834
|
const handleResetToRuleSet = React.useCallback(async () => {
|
|
831
835
|
if (isReadOnly) return;
|
|
832
836
|
if (!effectiveRulesetId) return;
|
|
837
|
+
if (requiresResetConfirmation(availabilityRules)) {
|
|
838
|
+
const confirmed = await confirm({
|
|
839
|
+
title: listLabels.ruleSetConfirm,
|
|
840
|
+
variant: "destructive"
|
|
841
|
+
});
|
|
842
|
+
if (!confirmed) return;
|
|
843
|
+
}
|
|
833
844
|
try {
|
|
845
|
+
const idsToDelete = selectCustomRuleIdsToDelete("reset", availabilityRules);
|
|
834
846
|
await Promise.all(
|
|
835
|
-
|
|
847
|
+
idsToDelete.map((id) => deleteCrud("planner/availability", id, { errorMessage: listLabels.saveWeeklyError }))
|
|
836
848
|
);
|
|
837
849
|
setCustomOverridesEnabled(false);
|
|
838
850
|
await refreshAvailability();
|
|
@@ -840,18 +852,14 @@ function AvailabilityRulesEditor({
|
|
|
840
852
|
const message = error2 instanceof Error ? error2.message : listLabels.saveWeeklyError;
|
|
841
853
|
flash(message, "error");
|
|
842
854
|
}
|
|
843
|
-
}, [availabilityRules, effectiveRulesetId, listLabels.saveWeeklyError, refreshAvailability, isReadOnly]);
|
|
855
|
+
}, [availabilityRules, confirm, effectiveRulesetId, listLabels.ruleSetConfirm, listLabels.saveWeeklyError, refreshAvailability, isReadOnly]);
|
|
844
856
|
const handleRuleSetChange = React.useCallback(async (nextId) => {
|
|
845
857
|
if (isReadOnly) return;
|
|
846
858
|
if (!onRulesetChange) return;
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
title: listLabels.ruleSetConfirm,
|
|
850
|
-
variant: "default"
|
|
851
|
-
});
|
|
852
|
-
if (!confirmed) return;
|
|
859
|
+
const idsToDelete = selectCustomRuleIdsToDelete("switch", availabilityRules);
|
|
860
|
+
if (idsToDelete.length) {
|
|
853
861
|
await Promise.all(
|
|
854
|
-
|
|
862
|
+
idsToDelete.map((id) => deleteCrud("planner/availability", id, { errorMessage: listLabels.saveWeeklyError }))
|
|
855
863
|
);
|
|
856
864
|
}
|
|
857
865
|
setSelectedRulesetId(nextId);
|
|
@@ -865,9 +873,7 @@ function AvailabilityRulesEditor({
|
|
|
865
873
|
}
|
|
866
874
|
}, [
|
|
867
875
|
availabilityRules,
|
|
868
|
-
confirm,
|
|
869
876
|
effectiveRulesetId,
|
|
870
|
-
listLabels.ruleSetConfirm,
|
|
871
877
|
listLabels.saveWeeklyError,
|
|
872
878
|
onRulesetChange,
|
|
873
879
|
refreshAvailability,
|