@open-mercato/core 0.6.7-develop.6553.1.2ec7246e04 → 0.6.7-develop.6566.1.15385fa179
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/attachments/api/partitions/route.js.map +1 -1
- package/dist/modules/attachments/lib/reconcileOrganization.js +9 -7
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js.map +1 -1
- package/dist/modules/customers/api/companies/[id]/people/route.js +2 -2
- package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/companies/route.js +2 -2
- package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js +2 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +4 -4
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/entity-roles-factory.js +3 -3
- package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
- package/dist/modules/customers/api/labels/assign/route.js +2 -2
- package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
- package/dist/modules/customers/api/labels/unassign/route.js +2 -2
- package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +3 -7
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/context.js +3 -3
- package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +2 -2
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/customers/commands/addresses.js +3 -3
- package/dist/modules/customers/commands/addresses.js.map +2 -2
- package/dist/modules/customers/commands/comments.js +3 -3
- package/dist/modules/customers/commands/comments.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +2 -2
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +3 -3
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/dictionaries.js +3 -3
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/entity-roles.js +3 -3
- package/dist/modules/customers/commands/entity-roles.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +6 -6
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/labels.js +7 -7
- package/dist/modules/customers/commands/labels.js.map +2 -2
- package/dist/modules/customers/commands/people.js +3 -3
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/dist/modules/customers/commands/personCompanyLinks.js +6 -6
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +3 -3
- package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
- package/dist/modules/customers/commands/pipelines.js +3 -3
- package/dist/modules/customers/commands/pipelines.js.map +2 -2
- package/dist/modules/customers/commands/shared.js +3 -3
- package/dist/modules/customers/commands/shared.js.map +2 -2
- package/dist/modules/customers/commands/tags.js +7 -7
- package/dist/modules/customers/commands/tags.js.map +2 -2
- package/dist/modules/customers/commands/todos.js +3 -3
- package/dist/modules/customers/commands/todos.js.map +2 -2
- package/dist/modules/customers/lib/personCompanies.js +4 -4
- package/dist/modules/customers/lib/personCompanies.js.map +2 -2
- package/dist/modules/perspectives/data/validators.js +4 -0
- package/dist/modules/perspectives/data/validators.js.map +2 -2
- package/dist/modules/sales/api/notes/route.js +2 -4
- package/dist/modules/sales/api/notes/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/public/[token]/route.js +3 -7
- package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/send/route.js +2 -2
- package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
- package/dist/modules/sales/api/returns/[id]/route.js +2 -2
- package/dist/modules/sales/api/returns/[id]/route.js.map +2 -2
- package/dist/modules/sales/commands/configuration.js +11 -11
- package/dist/modules/sales/commands/configuration.js.map +2 -2
- package/dist/modules/sales/commands/documentAddresses.js +3 -3
- package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +31 -39
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/notes.js +7 -7
- package/dist/modules/sales/commands/notes.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +2 -2
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +8 -8
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +2 -2
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/commands/shipments.js +5 -5
- package/dist/modules/sales/commands/shipments.js.map +2 -2
- package/dist/modules/sales/commands/statuses.js +2 -2
- package/dist/modules/sales/commands/statuses.js.map +2 -2
- package/dist/modules/sales/commands/tags.js +3 -3
- package/dist/modules/sales/commands/tags.js.map +2 -2
- package/package.json +8 -8
- package/src/modules/attachments/api/partitions/route.ts +1 -1
- package/src/modules/attachments/lib/reconcileOrganization.ts +16 -7
- package/src/modules/catalog/api/products/route.ts +1 -1
- package/src/modules/customer_accounts/i18n/de.json +12 -0
- package/src/modules/customer_accounts/i18n/en.json +12 -0
- package/src/modules/customer_accounts/i18n/es.json +34 -22
- package/src/modules/customer_accounts/i18n/pl.json +32 -20
- package/src/modules/customers/api/companies/[id]/people/route.ts +2 -2
- package/src/modules/customers/api/deals/[id]/companies/route.ts +2 -2
- package/src/modules/customers/api/deals/[id]/people/route.ts +2 -2
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +4 -4
- package/src/modules/customers/api/entity-roles-factory.ts +3 -3
- package/src/modules/customers/api/labels/assign/route.ts +2 -2
- package/src/modules/customers/api/labels/unassign/route.ts +2 -2
- package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +3 -7
- package/src/modules/customers/api/people/[id]/companies/context.ts +3 -3
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +2 -2
- package/src/modules/customers/api/todos/route.ts +2 -2
- package/src/modules/customers/commands/addresses.ts +3 -3
- package/src/modules/customers/commands/comments.ts +3 -3
- package/src/modules/customers/commands/companies.ts +2 -2
- package/src/modules/customers/commands/deals.ts +3 -3
- package/src/modules/customers/commands/dictionaries.ts +3 -3
- package/src/modules/customers/commands/entity-roles.ts +3 -3
- package/src/modules/customers/commands/interactions.ts +6 -6
- package/src/modules/customers/commands/labels.ts +7 -7
- package/src/modules/customers/commands/people.ts +3 -3
- package/src/modules/customers/commands/personCompanyLinks.ts +6 -6
- package/src/modules/customers/commands/pipeline-stages.ts +3 -3
- package/src/modules/customers/commands/pipelines.ts +3 -3
- package/src/modules/customers/commands/shared.ts +3 -3
- package/src/modules/customers/commands/tags.ts +7 -7
- package/src/modules/customers/commands/todos.ts +3 -3
- package/src/modules/customers/lib/personCompanies.ts +4 -4
- package/src/modules/perspectives/data/validators.ts +4 -0
- package/src/modules/sales/api/notes/route.ts +2 -4
- package/src/modules/sales/api/quotes/accept/route.ts +2 -2
- package/src/modules/sales/api/quotes/public/[token]/route.ts +3 -7
- package/src/modules/sales/api/quotes/send/route.ts +2 -2
- package/src/modules/sales/api/returns/[id]/route.ts +2 -2
- package/src/modules/sales/commands/configuration.ts +11 -11
- package/src/modules/sales/commands/documentAddresses.ts +3 -3
- package/src/modules/sales/commands/documents.ts +23 -31
- package/src/modules/sales/commands/notes.ts +7 -7
- package/src/modules/sales/commands/payments.ts +2 -2
- package/src/modules/sales/commands/returns.ts +8 -8
- package/src/modules/sales/commands/shared.ts +2 -2
- package/src/modules/sales/commands/shipments.ts +5 -5
- package/src/modules/sales/commands/statuses.ts +2 -2
- package/src/modules/sales/commands/tags.ts +3 -3
- package/tsconfig.json +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customers/api/deals/%5Bid%5D/companies/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { CustomerCompanyProfile, CustomerDeal, CustomerDealCompanyLink, CustomerEntity } from '../../../../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst querySchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(20),\n search: z.string().optional(),\n sort: z.enum(['label-asc', 'label-desc', 'name-asc', 'name-desc', 'recent']).default('label-asc'),\n})\n\ntype DealLinkedEntitySort = 'label-asc' | 'label-desc' | 'recent'\n\nfunction normalizeSort(sort: z.infer<typeof querySchema>['sort']): DealLinkedEntitySort {\n if (sort === 'name-asc') return 'label-asc'\n if (sort === 'name-desc') return 'label-desc'\n return sort\n}\n\ntype DealCompanyItem = {\n id: string\n label: string\n subtitle: string | null\n kind: 'company'\n linkedAt: string\n}\n\nfunction matchesSearch(item: DealCompanyItem, query: string): boolean {\n const normalized = query.trim().toLowerCase()\n if (!normalized.length) return true\n return [item.label, item.subtitle]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .some((value) => value.toLowerCase().includes(normalized))\n}\n\nfunction sortItems(items: DealCompanyItem[], sort: 'label-asc' | 'label-desc' | 'recent'): DealCompanyItem[] {\n if (sort === 'recent') {\n return [...items].sort((left, right) => {\n const compare = new Date(right.linkedAt).getTime() - new Date(left.linkedAt).getTime()\n if (compare !== 0) return compare\n return left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n })\n }\n return [...items].sort((left, right) => {\n const compare = left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n return sort === 'label-asc' ? compare : -compare\n })\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) throw new CrudHttpError(401, { error: 'Unauthorized' })\n\n const url = new URL(req.url)\n const query = querySchema.parse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sort: url.searchParams.get('sort') ?? undefined,\n })\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = (container.resolve('em') as EntityManager).fork()\n const decryptionScope = {\n tenantId: auth.tenantId,\n organizationId: scope?.selectedId ?? auth.orgId ?? null,\n }\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id, tenantId: auth.tenantId, deletedAt: null },\n {},\n decryptionScope,\n )\n if (!deal) {\n throw
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { CustomerCompanyProfile, CustomerDeal, CustomerDealCompanyLink, CustomerEntity } from '../../../../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst querySchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(20),\n search: z.string().optional(),\n sort: z.enum(['label-asc', 'label-desc', 'name-asc', 'name-desc', 'recent']).default('label-asc'),\n})\n\ntype DealLinkedEntitySort = 'label-asc' | 'label-desc' | 'recent'\n\nfunction normalizeSort(sort: z.infer<typeof querySchema>['sort']): DealLinkedEntitySort {\n if (sort === 'name-asc') return 'label-asc'\n if (sort === 'name-desc') return 'label-desc'\n return sort\n}\n\ntype DealCompanyItem = {\n id: string\n label: string\n subtitle: string | null\n kind: 'company'\n linkedAt: string\n}\n\nfunction matchesSearch(item: DealCompanyItem, query: string): boolean {\n const normalized = query.trim().toLowerCase()\n if (!normalized.length) return true\n return [item.label, item.subtitle]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .some((value) => value.toLowerCase().includes(normalized))\n}\n\nfunction sortItems(items: DealCompanyItem[], sort: 'label-asc' | 'label-desc' | 'recent'): DealCompanyItem[] {\n if (sort === 'recent') {\n return [...items].sort((left, right) => {\n const compare = new Date(right.linkedAt).getTime() - new Date(left.linkedAt).getTime()\n if (compare !== 0) return compare\n return left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n })\n }\n return [...items].sort((left, right) => {\n const compare = left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n return sort === 'label-asc' ? compare : -compare\n })\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) throw new CrudHttpError(401, { error: 'Unauthorized' })\n\n const url = new URL(req.url)\n const query = querySchema.parse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sort: url.searchParams.get('sort') ?? undefined,\n })\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = (container.resolve('em') as EntityManager).fork()\n const decryptionScope = {\n tenantId: auth.tenantId,\n organizationId: scope?.selectedId ?? auth.orgId ?? null,\n }\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id, tenantId: auth.tenantId, deletedAt: null },\n {},\n decryptionScope,\n )\n if (!deal) {\n throw notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n\n const entityScope = { tenantId: deal.tenantId, organizationId: deal.organizationId }\n const links = await findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { populate: ['company'] },\n entityScope,\n )\n\n const companyIds = links\n .map((link) => link.company?.id)\n .filter((companyId): companyId is string => typeof companyId === 'string' && companyId.length > 0)\n const profiles = companyIds.length > 0\n ? await findWithDecryption(\n em,\n CustomerCompanyProfile,\n {\n entity: { $in: companyIds },\n tenantId: deal.tenantId,\n organizationId: deal.organizationId,\n },\n {},\n entityScope,\n )\n : []\n const profileByCompanyId = new Map(\n profiles.map((profile) => [(profile.entity as { id: string }).id, profile]),\n )\n\n const items = links\n .map((link) => {\n const company = link.company as CustomerEntity | null\n if (!company?.id) return null\n const profile = profileByCompanyId.get(company.id) ?? null\n return {\n id: company.id,\n label: company.displayName ?? profile?.domain ?? company.id,\n subtitle: profile?.domain ?? company.primaryEmail ?? company.primaryPhone ?? null,\n kind: 'company',\n linkedAt: link.createdAt.toISOString(),\n } satisfies DealCompanyItem\n })\n .filter((item): item is DealCompanyItem => item !== null)\n\n const filtered = query.search?.trim().length ? items.filter((item) => matchesSearch(item, query.search ?? '')) : items\n const sorted = sortItems(filtered, normalizeSort(query.sort))\n const total = sorted.length\n const totalPages = Math.max(1, Math.ceil(total / query.pageSize))\n const page = Math.min(query.page, totalPages)\n const start = (page - 1) * query.pageSize\n\n return NextResponse.json({\n items: sorted.slice(start, start + query.pageSize),\n total,\n page,\n pageSize: query.pageSize,\n totalPages,\n })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n logger.error('customers.deals.companies.GET', { err: error })\n return NextResponse.json({ error: translate('customers.errors.deal_companies_load_failed', 'Failed to load linked companies') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n methods: {\n GET: {\n summary: 'List linked companies for a deal',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Paginated linked companies',\n schema: z.object({\n items: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable(),\n kind: z.literal('company'),\n linkedAt: z.string(),\n }),\n ),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
|
+
import { CrudHttpError, isCrudHttpError, notFound } from "@open-mercato/shared/lib/crud/errors";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
6
6
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
@@ -73,7 +73,7 @@ async function GET(req, ctx) {
|
|
|
73
73
|
decryptionScope
|
|
74
74
|
);
|
|
75
75
|
if (!deal) {
|
|
76
|
-
throw
|
|
76
|
+
throw notFound(translate("customers.errors.deal_not_found", "Deal not found"));
|
|
77
77
|
}
|
|
78
78
|
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {
|
|
79
79
|
throw new CrudHttpError(403, { error: translate("customers.errors.access_denied", "Access denied") });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customers/api/deals/%5Bid%5D/people/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { CustomerDeal, CustomerDealPersonLink, CustomerEntity } from '../../../../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst querySchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(20),\n search: z.string().optional(),\n sort: z.enum(['label-asc', 'label-desc', 'name-asc', 'name-desc', 'recent']).default('label-asc'),\n})\n\ntype DealLinkedEntitySort = 'label-asc' | 'label-desc' | 'recent'\n\nfunction normalizeSort(sort: z.infer<typeof querySchema>['sort']): DealLinkedEntitySort {\n if (sort === 'name-asc') return 'label-asc'\n if (sort === 'name-desc') return 'label-desc'\n return sort\n}\n\ntype DealPersonItem = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person'\n linkedAt: string\n}\n\nfunction matchesSearch(item: DealPersonItem, query: string): boolean {\n const normalized = query.trim().toLowerCase()\n if (!normalized.length) return true\n return [item.label, item.subtitle]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .some((value) => value.toLowerCase().includes(normalized))\n}\n\nfunction sortItems(items: DealPersonItem[], sort: 'label-asc' | 'label-desc' | 'recent'): DealPersonItem[] {\n if (sort === 'recent') {\n return [...items].sort((left, right) => {\n const compare = new Date(right.linkedAt).getTime() - new Date(left.linkedAt).getTime()\n if (compare !== 0) return compare\n return left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n })\n }\n return [...items].sort((left, right) => {\n const compare = left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n return sort === 'label-asc' ? compare : -compare\n })\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) throw new CrudHttpError(401, { error: 'Unauthorized' })\n\n const url = new URL(req.url)\n const query = querySchema.parse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sort: url.searchParams.get('sort') ?? undefined,\n })\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = (container.resolve('em') as EntityManager).fork()\n const decryptionScope = {\n tenantId: auth.tenantId,\n organizationId: scope?.selectedId ?? auth.orgId ?? null,\n }\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id, tenantId: auth.tenantId, deletedAt: null },\n {},\n decryptionScope,\n )\n if (!deal) {\n throw
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { CustomerDeal, CustomerDealPersonLink, CustomerEntity } from '../../../../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst querySchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(20),\n search: z.string().optional(),\n sort: z.enum(['label-asc', 'label-desc', 'name-asc', 'name-desc', 'recent']).default('label-asc'),\n})\n\ntype DealLinkedEntitySort = 'label-asc' | 'label-desc' | 'recent'\n\nfunction normalizeSort(sort: z.infer<typeof querySchema>['sort']): DealLinkedEntitySort {\n if (sort === 'name-asc') return 'label-asc'\n if (sort === 'name-desc') return 'label-desc'\n return sort\n}\n\ntype DealPersonItem = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person'\n linkedAt: string\n}\n\nfunction matchesSearch(item: DealPersonItem, query: string): boolean {\n const normalized = query.trim().toLowerCase()\n if (!normalized.length) return true\n return [item.label, item.subtitle]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .some((value) => value.toLowerCase().includes(normalized))\n}\n\nfunction sortItems(items: DealPersonItem[], sort: 'label-asc' | 'label-desc' | 'recent'): DealPersonItem[] {\n if (sort === 'recent') {\n return [...items].sort((left, right) => {\n const compare = new Date(right.linkedAt).getTime() - new Date(left.linkedAt).getTime()\n if (compare !== 0) return compare\n return left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n })\n }\n return [...items].sort((left, right) => {\n const compare = left.label.localeCompare(right.label, undefined, { sensitivity: 'base' })\n return sort === 'label-asc' ? compare : -compare\n })\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { id?: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) throw new CrudHttpError(401, { error: 'Unauthorized' })\n\n const url = new URL(req.url)\n const query = querySchema.parse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sort: url.searchParams.get('sort') ?? undefined,\n })\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = (container.resolve('em') as EntityManager).fork()\n const decryptionScope = {\n tenantId: auth.tenantId,\n organizationId: scope?.selectedId ?? auth.orgId ?? null,\n }\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id, tenantId: auth.tenantId, deletedAt: null },\n {},\n decryptionScope,\n )\n if (!deal) {\n throw notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n\n const entityScope = { tenantId: deal.tenantId, organizationId: deal.organizationId }\n const links = await findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { populate: ['person'] },\n entityScope,\n )\n\n const items = links\n .map((link) => {\n const person = link.person as CustomerEntity | null\n if (!person?.id) return null\n return {\n id: person.id,\n label: person.displayName ?? person.primaryEmail ?? person.id,\n subtitle: person.primaryEmail ?? person.primaryPhone ?? null,\n kind: 'person',\n linkedAt: link.createdAt.toISOString(),\n } satisfies DealPersonItem\n })\n .filter((item): item is DealPersonItem => item !== null)\n\n const filtered = query.search?.trim().length ? items.filter((item) => matchesSearch(item, query.search ?? '')) : items\n const sorted = sortItems(filtered, normalizeSort(query.sort))\n const total = sorted.length\n const totalPages = Math.max(1, Math.ceil(total / query.pageSize))\n const page = Math.min(query.page, totalPages)\n const start = (page - 1) * query.pageSize\n\n return NextResponse.json({\n items: sorted.slice(start, start + query.pageSize),\n total,\n page,\n pageSize: query.pageSize,\n totalPages,\n })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n logger.error('customers.deals.people.GET', { err: error })\n return NextResponse.json({ error: translate('customers.errors.deal_people_load_failed', 'Failed to load linked people') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n methods: {\n GET: {\n summary: 'List linked people for a deal',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Paginated linked people',\n schema: z.object({\n items: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable(),\n kind: z.literal('person'),\n linkedAt: z.string(),\n }),\n ),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
4
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { CrudHttpError, isCrudHttpError, notFound } from "@open-mercato/shared/lib/crud/errors";
|
|
5
5
|
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
|
|
6
6
|
import { CustomerDictionaryEntry } from "../../../../data/entities.js";
|
|
7
7
|
import { mapDictionaryKind, resolveDictionaryActorId, resolveDictionaryRouteContext } from "../../context.js";
|
|
@@ -75,7 +75,7 @@ async function PATCH(req, ctx) {
|
|
|
75
75
|
} catch (err) {
|
|
76
76
|
if (isCrudHttpError(err)) {
|
|
77
77
|
if (err.status === 404) {
|
|
78
|
-
throw
|
|
78
|
+
throw notFound(routeContext.translate("customers.errors.lookup_failed", "Dictionary entry not found"));
|
|
79
79
|
}
|
|
80
80
|
if (err.status === 409) {
|
|
81
81
|
if (err.body?.code === "role_type_in_use") {
|
|
@@ -111,7 +111,7 @@ async function PATCH(req, ctx) {
|
|
|
111
111
|
}
|
|
112
112
|
);
|
|
113
113
|
if (!entry) {
|
|
114
|
-
throw
|
|
114
|
+
throw notFound(routeContext.translate("customers.errors.lookup_failed", "Dictionary entry not found"));
|
|
115
115
|
}
|
|
116
116
|
if (result.changed) {
|
|
117
117
|
await invalidateDictionaryCache(routeContext.cache, {
|
|
@@ -203,7 +203,7 @@ async function DELETE(req, ctx) {
|
|
|
203
203
|
});
|
|
204
204
|
} catch (err) {
|
|
205
205
|
if (isCrudHttpError(err) && err.status === 404) {
|
|
206
|
-
throw
|
|
206
|
+
throw notFound(routeContext.translate("customers.errors.lookup_failed", "Dictionary entry not found"));
|
|
207
207
|
}
|
|
208
208
|
if (isCrudHttpError(err) && err.status === 409 && err.body?.code === "role_type_in_use") {
|
|
209
209
|
const usageCount = Number(err.body?.usageCount ?? 0);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customers/api/dictionaries/%5Bkind%5D/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandExecuteResult } from '@open-mercato/shared/lib/commands/types'\nimport { CustomerDictionaryEntry } from '../../../../data/entities'\nimport { mapDictionaryKind, resolveDictionaryActorId, resolveDictionaryRouteContext } from '../../context'\nimport { invalidateDictionaryCache } from '../../cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst patchSchema = z\n .object({\n value: z.string().trim().min(1).max(150).optional(),\n label: z.string().trim().max(150).optional(),\n color: z.union([z.string().trim(), z.null()]).optional(),\n icon: z.union([z.string().trim(), z.null()]).optional(),\n })\n .refine((input) => input.value !== undefined || input.label !== undefined || input.color !== undefined || input.icon !== undefined, {\n message: 'No changes provided',\n })\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\nfunction formatCountMessage(template: string, count: number) {\n return template.replace('{{count}}', String(count))\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { kind?: string; id?: string } }) {\n try {\n const routeContext = await resolveDictionaryRouteContext(req)\n if (!routeContext.organizationId) {\n throw new CrudHttpError(400, { error: routeContext.translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const payload = patchSchema.parse(await readJsonSafe(req, {}))\n const guardUserId = resolveDictionaryActorId(routeContext.auth)\n const guardResult = await validateCrudMutationGuard(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n const commandBus = (routeContext.container.resolve('commandBus') as CommandBus)\n let commandResult: CommandExecuteResult<{ entryId: string; changed: boolean }>\n try {\n commandResult = (await commandBus.execute('customers.dictionaryEntries.update', {\n input: {\n id,\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n kind: mappedKind,\n value: payload.value,\n label: payload.label,\n color: payload.color,\n icon: payload.icon,\n },\n ctx: routeContext.ctx,\n })) as CommandExecuteResult<{ entryId: string; changed: boolean }>\n } catch (err) {\n if (isCrudHttpError(err)) {\n if (err.status === 404) {\n throw new CrudHttpError(404, { error: routeContext.translate('customers.errors.lookup_failed', 'Dictionary entry not found') })\n }\n if (err.status === 409) {\n if ((err.body as Record<string, unknown> | undefined)?.code === 'role_type_in_use') {\n const usageCount = Number((err.body as Record<string, unknown> | undefined)?.usageCount ?? 0)\n throw new CrudHttpError(409, {\n ...(err.body as Record<string, unknown> | undefined),\n error: formatCountMessage(\n routeContext.translate(\n 'customers.config.dictionaries.errors.roleTypeValueInUse',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before changing its value.',\n ),\n usageCount,\n ),\n })\n }\n throw new CrudHttpError(409, { error: routeContext.translate('customers.config.dictionaries.errors.duplicate', 'An entry with this value already exists') })\n }\n if (err.status === 400) {\n throw new CrudHttpError(400, { error: routeContext.translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') })\n }\n }\n throw err\n }\n const { result, logEntry } = commandResult\n\n const entry = await findOneWithDecryption(\n routeContext.em.fork(),\n CustomerDictionaryEntry,\n { id },\n {},\n {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n },\n )\n if (!entry) {\n throw new CrudHttpError(404, { error: routeContext.translate('customers.errors.lookup_failed', 'Dictionary entry not found') })\n }\n\n if (result.changed) {\n await invalidateDictionaryCache(routeContext.cache, {\n tenantId: routeContext.tenantId,\n mappedKind,\n organizationIds: [routeContext.organizationId],\n })\n }\n\n const response = NextResponse.json({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n organizationId: entry.organizationId,\n isInherited: false,\n })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.dictionary_entry',\n resourceId: entry.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: entry.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('customers.dictionaries.update failed', { err })\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') }, { status: 400 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { kind?: string; id?: string } }) {\n try {\n const routeContext = await resolveDictionaryRouteContext(req)\n if (!routeContext.organizationId) {\n throw new CrudHttpError(400, { error: routeContext.translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const guardUserId = resolveDictionaryActorId(routeContext.auth)\n const guardResult = await validateCrudMutationGuard(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n const commandBus = (routeContext.container.resolve('commandBus') as CommandBus)\n let deleteResult: CommandExecuteResult<{ entryId: string }>\n try {\n deleteResult = (await commandBus.execute('customers.dictionaryEntries.delete', {\n input: {\n id,\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n kind: mappedKind,\n },\n ctx: routeContext.ctx,\n })) as CommandExecuteResult<{ entryId: string }>\n } catch (err) {\n if (isCrudHttpError(err) && err.status === 404) {\n throw new CrudHttpError(404, { error: routeContext.translate('customers.errors.lookup_failed', 'Dictionary entry not found') })\n }\n if (isCrudHttpError(err) && err.status === 409 && (err.body as Record<string, unknown> | undefined)?.code === 'role_type_in_use') {\n const usageCount = Number((err.body as Record<string, unknown> | undefined)?.usageCount ?? 0)\n throw new CrudHttpError(409, {\n ...(err.body as Record<string, unknown> | undefined),\n error: formatCountMessage(\n routeContext.translate(\n 'customers.config.dictionaries.errors.roleTypeDeleteInUse',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n ),\n usageCount,\n ),\n })\n }\n throw err\n }\n const { logEntry } = deleteResult\n\n await invalidateDictionaryCache(routeContext.cache, {\n tenantId: routeContext.tenantId,\n mappedKind,\n organizationIds: [routeContext.organizationId],\n })\n\n const response = NextResponse.json({ success: true })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.dictionary_entry',\n resourceId: id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('customers.dictionaries.delete failed', { err })\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to delete dictionary entry') }, { status: 400 })\n }\n}\n\nconst dictionaryEntrySchema = z.object({\n id: z.string().uuid(),\n value: z.string(),\n label: z.string().nullable().optional(),\n color: z.string().nullable().optional(),\n icon: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n isInherited: z.boolean().optional(),\n})\n\nconst dictionaryDeleteResponseSchema = z.object({\n success: z.literal(true),\n})\n\nconst dictionaryErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer dictionary entry',\n methods: {\n PATCH: {\n summary: 'Update dictionary entry',\n description: 'Updates value, label, color, or icon for an existing customer dictionary entry.',\n requestBody: {\n contentType: 'application/json',\n schema: patchSchema,\n },\n responses: [\n { status: 200, description: 'Updated dictionary entry', schema: dictionaryEntrySchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: dictionaryErrorSchema },\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 404, description: 'Entry not found', schema: dictionaryErrorSchema },\n { status: 409, description: 'Duplicate value conflict', schema: dictionaryErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete dictionary entry',\n description: 'Removes a customer dictionary entry by identifier.',\n responses: [\n { status: 200, description: 'Entry deleted', schema: dictionaryDeleteResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 404, description: 'Entry not found', schema: dictionaryErrorSchema },\n { status: 409, description: 'Entry is in use and cannot be deleted', schema: dictionaryErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,2BAA2B;AACpC,SAAS,eAAe,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandExecuteResult } from '@open-mercato/shared/lib/commands/types'\nimport { CustomerDictionaryEntry } from '../../../../data/entities'\nimport { mapDictionaryKind, resolveDictionaryActorId, resolveDictionaryRouteContext } from '../../context'\nimport { invalidateDictionaryCache } from '../../cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst patchSchema = z\n .object({\n value: z.string().trim().min(1).max(150).optional(),\n label: z.string().trim().max(150).optional(),\n color: z.union([z.string().trim(), z.null()]).optional(),\n icon: z.union([z.string().trim(), z.null()]).optional(),\n })\n .refine((input) => input.value !== undefined || input.label !== undefined || input.color !== undefined || input.icon !== undefined, {\n message: 'No changes provided',\n })\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\nfunction formatCountMessage(template: string, count: number) {\n return template.replace('{{count}}', String(count))\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { kind?: string; id?: string } }) {\n try {\n const routeContext = await resolveDictionaryRouteContext(req)\n if (!routeContext.organizationId) {\n throw new CrudHttpError(400, { error: routeContext.translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const payload = patchSchema.parse(await readJsonSafe(req, {}))\n const guardUserId = resolveDictionaryActorId(routeContext.auth)\n const guardResult = await validateCrudMutationGuard(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n const commandBus = (routeContext.container.resolve('commandBus') as CommandBus)\n let commandResult: CommandExecuteResult<{ entryId: string; changed: boolean }>\n try {\n commandResult = (await commandBus.execute('customers.dictionaryEntries.update', {\n input: {\n id,\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n kind: mappedKind,\n value: payload.value,\n label: payload.label,\n color: payload.color,\n icon: payload.icon,\n },\n ctx: routeContext.ctx,\n })) as CommandExecuteResult<{ entryId: string; changed: boolean }>\n } catch (err) {\n if (isCrudHttpError(err)) {\n if (err.status === 404) {\n throw notFound(routeContext.translate('customers.errors.lookup_failed', 'Dictionary entry not found'))\n }\n if (err.status === 409) {\n if ((err.body as Record<string, unknown> | undefined)?.code === 'role_type_in_use') {\n const usageCount = Number((err.body as Record<string, unknown> | undefined)?.usageCount ?? 0)\n throw new CrudHttpError(409, {\n ...(err.body as Record<string, unknown> | undefined),\n error: formatCountMessage(\n routeContext.translate(\n 'customers.config.dictionaries.errors.roleTypeValueInUse',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before changing its value.',\n ),\n usageCount,\n ),\n })\n }\n throw new CrudHttpError(409, { error: routeContext.translate('customers.config.dictionaries.errors.duplicate', 'An entry with this value already exists') })\n }\n if (err.status === 400) {\n throw new CrudHttpError(400, { error: routeContext.translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') })\n }\n }\n throw err\n }\n const { result, logEntry } = commandResult\n\n const entry = await findOneWithDecryption(\n routeContext.em.fork(),\n CustomerDictionaryEntry,\n { id },\n {},\n {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n },\n )\n if (!entry) {\n throw notFound(routeContext.translate('customers.errors.lookup_failed', 'Dictionary entry not found'))\n }\n\n if (result.changed) {\n await invalidateDictionaryCache(routeContext.cache, {\n tenantId: routeContext.tenantId,\n mappedKind,\n organizationIds: [routeContext.organizationId],\n })\n }\n\n const response = NextResponse.json({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n organizationId: entry.organizationId,\n isInherited: false,\n })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.dictionary_entry',\n resourceId: entry.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: entry.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('customers.dictionaries.update failed', { err })\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to save dictionary entry') }, { status: 400 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { kind?: string; id?: string } }) {\n try {\n const routeContext = await resolveDictionaryRouteContext(req)\n if (!routeContext.organizationId) {\n throw new CrudHttpError(400, { error: routeContext.translate('customers.errors.organization_required', 'Organization context is required') })\n }\n const { mappedKind } = mapDictionaryKind(ctx.params?.kind)\n const { id } = paramsSchema.parse({ id: ctx.params?.id })\n const guardUserId = resolveDictionaryActorId(routeContext.auth)\n const guardResult = await validateCrudMutationGuard(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n const commandBus = (routeContext.container.resolve('commandBus') as CommandBus)\n let deleteResult: CommandExecuteResult<{ entryId: string }>\n try {\n deleteResult = (await commandBus.execute('customers.dictionaryEntries.delete', {\n input: {\n id,\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n kind: mappedKind,\n },\n ctx: routeContext.ctx,\n })) as CommandExecuteResult<{ entryId: string }>\n } catch (err) {\n if (isCrudHttpError(err) && err.status === 404) {\n throw notFound(routeContext.translate('customers.errors.lookup_failed', 'Dictionary entry not found'))\n }\n if (isCrudHttpError(err) && err.status === 409 && (err.body as Record<string, unknown> | undefined)?.code === 'role_type_in_use') {\n const usageCount = Number((err.body as Record<string, unknown> | undefined)?.usageCount ?? 0)\n throw new CrudHttpError(409, {\n ...(err.body as Record<string, unknown> | undefined),\n error: formatCountMessage(\n routeContext.translate(\n 'customers.config.dictionaries.errors.roleTypeDeleteInUse',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n ),\n usageCount,\n ),\n })\n }\n throw err\n }\n const { logEntry } = deleteResult\n\n await invalidateDictionaryCache(routeContext.cache, {\n tenantId: routeContext.tenantId,\n mappedKind,\n organizationIds: [routeContext.organizationId],\n })\n\n const response = NextResponse.json({ success: true })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'customers.dictionary_entry',\n resourceId: id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(routeContext.container, {\n tenantId: routeContext.tenantId,\n organizationId: routeContext.organizationId,\n userId: guardUserId,\n resourceKind: 'customers.dictionary_entry',\n resourceId: id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('customers.dictionaries.delete failed', { err })\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to delete dictionary entry') }, { status: 400 })\n }\n}\n\nconst dictionaryEntrySchema = z.object({\n id: z.string().uuid(),\n value: z.string(),\n label: z.string().nullable().optional(),\n color: z.string().nullable().optional(),\n icon: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n isInherited: z.boolean().optional(),\n})\n\nconst dictionaryDeleteResponseSchema = z.object({\n success: z.literal(true),\n})\n\nconst dictionaryErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer dictionary entry',\n methods: {\n PATCH: {\n summary: 'Update dictionary entry',\n description: 'Updates value, label, color, or icon for an existing customer dictionary entry.',\n requestBody: {\n contentType: 'application/json',\n schema: patchSchema,\n },\n responses: [\n { status: 200, description: 'Updated dictionary entry', schema: dictionaryEntrySchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: dictionaryErrorSchema },\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 404, description: 'Entry not found', schema: dictionaryErrorSchema },\n { status: 409, description: 'Duplicate value conflict', schema: dictionaryErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete dictionary entry',\n description: 'Removes a customer dictionary entry by identifier.',\n responses: [\n { status: 200, description: 'Entry deleted', schema: dictionaryDeleteResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: dictionaryErrorSchema },\n { status: 404, description: 'Entry not found', schema: dictionaryErrorSchema },\n { status: 409, description: 'Entry is in use and cannot be deleted', schema: dictionaryErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
4
4
|
import { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
5
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
|
+
import { CrudHttpError, isCrudHttpError, notFound } from "@open-mercato/shared/lib/crud/errors";
|
|
6
6
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
7
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
8
8
|
import { isOrganizationReadAccessAllowed } from "@open-mercato/core/modules/directory/utils/organizationScopeGuard";
|
|
@@ -94,7 +94,7 @@ async function resolveEntityRouteScope(em, auth, scope, entityType, entityId, tr
|
|
|
94
94
|
{ tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null }
|
|
95
95
|
);
|
|
96
96
|
if (!entity || entity.tenantId !== auth.tenantId) {
|
|
97
|
-
throw
|
|
97
|
+
throw notFound(translate("customers.errors.customer_not_found", "Customer not found"));
|
|
98
98
|
}
|
|
99
99
|
ensureRouteOrganizationAccess(entity.organizationId, scope, auth, translate);
|
|
100
100
|
return {
|
|
@@ -112,7 +112,7 @@ async function resolveRoleRouteScope(em, auth, scope, entityType, entityId, role
|
|
|
112
112
|
{ tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null }
|
|
113
113
|
);
|
|
114
114
|
if (!role || role.tenantId !== auth.tenantId || role.entityType !== entityType || role.entityId !== entityId) {
|
|
115
|
-
throw
|
|
115
|
+
throw notFound(translate("customers.errors.role_not_found", "Role not found"));
|
|
116
116
|
}
|
|
117
117
|
ensureRouteOrganizationAccess(role.organizationId, scope, auth, translate);
|
|
118
118
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/api/entity-roles-factory.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerEntity, CustomerEntityRole } from '../data/entities'\nimport { entityRoleCreateSchema, entityRoleUpdateSchema, entityRoleDeleteSchema, type EntityRoleCreateInput, type EntityRoleUpdateInput, type EntityRoleDeleteInput } from '../data/validators'\nimport { withScopedPayload } from './utils'\nimport { resolveCustomersRequestContext, resolveAuthActorId } from '../lib/interactionRequestContext'\nimport { deriveDisplayNameFromEmail } from '../lib/displayName'\nimport { withOperationMetadata } from '../lib/operationMetadata'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\nconst roleIdQuerySchema = z.object({ roleId: z.string().uuid() })\n\nconst createBodySchema = z.object({\n roleType: z.string().trim().min(1).max(100),\n userId: z.string().uuid(),\n})\nconst updateBodySchema = z.object({\n userId: z.string().uuid(),\n})\n\nconst listItemSchema = z.object({\n id: z.string().uuid(),\n entityType: z.enum(['company', 'person']),\n entityId: z.string().uuid(),\n userId: z.string().uuid(),\n userName: z.string().nullable().optional(),\n userEmail: z.string().nullable().optional(),\n userPhone: z.string().nullable().optional(),\n roleType: z.string(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst listResponseSchema = z.object({ items: z.array(listItemSchema) })\nconst okResponseSchema = z.object({ ok: z.boolean() })\nconst createResponseSchema = z.object({ id: z.string().uuid() })\nconst errorSchema = z.object({ error: z.string() })\n\ntype EntityType = 'company' | 'person'\n\ntype Translator = Awaited<ReturnType<typeof resolveTranslations>>['translate']\n\nfunction getRoleContext(entityType: EntityType, entityId: string) {\n const resourceKind = entityType === 'company' ? 'customers.company' : 'customers.person'\n return { entityType, entityId, resourceKind, resourceId: entityId }\n}\n\nfunction buildValidationErrorResponse(error: z.ZodError, translate: Translator) {\n return NextResponse.json(\n { error: translate('customers.errors.validationFailed', 'Validation failed'), fieldErrors: error.flatten().fieldErrors },\n { status: 400 },\n )\n}\n\nasync function buildContext(request: Request) {\n const context = await resolveCustomersRequestContext(request)\n return {\n ...context,\n ctx: context.commandContext,\n }\n}\n\nfunction ensureRouteOrganizationAccess(\n organizationId: string,\n scope: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['scope'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n translate: Translator,\n) {\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n}\n\nasync function ensureFeatureOnOrganization(\n container: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['container'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n feature: string,\n organizationId: string,\n translate: Translator,\n) {\n const actorId = resolveAuthActorId(auth)\n if (!actorId) {\n throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n }\n let rbac: RbacService | undefined\n try {\n rbac = container.resolve('rbacService') as RbacService | undefined\n } catch (err) {\n logger.error('rbacService resolve failed', { component: 'entity-roles-factory', err })\n rbac = undefined\n }\n if (!rbac) {\n throw new CrudHttpError(500, { error: translate('customers.errors.internal', 'Internal error') })\n }\n const hasFeature = await rbac.userHasAllFeatures(actorId, [feature], {\n tenantId: auth.tenantId,\n organizationId,\n })\n if (!hasFeature) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n}\n\nasync function resolveEntityRouteScope(\n em: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['em'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n scope: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['scope'],\n entityType: EntityType,\n entityId: string,\n translate: Translator,\n) {\n const entity = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: entityId, kind: entityType, tenantId: auth.tenantId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null },\n )\n if (!entity || entity.tenantId !== auth.tenantId) {\n throw new CrudHttpError(404, { error: translate('customers.errors.customer_not_found', 'Customer not found') })\n }\n ensureRouteOrganizationAccess(entity.organizationId, scope, auth, translate)\n return {\n entity,\n organizationId: entity.organizationId,\n tenantId: entity.tenantId,\n }\n}\n\nasync function resolveRoleRouteScope(\n em: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['em'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n scope: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['scope'],\n entityType: EntityType,\n entityId: string,\n roleId: string,\n translate: Translator,\n) {\n const role = await findOneWithDecryption(\n em,\n CustomerEntityRole,\n { id: roleId, tenantId: auth.tenantId, entityType, entityId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null },\n )\n if (\n !role ||\n role.tenantId !== auth.tenantId ||\n role.entityType !== entityType ||\n role.entityId !== entityId\n ) {\n throw new CrudHttpError(404, { error: translate('customers.errors.role_not_found', 'Role not found') })\n }\n ensureRouteOrganizationAccess(role.organizationId, scope, auth, translate)\n return {\n role,\n organizationId: role.organizationId,\n tenantId: role.tenantId,\n }\n}\n\nfunction createScopedCommandContext(\n ctx: Awaited<ReturnType<typeof buildContext>>['ctx'],\n organizationId: string,\n) {\n return {\n ...ctx,\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n }\n}\n\nexport const entityRolesMetadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.roles.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.roles.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.roles.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.roles.manage'] },\n}\n\nexport function buildEntityRolesOpenApi(entityType: EntityType): OpenApiRouteDoc {\n const label = entityType === 'company' ? 'company' : 'person'\n return {\n tag: 'Customers',\n summary: `${label.charAt(0).toUpperCase() + label.slice(1)} role assignments`,\n pathParams: paramsSchema,\n methods: {\n GET: {\n summary: `List roles for a ${label}`,\n responses: [{ status: 200, description: 'Role assignments', schema: listResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: `Assign a role to a ${label}`,\n requestBody: { contentType: 'application/json', schema: createBodySchema },\n responses: [{ status: 201, description: 'Role created', schema: createResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 409, description: 'Role already assigned', schema: errorSchema },\n ],\n },\n PUT: {\n summary: `Update a ${label} role assignment`,\n query: roleIdQuerySchema,\n requestBody: { contentType: 'application/json', schema: updateBodySchema },\n responses: [{ status: 200, description: 'Role updated', schema: okResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: `Remove a ${label} role assignment`,\n query: roleIdQuerySchema,\n responses: [{ status: 200, description: 'Role deleted', schema: okResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n },\n },\n }\n}\n\nexport function createEntityRolesHandlers(entityType: EntityType) {\n const resourceKind = entityType === 'company' ? 'customers.company' : 'customers.person'\n const logPrefix = entityType === 'company' ? 'customers.company.roles' : 'customers.person.roles'\n\n async function GET(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { container, em, auth, scope } = await buildContext(request)\n const targetScope = await resolveEntityRouteScope(em, auth, scope, entityType, entityId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.view', targetScope.organizationId, translate)\n\n const roles = await findWithDecryption(\n em,\n CustomerEntityRole,\n {\n entityType,\n entityId,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n deletedAt: null,\n },\n { orderBy: { roleType: 'asc' } },\n {\n tenantId: targetScope.tenantId,\n organizationId: targetScope.organizationId,\n },\n )\n\n const userIds = Array.from(new Set(roles.map((role) => role.userId).filter((value): value is string => typeof value === 'string' && value.length > 0)))\n const users = userIds.length\n ? await findWithDecryption(\n em,\n User,\n {\n id: { $in: userIds },\n deletedAt: null,\n ...(targetScope.tenantId ? { tenantId: targetScope.tenantId } : {}),\n },\n undefined,\n {\n tenantId: targetScope.tenantId,\n organizationId: targetScope.organizationId,\n },\n )\n : []\n const userMap = new Map(users.map((user) => [user.id, {\n name: user.name ?? deriveDisplayNameFromEmail(user.email) ?? null,\n email: user.email ?? null,\n phone: null,\n }]))\n\n return NextResponse.json({\n items: roles.map((role) => ({\n ...(userMap.has(role.userId)\n ? {\n userName: userMap.get(role.userId)?.name ?? null,\n userEmail: userMap.get(role.userId)?.email ?? null,\n userPhone: userMap.get(role.userId)?.phone ?? null,\n }\n : {}),\n id: role.id,\n entityType: role.entityType,\n entityId: role.entityId,\n userId: role.userId,\n roleType: role.roleType,\n createdAt: role.createdAt.toISOString(),\n updatedAt: role.updatedAt.toISOString(),\n })),\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles GET failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_load_roles', 'Failed to load roles') }, { status: 500 })\n }\n }\n\n async function POST(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { container, em, auth, scope, ctx } = await buildContext(request)\n const targetScope = await resolveEntityRouteScope(em, auth, scope, entityType, entityId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.manage', targetScope.organizationId, translate)\n const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId)\n\n const rawBody = await readJsonSafe<Record<string, unknown>>(request, {})\n const scoped = withScopedPayload(\n {\n ...rawBody,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n ...getRoleContext(entityType, entityId),\n },\n commandCtx,\n translate,\n )\n const parsed = entityRoleCreateSchema.parse(scoped)\n\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, mutationPayload: rawBody,\n })\n if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status })\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<EntityRoleCreateInput, { roleId: string }>(\n 'customers.entityRoles.create',\n { input: parsed, ctx: commandCtx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, metadata: guardResult.metadata ?? null,\n })\n }\n\n return withOperationMetadata(\n NextResponse.json({ id: result?.roleId ?? null }, { status: 201 }),\n logEntry,\n { resourceKind, resourceId: entityId },\n )\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles POST failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_assign_role', 'Failed to assign role') }, { status: 500 })\n }\n }\n\n async function PUT(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { roleId } = roleIdQuerySchema.parse(Object.fromEntries(new URL(request.url).searchParams))\n const { container, em, auth, scope, ctx } = await buildContext(request)\n const targetScope = await resolveRoleRouteScope(em, auth, scope, entityType, entityId, roleId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.manage', targetScope.organizationId, translate)\n const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId)\n\n const rawBody = await readJsonSafe<Record<string, unknown>>(request, {})\n const scoped = withScopedPayload(\n {\n ...rawBody,\n id: roleId,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n },\n commandCtx,\n translate,\n )\n const parsed = entityRoleUpdateSchema.parse(scoped)\n\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, mutationPayload: { roleId, ...rawBody },\n })\n if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status })\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<EntityRoleUpdateInput, { roleId: string }>(\n 'customers.entityRoles.update',\n { input: parsed, ctx: commandCtx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, metadata: guardResult.metadata ?? null,\n })\n }\n\n return withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind, resourceId: entityId },\n )\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles PUT failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_update_role', 'Failed to update role') }, { status: 500 })\n }\n }\n\n async function DELETE(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { roleId } = roleIdQuerySchema.parse(Object.fromEntries(new URL(request.url).searchParams))\n const { container, em, auth, scope, ctx } = await buildContext(request)\n const targetScope = await resolveRoleRouteScope(em, auth, scope, entityType, entityId, roleId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.manage', targetScope.organizationId, translate)\n const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId)\n\n const parsed = entityRoleDeleteSchema.parse(\n withScopedPayload(\n {\n id: roleId,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n },\n commandCtx,\n translate,\n ),\n )\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, mutationPayload: { roleId },\n })\n if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status })\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<EntityRoleDeleteInput, { roleId: string }>(\n 'customers.entityRoles.delete',\n { input: parsed, ctx: commandCtx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, metadata: guardResult.metadata ?? null,\n })\n }\n\n return withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind, resourceId: entityId },\n )\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles DELETE failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_delete_role', 'Failed to delete role') }, { status: 500 })\n }\n }\n\n return { GET, POST, PUT, DELETE }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAGlB,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B,wCAAwC;AAC5E,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,uCAAuC;AAChD,SAAS,YAAY;AAErB,SAAS,gBAAgB,0BAA0B;AACnD,SAAS,wBAAwB,wBAAwB,8BAAkH;AAC3K,SAAS,yBAAyB;AAClC,SAAS,gCAAgC,0BAA0B;AACnE,SAAS,kCAAkC;AAC3C,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AAEvC,MAAM,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACvD,MAAM,oBAAoB,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAEhE,MAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC1C,QAAQ,EAAE,OAAO,EAAE,KAAK;AAC1B,CAAC;AACD,MAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,KAAK;AAC1B,CAAC;AAED,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,YAAY,EAAE,KAAK,CAAC,WAAW,QAAQ,CAAC;AAAA,EACxC,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACzC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC1C,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC1C,UAAU,EAAE,OAAO;AAAA,EACnB,WAAW,EAAE,OAAO;AAAA,EACpB,WAAW,EAAE,OAAO;AACtB,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,EAAE,CAAC;AACtE,MAAM,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACrD,MAAM,uBAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC/D,MAAM,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAMlD,SAAS,eAAe,YAAwB,UAAkB;AAChE,QAAM,eAAe,eAAe,YAAY,sBAAsB;AACtE,SAAO,EAAE,YAAY,UAAU,cAAc,YAAY,SAAS;AACpE;AAEA,SAAS,6BAA6B,OAAmB,WAAuB;AAC9E,SAAO,aAAa;AAAA,IAClB,EAAE,OAAO,UAAU,qCAAqC,mBAAmB,GAAG,aAAa,MAAM,QAAQ,EAAE,YAAY;AAAA,IACvH,EAAE,QAAQ,IAAI;AAAA,EAChB;AACF;AAEA,eAAe,aAAa,SAAkB;AAC5C,QAAM,UAAU,MAAM,+BAA+B,OAAO;AAC5D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,KAAK,QAAQ;AAAA,EACf;AACF;AAEA,SAAS,8BACP,gBACA,OACA,MACA,WACA;AACA,MAAI,CAAC,gCAAgC,EAAE,OAAO,MAAM,eAAe,CAAC,GAAG;AACrE,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,kCAAkC,eAAe,EAAE,CAAC;AAAA,EACtG;AACF;AAEA,eAAe,4BACb,WACA,MACA,SACA,gBACA,WACA;AACA,QAAM,UAAU,mBAAmB,IAAI;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,iCAAiC,cAAc,EAAE,CAAC;AAAA,EACpG;AACA,MAAI;AACJ,MAAI;AACF,WAAO,UAAU,QAAQ,aAAa;AAAA,EACxC,SAAS,KAAK;AACZ,WAAO,MAAM,8BAA8B,EAAE,WAAW,wBAAwB,IAAI,CAAC;AACrF,WAAO;AAAA,EACT;AACA,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,gBAAgB,EAAE,CAAC;AAAA,EAClG;AACA,QAAM,aAAa,MAAM,KAAK,mBAAmB,SAAS,CAAC,OAAO,GAAG;AAAA,IACnE,UAAU,KAAK;AAAA,IACf;AAAA,EACF,CAAC;AACD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,kCAAkC,eAAe,EAAE,CAAC;AAAA,EACtG;AACF;AAEA,eAAe,wBACb,IACA,MACA,OACA,YACA,UACA,WACA;AACA,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,UAAU,MAAM,YAAY,UAAU,KAAK,UAAU,WAAW,KAAK;AAAA,IAC3E;AAAA,IACA,EAAE,UAAU,KAAK,UAAU,gBAAgB,OAAO,cAAc,KAAK,SAAS,KAAK;AAAA,EACrF;AACA,MAAI,CAAC,UAAU,OAAO,aAAa,KAAK,UAAU;AAChD,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,uCAAuC,oBAAoB,EAAE,CAAC;AAAA,EAChH;AACA,gCAA8B,OAAO,gBAAgB,OAAO,MAAM,SAAS;AAC3E,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,OAAO;AAAA,IACvB,UAAU,OAAO;AAAA,EACnB;AACF;AAEA,eAAe,sBACb,IACA,MACA,OACA,YACA,UACA,QACA,WACA;AACA,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,QAAQ,UAAU,KAAK,UAAU,YAAY,UAAU,WAAW,KAAK;AAAA,IAC7E;AAAA,IACA,EAAE,UAAU,KAAK,UAAU,gBAAgB,OAAO,cAAc,KAAK,SAAS,KAAK;AAAA,EACrF;AACA,MACE,CAAC,QACD,KAAK,aAAa,KAAK,YACvB,KAAK,eAAe,cACpB,KAAK,aAAa,UAClB;AACA,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,mCAAmC,gBAAgB,EAAE,CAAC;AAAA,EACxG;AACA,gCAA8B,KAAK,gBAAgB,OAAO,MAAM,SAAS;AACzE,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,KAAK;AAAA,IACrB,UAAU,KAAK;AAAA,EACjB;AACF;AAEA,SAAS,2BACP,KACA,gBACA;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,wBAAwB;AAAA,IACxB,iBAAiB,CAAC,cAAc;AAAA,EAClC;AACF;AAEO,MAAM,sBAAsB;AAAA,EACjC,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AAAA,EACpE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,wBAAwB,EAAE;AAAA,EACvE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,wBAAwB,EAAE;AAAA,EACtE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,wBAAwB,EAAE;AAC3E;AAEO,SAAS,wBAAwB,YAAyC;AAC/E,QAAM,QAAQ,eAAe,YAAY,YAAY;AACrD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,SAAS,GAAG,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC,CAAC;AAAA,IAC1D,YAAY;AAAA,IACZ,SAAS;AAAA,MACP,KAAK;AAAA,QACH,SAAS,oBAAoB,KAAK;AAAA,QAClC,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,oBAAoB,QAAQ,mBAAmB,CAAC;AAAA,QACxF,QAAQ;AAAA,UACN,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,YAAY;AAAA,UACnE,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAClE;AAAA,MACF;AAAA,MACA,MAAM;AAAA,QACJ,SAAS,sBAAsB,KAAK;AAAA,QACpC,aAAa,EAAE,aAAa,oBAAoB,QAAQ,iBAAiB;AAAA,QACzE,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,qBAAqB,CAAC;AAAA,QACtF,QAAQ;AAAA,UACN,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,YAAY;AAAA,UACnE,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,UAChE,EAAE,QAAQ,KAAK,aAAa,yBAAyB,QAAQ,YAAY;AAAA,QAC3E;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,SAAS,YAAY,KAAK;AAAA,QAC1B,OAAO;AAAA,QACP,aAAa,EAAE,aAAa,oBAAoB,QAAQ,iBAAiB;AAAA,QACzE,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,iBAAiB,CAAC;AAAA,QAClF,QAAQ;AAAA,UACN,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,YAAY;AAAA,UACnE,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,UAChE,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,YAAY;AAAA,QACpE;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,SAAS,YAAY,KAAK;AAAA,QAC1B,OAAO;AAAA,QACP,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,iBAAiB,CAAC;AAAA,QAClF,QAAQ;AAAA,UACN,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,YAAY;AAAA,UACnE,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,UAChE,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,YAAY;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,0BAA0B,YAAwB;AAChE,QAAM,eAAe,eAAe,YAAY,sBAAsB;AACtE,QAAM,YAAY,eAAe,YAAY,4BAA4B;AAEzE,iBAAe,IAAI,SAAkB,EAAE,OAAO,GAA+B;AAC3E,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI;AACF,YAAM,EAAE,IAAI,SAAS,IAAI,aAAa,MAAM,MAAM;AAClD,YAAM,EAAE,WAAW,IAAI,MAAM,MAAM,IAAI,MAAM,aAAa,OAAO;AACjE,YAAM,cAAc,MAAM,wBAAwB,IAAI,MAAM,OAAO,YAAY,UAAU,SAAS;AAClG,YAAM,4BAA4B,WAAW,MAAM,wBAAwB,YAAY,gBAAgB,SAAS;AAEhH,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,gBAAgB,YAAY;AAAA,UAC5B,UAAU,YAAY;AAAA,UACtB,WAAW;AAAA,QACb;AAAA,QACA,EAAE,SAAS,EAAE,UAAU,MAAM,EAAE;AAAA,QAC/B;AAAA,UACE,UAAU,YAAY;AAAA,UACtB,gBAAgB,YAAY;AAAA,QAC9B;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,MAAM,EAAE,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,CAAC,CAAC;AACtJ,YAAM,QAAQ,QAAQ,SAClB,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,UACE,IAAI,EAAE,KAAK,QAAQ;AAAA,UACnB,WAAW;AAAA,UACX,GAAI,YAAY,WAAW,EAAE,UAAU,YAAY,SAAS,IAAI,CAAC;AAAA,QACnE;AAAA,QACA;AAAA,QACA;AAAA,UACE,UAAU,YAAY;AAAA,UACtB,gBAAgB,YAAY;AAAA,QAC9B;AAAA,MACF,IACA,CAAC;AACL,YAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI;AAAA,QACpD,MAAM,KAAK,QAAQ,2BAA2B,KAAK,KAAK,KAAK;AAAA,QAC7D,OAAO,KAAK,SAAS;AAAA,QACrB,OAAO;AAAA,MACT,CAAC,CAAC,CAAC;AAEH,aAAO,aAAa,KAAK;AAAA,QACvB,OAAO,MAAM,IAAI,CAAC,UAAU;AAAA,UAC1B,GAAI,QAAQ,IAAI,KAAK,MAAM,IACvB;AAAA,YACE,UAAU,QAAQ,IAAI,KAAK,MAAM,GAAG,QAAQ;AAAA,YAC5C,WAAW,QAAQ,IAAI,KAAK,MAAM,GAAG,SAAS;AAAA,YAC9C,WAAW,QAAQ,IAAI,KAAK,MAAM,GAAG,SAAS;AAAA,UAChD,IACA,CAAC;AAAA,UACL,IAAI,KAAK;AAAA,UACT,YAAY,KAAK;AAAA,UACjB,UAAU,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,UACb,UAAU,KAAK;AAAA,UACf,WAAW,KAAK,UAAU,YAAY;AAAA,UACtC,WAAW,KAAK,UAAU,YAAY;AAAA,QACxC,EAAE;AAAA,MACJ,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,UAAI,eAAe,EAAE,SAAU,QAAO,6BAA6B,KAAK,SAAS;AACjF,aAAO,MAAM,2BAA2B,EAAE,WAAW,WAAW,IAAI,CAAC;AACrE,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,yCAAyC,sBAAsB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACjI;AAAA,EACF;AAEA,iBAAe,KAAK,SAAkB,EAAE,OAAO,GAA+B;AAC5E,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI;AACF,YAAM,EAAE,IAAI,SAAS,IAAI,aAAa,MAAM,MAAM;AAClD,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,IAAI,IAAI,MAAM,aAAa,OAAO;AACtE,YAAM,cAAc,MAAM,wBAAwB,IAAI,MAAM,OAAO,YAAY,UAAU,SAAS;AAClG,YAAM,4BAA4B,WAAW,MAAM,0BAA0B,YAAY,gBAAgB,SAAS;AAClH,YAAM,aAAa,2BAA2B,KAAK,YAAY,cAAc;AAE7E,YAAM,UAAU,MAAM,aAAsC,SAAS,CAAC,CAAC;AACvE,YAAM,SAAS;AAAA,QACb;AAAA,UACE,GAAG;AAAA,UACH,gBAAgB,YAAY;AAAA,UAC5B,UAAU,YAAY;AAAA,UACtB,GAAG,eAAe,YAAY,QAAQ;AAAA,QACxC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,SAAS,uBAAuB,MAAM,MAAM;AAElD,YAAM,cAAc,mBAAmB,IAAI;AAC3C,YAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,QAC7D,UAAU,YAAY;AAAA,QAAU,gBAAgB,YAAY;AAAA,QAAgB,QAAQ;AAAA,QACpF;AAAA,QAAc,YAAY;AAAA,QAAU,WAAW;AAAA,QAC/C,eAAe,QAAQ;AAAA,QAAQ,gBAAgB,QAAQ;AAAA,QAAS,iBAAiB;AAAA,MACnF,CAAC;AACD,UAAI,eAAe,CAAC,YAAY,GAAI,QAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAE7G,YAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,YAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW;AAAA,QAC5C;AAAA,QACA,EAAE,OAAO,QAAQ,KAAK,WAAW;AAAA,MACnC;AAEA,UAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,cAAM,iCAAiC,WAAW;AAAA,UAChD,UAAU,YAAY;AAAA,UAAU,gBAAgB,YAAY;AAAA,UAAgB,QAAQ;AAAA,UACpF;AAAA,UAAc,YAAY;AAAA,UAAU,WAAW;AAAA,UAC/C,eAAe,QAAQ;AAAA,UAAQ,gBAAgB,QAAQ;AAAA,UAAS,UAAU,YAAY,YAAY;AAAA,QACpG,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,QACL,aAAa,KAAK,EAAE,IAAI,QAAQ,UAAU,KAAK,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,QACjE;AAAA,QACA,EAAE,cAAc,YAAY,SAAS;AAAA,MACvC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,UAAI,eAAe,EAAE,SAAU,QAAO,6BAA6B,KAAK,SAAS;AACjF,aAAO,MAAM,4BAA4B,EAAE,WAAW,WAAW,IAAI,CAAC;AACtE,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,0CAA0C,uBAAuB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACnI;AAAA,EACF;AAEA,iBAAe,IAAI,SAAkB,EAAE,OAAO,GAA+B;AAC3E,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI;AACF,YAAM,EAAE,IAAI,SAAS,IAAI,aAAa,MAAM,MAAM;AAClD,YAAM,EAAE,OAAO,IAAI,kBAAkB,MAAM,OAAO,YAAY,IAAI,IAAI,QAAQ,GAAG,EAAE,YAAY,CAAC;AAChG,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,IAAI,IAAI,MAAM,aAAa,OAAO;AACtE,YAAM,cAAc,MAAM,sBAAsB,IAAI,MAAM,OAAO,YAAY,UAAU,QAAQ,SAAS;AACxG,YAAM,4BAA4B,WAAW,MAAM,0BAA0B,YAAY,gBAAgB,SAAS;AAClH,YAAM,aAAa,2BAA2B,KAAK,YAAY,cAAc;AAE7E,YAAM,UAAU,MAAM,aAAsC,SAAS,CAAC,CAAC;AACvE,YAAM,SAAS;AAAA,QACb;AAAA,UACE,GAAG;AAAA,UACH,IAAI;AAAA,UACJ,gBAAgB,YAAY;AAAA,UAC5B,UAAU,YAAY;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,SAAS,uBAAuB,MAAM,MAAM;AAElD,YAAM,cAAc,mBAAmB,IAAI;AAC3C,YAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,QAC7D,UAAU,YAAY;AAAA,QAAU,gBAAgB,YAAY;AAAA,QAAgB,QAAQ;AAAA,QACpF;AAAA,QAAc,YAAY;AAAA,QAAU,WAAW;AAAA,QAC/C,eAAe,QAAQ;AAAA,QAAQ,gBAAgB,QAAQ;AAAA,QAAS,iBAAiB,EAAE,QAAQ,GAAG,QAAQ;AAAA,MACxG,CAAC;AACD,UAAI,eAAe,CAAC,YAAY,GAAI,QAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAE7G,YAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,YAAM,EAAE,SAAS,IAAI,MAAM,WAAW;AAAA,QACpC;AAAA,QACA,EAAE,OAAO,QAAQ,KAAK,WAAW;AAAA,MACnC;AAEA,UAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,cAAM,iCAAiC,WAAW;AAAA,UAChD,UAAU,YAAY;AAAA,UAAU,gBAAgB,YAAY;AAAA,UAAgB,QAAQ;AAAA,UACpF;AAAA,UAAc,YAAY;AAAA,UAAU,WAAW;AAAA,UAC/C,eAAe,QAAQ;AAAA,UAAQ,gBAAgB,QAAQ;AAAA,UAAS,UAAU,YAAY,YAAY;AAAA,QACpG,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,QACL,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,QAC9B;AAAA,QACA,EAAE,cAAc,YAAY,SAAS;AAAA,MACvC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,UAAI,eAAe,EAAE,SAAU,QAAO,6BAA6B,KAAK,SAAS;AACjF,aAAO,MAAM,2BAA2B,EAAE,WAAW,WAAW,IAAI,CAAC;AACrE,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,0CAA0C,uBAAuB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACnI;AAAA,EACF;AAEA,iBAAe,OAAO,SAAkB,EAAE,OAAO,GAA+B;AAC9E,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,QAAI;AACF,YAAM,EAAE,IAAI,SAAS,IAAI,aAAa,MAAM,MAAM;AAClD,YAAM,EAAE,OAAO,IAAI,kBAAkB,MAAM,OAAO,YAAY,IAAI,IAAI,QAAQ,GAAG,EAAE,YAAY,CAAC;AAChG,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,IAAI,IAAI,MAAM,aAAa,OAAO;AACtE,YAAM,cAAc,MAAM,sBAAsB,IAAI,MAAM,OAAO,YAAY,UAAU,QAAQ,SAAS;AACxG,YAAM,4BAA4B,WAAW,MAAM,0BAA0B,YAAY,gBAAgB,SAAS;AAClH,YAAM,aAAa,2BAA2B,KAAK,YAAY,cAAc;AAE7E,YAAM,SAAS,uBAAuB;AAAA,QACpC;AAAA,UACE;AAAA,YACE,IAAI;AAAA,YACJ,gBAAgB,YAAY;AAAA,YAC5B,UAAU,YAAY;AAAA,UACxB;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,YAAM,cAAc,mBAAmB,IAAI;AAC3C,YAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,QAC7D,UAAU,YAAY;AAAA,QAAU,gBAAgB,YAAY;AAAA,QAAgB,QAAQ;AAAA,QACpF;AAAA,QAAc,YAAY;AAAA,QAAU,WAAW;AAAA,QAC/C,eAAe,QAAQ;AAAA,QAAQ,gBAAgB,QAAQ;AAAA,QAAS,iBAAiB,EAAE,OAAO;AAAA,MAC5F,CAAC;AACD,UAAI,eAAe,CAAC,YAAY,GAAI,QAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAE7G,YAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,YAAM,EAAE,SAAS,IAAI,MAAM,WAAW;AAAA,QACpC;AAAA,QACA,EAAE,OAAO,QAAQ,KAAK,WAAW;AAAA,MACnC;AAEA,UAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,cAAM,iCAAiC,WAAW;AAAA,UAChD,UAAU,YAAY;AAAA,UAAU,gBAAgB,YAAY;AAAA,UAAgB,QAAQ;AAAA,UACpF;AAAA,UAAc,YAAY;AAAA,UAAU,WAAW;AAAA,UAC/C,eAAe,QAAQ;AAAA,UAAQ,gBAAgB,QAAQ;AAAA,UAAS,UAAU,YAAY,YAAY;AAAA,QACpG,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,QACL,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,QAC9B;AAAA,QACA,EAAE,cAAc,YAAY,SAAS;AAAA,MACvC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,EAAG,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AACnF,UAAI,eAAe,EAAE,SAAU,QAAO,6BAA6B,KAAK,SAAS;AACjF,aAAO,MAAM,8BAA8B,EAAE,WAAW,WAAW,IAAI,CAAC;AACxE,aAAO,aAAa,KAAK,EAAE,OAAO,UAAU,0CAA0C,uBAAuB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACnI;AAAA,EACF;AAEA,SAAO,EAAE,KAAK,MAAM,KAAK,OAAO;AAClC;",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerEntity, CustomerEntityRole } from '../data/entities'\nimport { entityRoleCreateSchema, entityRoleUpdateSchema, entityRoleDeleteSchema, type EntityRoleCreateInput, type EntityRoleUpdateInput, type EntityRoleDeleteInput } from '../data/validators'\nimport { withScopedPayload } from './utils'\nimport { resolveCustomersRequestContext, resolveAuthActorId } from '../lib/interactionRequestContext'\nimport { deriveDisplayNameFromEmail } from '../lib/displayName'\nimport { withOperationMetadata } from '../lib/operationMetadata'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\nconst roleIdQuerySchema = z.object({ roleId: z.string().uuid() })\n\nconst createBodySchema = z.object({\n roleType: z.string().trim().min(1).max(100),\n userId: z.string().uuid(),\n})\nconst updateBodySchema = z.object({\n userId: z.string().uuid(),\n})\n\nconst listItemSchema = z.object({\n id: z.string().uuid(),\n entityType: z.enum(['company', 'person']),\n entityId: z.string().uuid(),\n userId: z.string().uuid(),\n userName: z.string().nullable().optional(),\n userEmail: z.string().nullable().optional(),\n userPhone: z.string().nullable().optional(),\n roleType: z.string(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst listResponseSchema = z.object({ items: z.array(listItemSchema) })\nconst okResponseSchema = z.object({ ok: z.boolean() })\nconst createResponseSchema = z.object({ id: z.string().uuid() })\nconst errorSchema = z.object({ error: z.string() })\n\ntype EntityType = 'company' | 'person'\n\ntype Translator = Awaited<ReturnType<typeof resolveTranslations>>['translate']\n\nfunction getRoleContext(entityType: EntityType, entityId: string) {\n const resourceKind = entityType === 'company' ? 'customers.company' : 'customers.person'\n return { entityType, entityId, resourceKind, resourceId: entityId }\n}\n\nfunction buildValidationErrorResponse(error: z.ZodError, translate: Translator) {\n return NextResponse.json(\n { error: translate('customers.errors.validationFailed', 'Validation failed'), fieldErrors: error.flatten().fieldErrors },\n { status: 400 },\n )\n}\n\nasync function buildContext(request: Request) {\n const context = await resolveCustomersRequestContext(request)\n return {\n ...context,\n ctx: context.commandContext,\n }\n}\n\nfunction ensureRouteOrganizationAccess(\n organizationId: string,\n scope: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['scope'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n translate: Translator,\n) {\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n}\n\nasync function ensureFeatureOnOrganization(\n container: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['container'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n feature: string,\n organizationId: string,\n translate: Translator,\n) {\n const actorId = resolveAuthActorId(auth)\n if (!actorId) {\n throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n }\n let rbac: RbacService | undefined\n try {\n rbac = container.resolve('rbacService') as RbacService | undefined\n } catch (err) {\n logger.error('rbacService resolve failed', { component: 'entity-roles-factory', err })\n rbac = undefined\n }\n if (!rbac) {\n throw new CrudHttpError(500, { error: translate('customers.errors.internal', 'Internal error') })\n }\n const hasFeature = await rbac.userHasAllFeatures(actorId, [feature], {\n tenantId: auth.tenantId,\n organizationId,\n })\n if (!hasFeature) {\n throw new CrudHttpError(403, { error: translate('customers.errors.access_denied', 'Access denied') })\n }\n}\n\nasync function resolveEntityRouteScope(\n em: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['em'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n scope: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['scope'],\n entityType: EntityType,\n entityId: string,\n translate: Translator,\n) {\n const entity = await findOneWithDecryption(\n em,\n CustomerEntity,\n { id: entityId, kind: entityType, tenantId: auth.tenantId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null },\n )\n if (!entity || entity.tenantId !== auth.tenantId) {\n throw notFound(translate('customers.errors.customer_not_found', 'Customer not found'))\n }\n ensureRouteOrganizationAccess(entity.organizationId, scope, auth, translate)\n return {\n entity,\n organizationId: entity.organizationId,\n tenantId: entity.tenantId,\n }\n}\n\nasync function resolveRoleRouteScope(\n em: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['em'],\n auth: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['auth'],\n scope: Awaited<ReturnType<typeof resolveCustomersRequestContext>>['scope'],\n entityType: EntityType,\n entityId: string,\n roleId: string,\n translate: Translator,\n) {\n const role = await findOneWithDecryption(\n em,\n CustomerEntityRole,\n { id: roleId, tenantId: auth.tenantId, entityType, entityId, deletedAt: null },\n undefined,\n { tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null },\n )\n if (\n !role ||\n role.tenantId !== auth.tenantId ||\n role.entityType !== entityType ||\n role.entityId !== entityId\n ) {\n throw notFound(translate('customers.errors.role_not_found', 'Role not found'))\n }\n ensureRouteOrganizationAccess(role.organizationId, scope, auth, translate)\n return {\n role,\n organizationId: role.organizationId,\n tenantId: role.tenantId,\n }\n}\n\nfunction createScopedCommandContext(\n ctx: Awaited<ReturnType<typeof buildContext>>['ctx'],\n organizationId: string,\n) {\n return {\n ...ctx,\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n }\n}\n\nexport const entityRolesMetadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.roles.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.roles.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.roles.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.roles.manage'] },\n}\n\nexport function buildEntityRolesOpenApi(entityType: EntityType): OpenApiRouteDoc {\n const label = entityType === 'company' ? 'company' : 'person'\n return {\n tag: 'Customers',\n summary: `${label.charAt(0).toUpperCase() + label.slice(1)} role assignments`,\n pathParams: paramsSchema,\n methods: {\n GET: {\n summary: `List roles for a ${label}`,\n responses: [{ status: 200, description: 'Role assignments', schema: listResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: `Assign a role to a ${label}`,\n requestBody: { contentType: 'application/json', schema: createBodySchema },\n responses: [{ status: 201, description: 'Role created', schema: createResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 409, description: 'Role already assigned', schema: errorSchema },\n ],\n },\n PUT: {\n summary: `Update a ${label} role assignment`,\n query: roleIdQuerySchema,\n requestBody: { contentType: 'application/json', schema: updateBodySchema },\n responses: [{ status: 200, description: 'Role updated', schema: okResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: `Remove a ${label} role assignment`,\n query: roleIdQuerySchema,\n responses: [{ status: 200, description: 'Role deleted', schema: okResponseSchema }],\n errors: [\n { status: 400, description: 'Invalid request', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n },\n },\n }\n}\n\nexport function createEntityRolesHandlers(entityType: EntityType) {\n const resourceKind = entityType === 'company' ? 'customers.company' : 'customers.person'\n const logPrefix = entityType === 'company' ? 'customers.company.roles' : 'customers.person.roles'\n\n async function GET(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { container, em, auth, scope } = await buildContext(request)\n const targetScope = await resolveEntityRouteScope(em, auth, scope, entityType, entityId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.view', targetScope.organizationId, translate)\n\n const roles = await findWithDecryption(\n em,\n CustomerEntityRole,\n {\n entityType,\n entityId,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n deletedAt: null,\n },\n { orderBy: { roleType: 'asc' } },\n {\n tenantId: targetScope.tenantId,\n organizationId: targetScope.organizationId,\n },\n )\n\n const userIds = Array.from(new Set(roles.map((role) => role.userId).filter((value): value is string => typeof value === 'string' && value.length > 0)))\n const users = userIds.length\n ? await findWithDecryption(\n em,\n User,\n {\n id: { $in: userIds },\n deletedAt: null,\n ...(targetScope.tenantId ? { tenantId: targetScope.tenantId } : {}),\n },\n undefined,\n {\n tenantId: targetScope.tenantId,\n organizationId: targetScope.organizationId,\n },\n )\n : []\n const userMap = new Map(users.map((user) => [user.id, {\n name: user.name ?? deriveDisplayNameFromEmail(user.email) ?? null,\n email: user.email ?? null,\n phone: null,\n }]))\n\n return NextResponse.json({\n items: roles.map((role) => ({\n ...(userMap.has(role.userId)\n ? {\n userName: userMap.get(role.userId)?.name ?? null,\n userEmail: userMap.get(role.userId)?.email ?? null,\n userPhone: userMap.get(role.userId)?.phone ?? null,\n }\n : {}),\n id: role.id,\n entityType: role.entityType,\n entityId: role.entityId,\n userId: role.userId,\n roleType: role.roleType,\n createdAt: role.createdAt.toISOString(),\n updatedAt: role.updatedAt.toISOString(),\n })),\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles GET failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_load_roles', 'Failed to load roles') }, { status: 500 })\n }\n }\n\n async function POST(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { container, em, auth, scope, ctx } = await buildContext(request)\n const targetScope = await resolveEntityRouteScope(em, auth, scope, entityType, entityId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.manage', targetScope.organizationId, translate)\n const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId)\n\n const rawBody = await readJsonSafe<Record<string, unknown>>(request, {})\n const scoped = withScopedPayload(\n {\n ...rawBody,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n ...getRoleContext(entityType, entityId),\n },\n commandCtx,\n translate,\n )\n const parsed = entityRoleCreateSchema.parse(scoped)\n\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, mutationPayload: rawBody,\n })\n if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status })\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<EntityRoleCreateInput, { roleId: string }>(\n 'customers.entityRoles.create',\n { input: parsed, ctx: commandCtx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, metadata: guardResult.metadata ?? null,\n })\n }\n\n return withOperationMetadata(\n NextResponse.json({ id: result?.roleId ?? null }, { status: 201 }),\n logEntry,\n { resourceKind, resourceId: entityId },\n )\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles POST failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_assign_role', 'Failed to assign role') }, { status: 500 })\n }\n }\n\n async function PUT(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { roleId } = roleIdQuerySchema.parse(Object.fromEntries(new URL(request.url).searchParams))\n const { container, em, auth, scope, ctx } = await buildContext(request)\n const targetScope = await resolveRoleRouteScope(em, auth, scope, entityType, entityId, roleId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.manage', targetScope.organizationId, translate)\n const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId)\n\n const rawBody = await readJsonSafe<Record<string, unknown>>(request, {})\n const scoped = withScopedPayload(\n {\n ...rawBody,\n id: roleId,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n },\n commandCtx,\n translate,\n )\n const parsed = entityRoleUpdateSchema.parse(scoped)\n\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, mutationPayload: { roleId, ...rawBody },\n })\n if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status })\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<EntityRoleUpdateInput, { roleId: string }>(\n 'customers.entityRoles.update',\n { input: parsed, ctx: commandCtx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, metadata: guardResult.metadata ?? null,\n })\n }\n\n return withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind, resourceId: entityId },\n )\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles PUT failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_update_role', 'Failed to update role') }, { status: 500 })\n }\n }\n\n async function DELETE(request: Request, { params }: { params: { id: string } }) {\n const { translate } = await resolveTranslations()\n try {\n const { id: entityId } = paramsSchema.parse(params)\n const { roleId } = roleIdQuerySchema.parse(Object.fromEntries(new URL(request.url).searchParams))\n const { container, em, auth, scope, ctx } = await buildContext(request)\n const targetScope = await resolveRoleRouteScope(em, auth, scope, entityType, entityId, roleId, translate)\n await ensureFeatureOnOrganization(container, auth, 'customers.roles.manage', targetScope.organizationId, translate)\n const commandCtx = createScopedCommandContext(ctx, targetScope.organizationId)\n\n const parsed = entityRoleDeleteSchema.parse(\n withScopedPayload(\n {\n id: roleId,\n organizationId: targetScope.organizationId,\n tenantId: targetScope.tenantId,\n },\n commandCtx,\n translate,\n ),\n )\n const guardUserId = resolveAuthActorId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, mutationPayload: { roleId },\n })\n if (guardResult && !guardResult.ok) return NextResponse.json(guardResult.body, { status: guardResult.status })\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<EntityRoleDeleteInput, { roleId: string }>(\n 'customers.entityRoles.delete',\n { input: parsed, ctx: commandCtx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: targetScope.tenantId, organizationId: targetScope.organizationId, userId: guardUserId,\n resourceKind, resourceId: entityId, operation: 'custom',\n requestMethod: request.method, requestHeaders: request.headers, metadata: guardResult.metadata ?? null,\n })\n }\n\n return withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind, resourceId: entityId },\n )\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n if (err instanceof z.ZodError) return buildValidationErrorResponse(err, translate)\n logger.error('Entity roles DELETE failed', { component: logPrefix, err })\n return NextResponse.json({ error: translate('customers.errors.failed_to_delete_role', 'Failed to delete role') }, { status: 500 })\n }\n }\n\n return { GET, POST, PUT, DELETE }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ import { labelAssignCommandSchema, labelAssignmentSchema } from "../../../data/v
|
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
6
6
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
7
|
-
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { CrudHttpError, isCrudHttpError, notFound } from "@open-mercato/shared/lib/crud/errors";
|
|
8
8
|
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
|
|
9
9
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
10
10
|
import { z } from "zod";
|
|
@@ -68,7 +68,7 @@ async function POST(req) {
|
|
|
68
68
|
{ tenantId: auth.tenantId, organizationId }
|
|
69
69
|
);
|
|
70
70
|
if (!targetEntity) {
|
|
71
|
-
throw
|
|
71
|
+
throw notFound(translate("customers.errors.entity_not_found", "Entity not found"));
|
|
72
72
|
}
|
|
73
73
|
const entityKind = targetEntity.kind === "person" || targetEntity.kind === "company" ? targetEntity.kind : null;
|
|
74
74
|
const resourceKind = resolveResourceKind(entityKind);
|