@open-mercato/core 0.7.1-develop.7130.1.fef2396fd8 → 0.7.1-develop.7131.1.20abf9e885
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +5 -0
- package/dist/modules/auth/api/profile/route.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +13 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +9 -0
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js +5 -0
- package/dist/modules/customers/api/dictionaries/kind-settings/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/cancel/route.js +5 -0
- package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/complete/route.js +5 -0
- package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
- package/dist/modules/customers/api/labels/assign/route.js +5 -0
- package/dist/modules/customers/api/labels/assign/route.js.map +2 -2
- package/dist/modules/customers/api/labels/route.js +5 -0
- package/dist/modules/customers/api/labels/route.js.map +2 -2
- package/dist/modules/customers/api/labels/unassign/route.js +5 -0
- package/dist/modules/customers/api/labels/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +9 -0
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/route.js +5 -0
- package/dist/modules/customers/api/people/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +5 -0
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/route.js +13 -0
- package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
- package/dist/modules/customers/api/pipelines/route.js +13 -0
- package/dist/modules/customers/api/pipelines/route.js.map +2 -2
- package/dist/modules/customers/api/settings/address-format/route.js +5 -0
- package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js +5 -0
- package/dist/modules/customers/api/settings/dictionary-sort-modes/route.js.map +2 -2
- package/dist/modules/customers/api/settings/stuck-threshold/route.js +5 -0
- package/dist/modules/customers/api/settings/stuck-threshold/route.js.map +2 -2
- package/dist/modules/customers/api/tags/assign/route.js +5 -0
- package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
- package/dist/modules/customers/api/tags/unassign/route.js +5 -0
- package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +13 -0
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +9 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +5 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +5 -0
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/eudr/api/plots/import/route.js.map +2 -2
- package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +5 -0
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +5 -0
- package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/archive/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/read/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/route.js +5 -0
- package/dist/modules/messages/api/[id]/conversation/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/forward/route.js +5 -0
- package/dist/modules/messages/api/[id]/forward/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/reply/route.js +5 -0
- package/dist/modules/messages/api/[id]/reply/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +9 -0
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/token/[token]/route.js +5 -0
- package/dist/modules/messages/api/token/[token]/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/assign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/unassign/route.js +5 -0
- package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +5 -0
- package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/convert/route.js +5 -0
- package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
- package/dist/modules/sales/api/settings/document-numbers/route.js +5 -0
- package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
- package/dist/modules/sales/api/settings/order-editing/route.js +5 -0
- package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +5 -0
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/self/route.js +5 -0
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +5 -0
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +5 -0
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +9 -0
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +5 -0
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +5 -0
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +5 -0
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +5 -0
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +5 -0
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +5 -0
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +5 -0
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +5 -0
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +5 -0
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +5 -0
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +5 -0
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +5 -0
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +5 -0
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +5 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +5 -0
- package/src/modules/auth/api/profile/route.ts +5 -0
- package/src/modules/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +5 -0
- package/src/modules/customers/api/dictionaries/kind-settings/route.ts +5 -0
- package/src/modules/customers/api/interactions/cancel/route.ts +5 -0
- package/src/modules/customers/api/interactions/complete/route.ts +5 -0
- package/src/modules/customers/api/labels/assign/route.ts +5 -0
- package/src/modules/customers/api/labels/route.ts +5 -0
- package/src/modules/customers/api/labels/unassign/route.ts +5 -0
- package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +9 -0
- package/src/modules/customers/api/people/[id]/companies/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +5 -0
- package/src/modules/customers/api/pipeline-stages/route.ts +13 -0
- package/src/modules/customers/api/pipelines/route.ts +13 -0
- package/src/modules/customers/api/settings/address-format/route.ts +5 -0
- package/src/modules/customers/api/settings/dictionary-sort-modes/route.ts +5 -0
- package/src/modules/customers/api/settings/stuck-threshold/route.ts +5 -0
- package/src/modules/customers/api/tags/assign/route.ts +5 -0
- package/src/modules/customers/api/tags/unassign/route.ts +5 -0
- package/src/modules/customers/api/todos/route.ts +13 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +9 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +5 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +5 -0
- package/src/modules/directory/api/organization-branding/route.ts +5 -0
- package/src/modules/eudr/api/plots/import/route.ts +7 -0
- package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +7 -0
- package/src/modules/feature_toggles/api/overrides/route.ts +5 -0
- package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/archive/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/read/route.ts +5 -0
- package/src/modules/messages/api/[id]/conversation/route.ts +5 -0
- package/src/modules/messages/api/[id]/forward/route.ts +5 -0
- package/src/modules/messages/api/[id]/reply/route.ts +5 -0
- package/src/modules/messages/api/[id]/route.ts +9 -0
- package/src/modules/messages/api/token/[token]/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/assign/route.ts +5 -0
- package/src/modules/resources/api/resources/tags/unassign/route.ts +5 -0
- package/src/modules/sales/api/quotes/accept/route.ts +5 -0
- package/src/modules/sales/api/quotes/convert/route.ts +5 -0
- package/src/modules/sales/api/settings/document-numbers/route.ts +5 -0
- package/src/modules/sales/api/settings/order-editing/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/accept/route.ts +5 -0
- package/src/modules/staff/api/leave-requests/reject/route.ts +5 -0
- package/src/modules/staff/api/team-members/self/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/assign/route.ts +5 -0
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +5 -0
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +5 -0
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +9 -0
- package/src/modules/warranty_claims/api/ai/assess/route.ts +5 -0
- package/src/modules/warranty_claims/api/assign/route.ts +5 -0
- package/src/modules/warranty_claims/api/credit-memo/route.ts +5 -0
- package/src/modules/warranty_claims/api/events/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/claims/route.ts +5 -0
- package/src/modules/warranty_claims/api/portal/events/route.ts +5 -0
- package/src/modules/warranty_claims/api/receiving/route.ts +5 -0
- package/src/modules/warranty_claims/api/replacement-order/route.ts +5 -0
- package/src/modules/warranty_claims/api/return-label/route.ts +5 -0
- package/src/modules/warranty_claims/api/sales-return/route.ts +5 -0
- package/src/modules/warranty_claims/api/settings-general/route.ts +5 -0
- package/src/modules/warranty_claims/api/submit/route.ts +5 -0
- package/src/modules/warranty_claims/api/transition/route.ts +5 -0
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +5 -0
- package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +5 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/api/pipeline-stages/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CustomerPipelineStage, CustomerDictionaryEntry } from '../../data/entities'\nimport {\n pipelineStageCreateSchema,\n pipelineStageUpdateSchema,\n pipelineStageDeleteSchema,\n type PipelineStageCreateInput,\n type PipelineStageUpdateInput,\n type PipelineStageDeleteInput,\n} from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId ?? null\n return { ctx, organizationId, tenantId, translate }\n}\n\nexport async function GET(req: Request) {\n try {\n const { ctx, tenantId, translate } = await buildContext(req)\n if (!tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const orgFilter = resolveOrganizationScopeFilter(ctx.organizationScope, ctx.auth)\n const url = new URL(req.url)\n const pipelineId = url.searchParams.get('pipelineId')\n\n const em = (ctx.container.resolve('em') as EntityManager)\n const where: Record<string, unknown> = { tenantId, ...orgFilter.where }\n if (pipelineId) where.pipelineId = pipelineId\n\n const stages = await em.find(CustomerPipelineStage, where, { orderBy: { order: 'ASC' } })\n\n const stageLabels = stages.map((s) => s.label.trim().toLowerCase())\n const dictEntries = stageLabels.length\n ? await em.find(CustomerDictionaryEntry, {\n tenantId,\n ...orgFilter.where,\n kind: 'pipeline_stage',\n normalizedValue: { $in: stageLabels },\n })\n : []\n const dictByNormalized = new Map<string, CustomerDictionaryEntry>()\n dictEntries.forEach((entry) => dictByNormalized.set(entry.normalizedValue, entry))\n\n const items = stages.map((stage) => {\n const dictEntry = dictByNormalized.get(stage.label.trim().toLowerCase())\n return {\n id: stage.id,\n pipelineId: stage.pipelineId,\n label: stage.label,\n order: stage.order,\n color: dictEntry?.color ?? null,\n icon: dictEntry?.icon ?? null,\n organizationId: stage.organizationId,\n tenantId: stage.tenantId,\n createdAt: stage.createdAt,\n updatedAt: stage.updatedAt,\n }\n })\n return NextResponse.json({ items, total: items.length })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipeline-stages GET failed', { err })\n return NextResponse.json({ error: 'Failed to load pipeline stages' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageCreateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<PipelineStageCreateInput, { stageId: string }>(\n 'customers.pipeline-stages.create',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: result?.stageId ?? organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ id: result?.stageId ?? null }, { status: 201 })\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.pipelineStage',\n resourceId: logEntry.resourceId ?? result?.stageId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipeline-stages POST failed', { err })\n return NextResponse.json({ error: 'Failed to create pipeline stage' }, { status: 400 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageUpdateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { logEntry } = await commandBus.execute<PipelineStageUpdateInput, void>(\n 'customers.pipeline-stages.update',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ ok: 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.pipelineStage',\n resourceId: logEntry.resourceId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipeline-stages PUT failed', { err })\n return NextResponse.json({ error: 'Failed to update pipeline stage' }, { status: 400 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageDeleteSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineStageDeleteInput, void>(\n 'customers.pipeline-stages.delete',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipeline-stages DELETE failed', { err })\n return NextResponse.json({ error: 'Failed to delete pipeline stage' }, { status: 400 })\n }\n}\n\nconst stageItemSchema = z.object({\n id: z.string().uuid(),\n pipelineId: z.string().uuid(),\n label: z.string(),\n order: z.number(),\n color: z.string().nullable(),\n icon: z.string().nullable(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n createdAt: z.date(),\n updatedAt: z.date(),\n})\n\nconst stageListResponseSchema = z.object({\n items: z.array(stageItemSchema),\n total: z.number(),\n})\n\nconst stageCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nconst stageOkResponseSchema = z.object({\n ok: z.boolean(),\n})\n\nconst stageErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Manage pipeline stages',\n methods: {\n GET: {\n summary: 'List pipeline stages',\n description: 'Returns pipeline stages for the authenticated organization, optionally filtered by pipelineId.',\n query: z.object({ pipelineId: z.string().uuid().optional() }),\n responses: [\n { status: 200, description: 'Stage list', schema: stageListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: stageErrorSchema },\n { status: 400, description: 'Invalid request', schema: stageErrorSchema },\n ],\n },\n POST: {\n summary: 'Create pipeline stage',\n description: 'Creates a new pipeline stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageCreateSchema },\n responses: [\n { status: 201, description: 'Stage created', schema: stageCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: stageErrorSchema },\n { status: 401, description: 'Unauthorized', schema: stageErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update pipeline stage',\n description: 'Updates an existing pipeline stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageUpdateSchema },\n responses: [\n { status: 200, description: 'Stage updated', schema: stageOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: stageErrorSchema },\n { status: 404, description: 'Stage not found', schema: stageErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete pipeline stage',\n description: 'Deletes a pipeline stage. Returns 409 if active deals use this stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageDeleteSchema },\n responses: [\n { status: 200, description: 'Stage deleted', schema: stageOkResponseSchema },\n ],\n errors: [\n { status: 409, description: 'Stage has active deals', schema: stageErrorSchema },\n { status: 404, description: 'Stage not found', schema: stageErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,sCAAsC;AAG/C,SAAS,uBAAuB,+BAA+B;AAC/D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,yBAAyB;AAClC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CustomerPipelineStage, CustomerDictionaryEntry } from '../../data/entities'\nimport {\n pipelineStageCreateSchema,\n pipelineStageUpdateSchema,\n pipelineStageDeleteSchema,\n type PipelineStageCreateInput,\n type PipelineStageUpdateInput,\n type PipelineStageDeleteInput,\n} from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nconst PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId ?? null\n return { ctx, organizationId, tenantId, translate }\n}\n\nexport async function GET(req: Request) {\n try {\n const { ctx, tenantId, translate } = await buildContext(req)\n if (!tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const orgFilter = resolveOrganizationScopeFilter(ctx.organizationScope, ctx.auth)\n const url = new URL(req.url)\n const pipelineId = url.searchParams.get('pipelineId')\n\n const em = (ctx.container.resolve('em') as EntityManager)\n const where: Record<string, unknown> = { tenantId, ...orgFilter.where }\n if (pipelineId) where.pipelineId = pipelineId\n\n const stages = await em.find(CustomerPipelineStage, where, { orderBy: { order: 'ASC' } })\n\n const stageLabels = stages.map((s) => s.label.trim().toLowerCase())\n const dictEntries = stageLabels.length\n ? await em.find(CustomerDictionaryEntry, {\n tenantId,\n ...orgFilter.where,\n kind: 'pipeline_stage',\n normalizedValue: { $in: stageLabels },\n })\n : []\n const dictByNormalized = new Map<string, CustomerDictionaryEntry>()\n dictEntries.forEach((entry) => dictByNormalized.set(entry.normalizedValue, entry))\n\n const items = stages.map((stage) => {\n const dictEntry = dictByNormalized.get(stage.label.trim().toLowerCase())\n return {\n id: stage.id,\n pipelineId: stage.pipelineId,\n label: stage.label,\n order: stage.order,\n color: dictEntry?.color ?? null,\n icon: dictEntry?.icon ?? null,\n organizationId: stage.organizationId,\n tenantId: stage.tenantId,\n createdAt: stage.createdAt,\n updatedAt: stage.updatedAt,\n }\n })\n return NextResponse.json({ items, total: items.length })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipeline-stages GET failed', { err })\n return NextResponse.json({ error: 'Failed to load pipeline stages' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageCreateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<PipelineStageCreateInput, { stageId: string }>(\n 'customers.pipeline-stages.create',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: result?.stageId ?? organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ id: result?.stageId ?? null }, { status: 201 })\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.pipelineStage',\n resourceId: logEntry.resourceId ?? result?.stageId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers.pipeline-stages POST failed', { err })\n return NextResponse.json({ error: 'Failed to create pipeline stage' }, { status: 400 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageUpdateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { logEntry } = await commandBus.execute<PipelineStageUpdateInput, void>(\n 'customers.pipeline-stages.update',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ ok: 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.pipelineStage',\n resourceId: logEntry.resourceId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers.pipeline-stages PUT failed', { err })\n return NextResponse.json({ error: 'Failed to update pipeline stage' }, { status: 400 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineStageDeleteSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineStageDeleteInput, void>(\n 'customers.pipeline-stages.delete',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_STAGE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers.pipeline-stages DELETE failed', { err })\n return NextResponse.json({ error: 'Failed to delete pipeline stage' }, { status: 400 })\n }\n}\n\nconst stageItemSchema = z.object({\n id: z.string().uuid(),\n pipelineId: z.string().uuid(),\n label: z.string(),\n order: z.number(),\n color: z.string().nullable(),\n icon: z.string().nullable(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n createdAt: z.date(),\n updatedAt: z.date(),\n})\n\nconst stageListResponseSchema = z.object({\n items: z.array(stageItemSchema),\n total: z.number(),\n})\n\nconst stageCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nconst stageOkResponseSchema = z.object({\n ok: z.boolean(),\n})\n\nconst stageErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Manage pipeline stages',\n methods: {\n GET: {\n summary: 'List pipeline stages',\n description: 'Returns pipeline stages for the authenticated organization, optionally filtered by pipelineId.',\n query: z.object({ pipelineId: z.string().uuid().optional() }),\n responses: [\n { status: 200, description: 'Stage list', schema: stageListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: stageErrorSchema },\n { status: 400, description: 'Invalid request', schema: stageErrorSchema },\n ],\n },\n POST: {\n summary: 'Create pipeline stage',\n description: 'Creates a new pipeline stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageCreateSchema },\n responses: [\n { status: 201, description: 'Stage created', schema: stageCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: stageErrorSchema },\n { status: 401, description: 'Unauthorized', schema: stageErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update pipeline stage',\n description: 'Updates an existing pipeline stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageUpdateSchema },\n responses: [\n { status: 200, description: 'Stage updated', schema: stageOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: stageErrorSchema },\n { status: 404, description: 'Stage not found', schema: stageErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete pipeline stage',\n description: 'Deletes a pipeline stage. Returns 409 if active deals use this stage.',\n requestBody: { contentType: 'application/json', schema: pipelineStageDeleteSchema },\n responses: [\n { status: 200, description: 'Stage deleted', schema: stageOkResponseSchema },\n ],\n errors: [\n { status: 409, description: 'Stage has active deals', schema: stageErrorSchema },\n { status: 404, description: 'Stage not found', schema: stageErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
validateCrudMutationGuard
|
|
20
20
|
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
21
21
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
22
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
22
23
|
const logger = createLogger("customers");
|
|
23
24
|
const PIPELINE_RESOURCE_KIND = "customers.pipeline";
|
|
24
25
|
const metadata = {
|
|
@@ -138,6 +139,10 @@ async function POST(req) {
|
|
|
138
139
|
if (isCrudHttpError(err)) {
|
|
139
140
|
return NextResponse.json(err.body, { status: err.status });
|
|
140
141
|
}
|
|
142
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
143
|
+
if (interceptorRejection) {
|
|
144
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
145
|
+
}
|
|
141
146
|
logger.error("customers.pipelines POST failed", { err });
|
|
142
147
|
return NextResponse.json({ error: "Failed to create pipeline" }, { status: 400 });
|
|
143
148
|
}
|
|
@@ -203,6 +208,10 @@ async function PUT(req) {
|
|
|
203
208
|
if (isCrudHttpError(err)) {
|
|
204
209
|
return NextResponse.json(err.body, { status: err.status });
|
|
205
210
|
}
|
|
211
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
212
|
+
if (interceptorRejection) {
|
|
213
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
214
|
+
}
|
|
206
215
|
logger.error("customers.pipelines PUT failed", { err });
|
|
207
216
|
return NextResponse.json({ error: "Failed to update pipeline" }, { status: 400 });
|
|
208
217
|
}
|
|
@@ -253,6 +262,10 @@ async function DELETE(req) {
|
|
|
253
262
|
if (isCrudHttpError(err)) {
|
|
254
263
|
return NextResponse.json(err.body, { status: err.status });
|
|
255
264
|
}
|
|
265
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
266
|
+
if (interceptorRejection) {
|
|
267
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
268
|
+
}
|
|
256
269
|
logger.error("customers.pipelines DELETE failed", { err });
|
|
257
270
|
return NextResponse.json({ error: "Failed to delete pipeline" }, { status: 400 });
|
|
258
271
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/api/pipelines/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CustomerPipeline } from '../../data/entities'\nimport {\n pipelineCreateSchema,\n pipelineUpdateSchema,\n pipelineDeleteSchema,\n type PipelineCreateInput,\n type PipelineUpdateInput,\n type PipelineDeleteInput,\n} from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst PIPELINE_RESOURCE_KIND = 'customers.pipeline'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId ?? null\n return { ctx, organizationId, tenantId, translate }\n}\n\nexport async function GET(req: Request) {\n try {\n const { ctx, tenantId, translate } = await buildContext(req)\n if (!tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const orgFilter = resolveOrganizationScopeFilter(ctx.organizationScope, ctx.auth)\n const url = new URL(req.url)\n const isDefaultParam = url.searchParams.get('isDefault')\n\n const em = (ctx.container.resolve('em') as EntityManager)\n const where: Record<string, unknown> = { tenantId, ...orgFilter.where }\n if (isDefaultParam === 'true') where.isDefault = true\n if (isDefaultParam === 'false') where.isDefault = false\n\n const pipelines = await em.find(CustomerPipeline, where, { orderBy: { createdAt: 'ASC' } })\n const items = pipelines.map((pipeline) => ({\n id: pipeline.id,\n name: pipeline.name,\n isDefault: pipeline.isDefault,\n organizationId: pipeline.organizationId,\n tenantId: pipeline.tenantId,\n createdAt: pipeline.createdAt,\n updatedAt: pipeline.updatedAt,\n }))\n return NextResponse.json({ items, total: items.length })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipelines GET failed', { err })\n return NextResponse.json({ error: 'Failed to load pipelines' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineCreateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<PipelineCreateInput, { pipelineId: string }>(\n 'customers.pipelines.create',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: result?.pipelineId ?? organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ id: result?.pipelineId ?? null }, { status: 201 })\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.pipeline',\n resourceId: logEntry.resourceId ?? result?.pipelineId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipelines POST failed', { err })\n return NextResponse.json({ error: 'Failed to create pipeline' }, { status: 400 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineUpdateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { logEntry } = await commandBus.execute<PipelineUpdateInput, void>(\n 'customers.pipelines.update',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ ok: 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.pipeline',\n resourceId: logEntry.resourceId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipelines PUT failed', { err })\n return NextResponse.json({ error: 'Failed to update pipeline' }, { status: 400 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineDeleteSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineDeleteInput, void>(\n 'customers.pipelines.delete',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipelines DELETE failed', { err })\n return NextResponse.json({ error: 'Failed to delete pipeline' }, { status: 400 })\n }\n}\n\nconst pipelineItemSchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n isDefault: z.boolean(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n createdAt: z.date(),\n updatedAt: z.date(),\n})\n\nconst pipelineListResponseSchema = z.object({\n items: z.array(pipelineItemSchema),\n total: z.number(),\n})\n\nconst pipelineCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nconst pipelineOkResponseSchema = z.object({\n ok: z.boolean(),\n})\n\nconst pipelineErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Manage customer pipelines',\n methods: {\n GET: {\n summary: 'List pipelines',\n description: 'Returns a list of pipelines scoped to the authenticated organization.',\n query: z.object({ isDefault: z.string().optional() }),\n responses: [\n { status: 200, description: 'Pipeline list', schema: pipelineListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: pipelineErrorSchema },\n { status: 400, description: 'Invalid request', schema: pipelineErrorSchema },\n ],\n },\n POST: {\n summary: 'Create pipeline',\n description: 'Creates a new pipeline within the authenticated organization.',\n requestBody: { contentType: 'application/json', schema: pipelineCreateSchema },\n responses: [\n { status: 201, description: 'Pipeline created', schema: pipelineCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: pipelineErrorSchema },\n { status: 401, description: 'Unauthorized', schema: pipelineErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update pipeline',\n description: 'Updates an existing pipeline.',\n requestBody: { contentType: 'application/json', schema: pipelineUpdateSchema },\n responses: [\n { status: 200, description: 'Pipeline updated', schema: pipelineOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: pipelineErrorSchema },\n { status: 404, description: 'Pipeline not found', schema: pipelineErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete pipeline',\n description: 'Deletes a pipeline. Returns 409 if active deals exist.',\n requestBody: { contentType: 'application/json', schema: pipelineDeleteSchema },\n responses: [\n { status: 200, description: 'Pipeline deleted', schema: pipelineOkResponseSchema },\n ],\n errors: [\n { status: 409, description: 'Pipeline has active deals', schema: pipelineErrorSchema },\n { status: 404, description: 'Pipeline not found', schema: pipelineErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,sCAAsC;AAG/C,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,yBAAyB;AAClC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CustomerPipeline } from '../../data/entities'\nimport {\n pipelineCreateSchema,\n pipelineUpdateSchema,\n pipelineDeleteSchema,\n type PipelineCreateInput,\n type PipelineUpdateInput,\n type PipelineDeleteInput,\n} from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nconst PIPELINE_RESOURCE_KIND = 'customers.pipeline'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId ?? null\n return { ctx, organizationId, tenantId, translate }\n}\n\nexport async function GET(req: Request) {\n try {\n const { ctx, tenantId, translate } = await buildContext(req)\n if (!tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const orgFilter = resolveOrganizationScopeFilter(ctx.organizationScope, ctx.auth)\n const url = new URL(req.url)\n const isDefaultParam = url.searchParams.get('isDefault')\n\n const em = (ctx.container.resolve('em') as EntityManager)\n const where: Record<string, unknown> = { tenantId, ...orgFilter.where }\n if (isDefaultParam === 'true') where.isDefault = true\n if (isDefaultParam === 'false') where.isDefault = false\n\n const pipelines = await em.find(CustomerPipeline, where, { orderBy: { createdAt: 'ASC' } })\n const items = pipelines.map((pipeline) => ({\n id: pipeline.id,\n name: pipeline.name,\n isDefault: pipeline.isDefault,\n organizationId: pipeline.organizationId,\n tenantId: pipeline.tenantId,\n createdAt: pipeline.createdAt,\n updatedAt: pipeline.updatedAt,\n }))\n return NextResponse.json({ items, total: items.length })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('customers.pipelines GET failed', { err })\n return NextResponse.json({ error: 'Failed to load pipelines' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineCreateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<PipelineCreateInput, { pipelineId: string }>(\n 'customers.pipelines.create',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: result?.pipelineId ?? organizationId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ id: result?.pipelineId ?? null }, { status: 201 })\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.pipeline',\n resourceId: logEntry.resourceId ?? result?.pipelineId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers.pipelines POST failed', { err })\n return NextResponse.json({ error: 'Failed to create pipeline' }, { status: 400 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineUpdateSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { logEntry } = await commandBus.execute<PipelineUpdateInput, void>(\n 'customers.pipelines.update',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const response = NextResponse.json({ ok: 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.pipeline',\n resourceId: logEntry.resourceId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers.pipelines PUT failed', { err })\n return NextResponse.json({ error: 'Failed to update pipeline' }, { status: 400 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const { ctx, organizationId, tenantId, translate } = await buildContext(req)\n if (!organizationId || !tenantId) {\n return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })\n }\n const body = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(body, ctx, translate)\n const input = pipelineDeleteSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n await commandBus.execute<PipelineDeleteInput, void>(\n 'customers.pipelines.delete',\n { input, ctx },\n )\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: PIPELINE_RESOURCE_KIND,\n resourceId: input.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('customers.pipelines DELETE failed', { err })\n return NextResponse.json({ error: 'Failed to delete pipeline' }, { status: 400 })\n }\n}\n\nconst pipelineItemSchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n isDefault: z.boolean(),\n organizationId: z.string().uuid(),\n tenantId: z.string().uuid(),\n createdAt: z.date(),\n updatedAt: z.date(),\n})\n\nconst pipelineListResponseSchema = z.object({\n items: z.array(pipelineItemSchema),\n total: z.number(),\n})\n\nconst pipelineCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nconst pipelineOkResponseSchema = z.object({\n ok: z.boolean(),\n})\n\nconst pipelineErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Manage customer pipelines',\n methods: {\n GET: {\n summary: 'List pipelines',\n description: 'Returns a list of pipelines scoped to the authenticated organization.',\n query: z.object({ isDefault: z.string().optional() }),\n responses: [\n { status: 200, description: 'Pipeline list', schema: pipelineListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: pipelineErrorSchema },\n { status: 400, description: 'Invalid request', schema: pipelineErrorSchema },\n ],\n },\n POST: {\n summary: 'Create pipeline',\n description: 'Creates a new pipeline within the authenticated organization.',\n requestBody: { contentType: 'application/json', schema: pipelineCreateSchema },\n responses: [\n { status: 201, description: 'Pipeline created', schema: pipelineCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: pipelineErrorSchema },\n { status: 401, description: 'Unauthorized', schema: pipelineErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update pipeline',\n description: 'Updates an existing pipeline.',\n requestBody: { contentType: 'application/json', schema: pipelineUpdateSchema },\n responses: [\n { status: 200, description: 'Pipeline updated', schema: pipelineOkResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: pipelineErrorSchema },\n { status: 404, description: 'Pipeline not found', schema: pipelineErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete pipeline',\n description: 'Deletes a pipeline. Returns 409 if active deals exist.',\n requestBody: { contentType: 'application/json', schema: pipelineDeleteSchema },\n responses: [\n { status: 200, description: 'Pipeline deleted', schema: pipelineOkResponseSchema },\n ],\n errors: [\n { status: 409, description: 'Pipeline has active deals', schema: pipelineErrorSchema },\n { status: 404, description: 'Pipeline not found', schema: pipelineErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ import { customerSettingsUpsertSchema } from "../../../data/validators.js";
|
|
|
13
13
|
import { loadCustomerSettings } from "../../../commands/settings.js";
|
|
14
14
|
import { withScopedPayload } from "../../utils.js";
|
|
15
15
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
16
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
16
17
|
const logger = createLogger("customers");
|
|
17
18
|
const SETTINGS_RESOURCE_KIND = "customers.settings";
|
|
18
19
|
const metadata = {
|
|
@@ -109,6 +110,10 @@ async function PUT(req) {
|
|
|
109
110
|
if (isCrudHttpError(err)) {
|
|
110
111
|
return NextResponse.json(err.body, { status: err.status });
|
|
111
112
|
}
|
|
113
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
114
|
+
if (interceptorRejection) {
|
|
115
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
116
|
+
}
|
|
112
117
|
const { translate } = await resolveTranslations();
|
|
113
118
|
logger.error("customers.settings.address-format.put failed", { err });
|
|
114
119
|
return NextResponse.json({ error: translate("customers.errors.save_failed", "Failed to save settings") }, { status: 400 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/settings/address-format/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { customerSettingsUpsertSchema, type CustomerSettingsUpsertInput } from '../../../data/validators'\nimport { loadCustomerSettings } from '../../../commands/settings'\nimport type { CustomerAddressFormat } from '../../../data/entities'\nimport { withScopedPayload } from '../../utils'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst SETTINGS_RESOURCE_KIND = 'customers.settings'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = (container.resolve('em') as EntityManager)\n return {\n ctx,\n tenantId: auth.tenantId,\n organizationId,\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, tenantId, organizationId } = await resolveSettingsContext(req)\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n return NextResponse.json({\n addressFormat: record?.addressFormat ?? 'line_first',\n })\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.settings.address-format.get failed', { err })\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to load settings') }, { status: 400 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, tenantId, organizationId, translate } = await resolveSettingsContext(req)\n const payload = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(payload, ctx, translate)\n const input = customerSettingsUpsertSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: SETTINGS_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result } = await commandBus.execute<CustomerSettingsUpsertInput, { settingsId: string; addressFormat: CustomerAddressFormat }>(\n 'customers.settings.save',\n { input, ctx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: SETTINGS_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n addressFormat: result?.addressFormat ?? input.addressFormat,\n })\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.settings.address-format.put failed', { err })\n return NextResponse.json({ error: translate('customers.errors.save_failed', 'Failed to save settings') }, { status: 400 })\n }\n}\n\nconst addressFormatResponseSchema = z.object({\n addressFormat: z.string(),\n})\n\nconst addressFormatErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer address format settings',\n methods: {\n GET: {\n summary: 'Retrieve address format',\n description: 'Returns the current address formatting preference for the selected organization.',\n responses: [\n { status: 200, description: 'Current address format', schema: addressFormatResponseSchema },\n { status: 401, description: 'Unauthorized', schema: addressFormatErrorSchema },\n { status: 400, description: 'Organization context missing', schema: addressFormatErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update address format',\n description: 'Updates the address format preference for the selected organization.',\n requestBody: {\n contentType: 'application/json',\n schema: customerSettingsUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Updated address format', schema: addressFormatResponseSchema },\n { status: 401, description: 'Unauthorized', schema: addressFormatErrorSchema },\n { status: 400, description: 'Invalid payload or organization context', schema: addressFormatErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAGnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAC/C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,oCAAsE;AAC/E,SAAS,4BAA4B;AAErC,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { customerSettingsUpsertSchema, type CustomerSettingsUpsertInput } from '../../../data/validators'\nimport { loadCustomerSettings } from '../../../commands/settings'\nimport type { CustomerAddressFormat } from '../../../data/entities'\nimport { withScopedPayload } from '../../utils'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nconst SETTINGS_RESOURCE_KIND = 'customers.settings'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, { error: translate('customers.errors.organization_required', 'Organization context is required') })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = (container.resolve('em') as EntityManager)\n return {\n ctx,\n tenantId: auth.tenantId,\n organizationId,\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, tenantId, organizationId } = await resolveSettingsContext(req)\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n return NextResponse.json({\n addressFormat: record?.addressFormat ?? 'line_first',\n })\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.settings.address-format.get failed', { err })\n return NextResponse.json({ error: translate('customers.errors.lookup_failed', 'Failed to load settings') }, { status: 400 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, tenantId, organizationId, translate } = await resolveSettingsContext(req)\n const payload = await req.json().catch(() => ({}))\n const scoped = withScopedPayload(payload, ctx, translate)\n const input = customerSettingsUpsertSchema.parse(scoped)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: SETTINGS_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result } = await commandBus.execute<CustomerSettingsUpsertInput, { settingsId: string; addressFormat: CustomerAddressFormat }>(\n 'customers.settings.save',\n { input, ctx },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId: ctx.auth!.sub,\n resourceKind: SETTINGS_RESOURCE_KIND,\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n addressFormat: result?.addressFormat ?? input.addressFormat,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('customers.settings.address-format.put failed', { err })\n return NextResponse.json({ error: translate('customers.errors.save_failed', 'Failed to save settings') }, { status: 400 })\n }\n}\n\nconst addressFormatResponseSchema = z.object({\n addressFormat: z.string(),\n})\n\nconst addressFormatErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer address format settings',\n methods: {\n GET: {\n summary: 'Retrieve address format',\n description: 'Returns the current address formatting preference for the selected organization.',\n responses: [\n { status: 200, description: 'Current address format', schema: addressFormatResponseSchema },\n { status: 401, description: 'Unauthorized', schema: addressFormatErrorSchema },\n { status: 400, description: 'Organization context missing', schema: addressFormatErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update address format',\n description: 'Updates the address format preference for the selected organization.',\n requestBody: {\n contentType: 'application/json',\n schema: customerSettingsUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Updated address format', schema: addressFormatResponseSchema },\n { status: 401, description: 'Unauthorized', schema: addressFormatErrorSchema },\n { status: 400, description: 'Invalid payload or organization context', schema: addressFormatErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "../../dictionaries/context.js";
|
|
26
26
|
import { invalidateDictionaryCache } from "../../dictionaries/cache.js";
|
|
27
27
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
28
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
28
29
|
const logger = createLogger("customers");
|
|
29
30
|
const metadata = {
|
|
30
31
|
GET: { requireAuth: true, requireFeatures: ["customers.settings.manage"] },
|
|
@@ -180,6 +181,10 @@ async function PATCH(req) {
|
|
|
180
181
|
if (isCrudHttpError(err)) {
|
|
181
182
|
return NextResponse.json(err.body, { status: err.status });
|
|
182
183
|
}
|
|
184
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
185
|
+
if (interceptorRejection) {
|
|
186
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
187
|
+
}
|
|
183
188
|
const { translate } = await resolveTranslations();
|
|
184
189
|
logger.error("customers.settings.dictionary-sort-modes.patch failed", { err });
|
|
185
190
|
return NextResponse.json(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/settings/dictionary-sort-modes/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\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 type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport {\n dictionaryEntrySortModeSchema,\n type DictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n customerDictionarySortModesUpsertSchema,\n type CustomerDictionarySortModesUpsertInput,\n} from '../../../data/validators'\nimport { loadCustomerSettings } from '../../../commands/settings'\nimport { withScopedPayload } from '../../utils'\nimport {\n BUILTIN_DICTIONARY_ROUTE_KINDS,\n dictionaryKindSchema,\n mapDictionaryKind,\n} from '../../dictionaries/context'\nimport { invalidateDictionaryCache } from '../../dictionaries/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n PATCH: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n cache?: CacheStrategy\n}\n\nconst dictionarySortModesPayloadSchema = z.object({\n dictionarySortModes: z.record(dictionaryKindSchema, dictionaryEntrySortModeSchema),\n})\n\nconst dictionarySortModesResponseSchema = z.object({\n dictionarySortModes: z.record(z.string(), dictionaryEntrySortModeSchema),\n})\n\nconst dictionarySortModesErrorSchema = z.object({\n error: z.string(),\n})\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, {\n error: translate('customers.errors.unauthorized', 'Unauthorized'),\n })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('customers.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n let cache: CacheStrategy | undefined\n try {\n cache = container.resolve('cache') as CacheStrategy\n } catch {}\n\n return {\n ctx,\n tenantId: auth.tenantId,\n organizationId,\n translate,\n em: container.resolve('em') as EntityManager,\n cache,\n }\n}\n\nfunction resolveActorId(ctx: CommandRuntimeContext): string {\n const auth = ctx.auth\n if (auth && typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (auth && typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (auth && typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nfunction normalizeDictionarySortModes(value: unknown): Partial<Record<string, DictionaryEntrySortMode>> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return {}\n const result: Partial<Record<string, DictionaryEntrySortMode>> = {}\n for (const [rawKind, rawMode] of Object.entries(value as Record<string, unknown>)) {\n const kind = dictionaryKindSchema.safeParse(rawKind)\n const mode = dictionaryEntrySortModeSchema.safeParse(rawMode)\n if (kind.success && mode.success) {\n result[kind.data] = mode.data\n }\n }\n return result\n}\n\nasync function invalidateCustomerDictionarySortCache(\n cache: CacheStrategy | undefined,\n tenantId: string,\n organizationId: string,\n dictionarySortModes: Partial<Record<string, DictionaryEntrySortMode>>,\n) {\n const routeKinds = new Set<string>([\n ...BUILTIN_DICTIONARY_ROUTE_KINDS,\n ...Object.keys(dictionarySortModes),\n ])\n for (const routeKind of routeKinds) {\n const parsed = dictionaryKindSchema.safeParse(routeKind)\n if (!parsed.success) continue\n const { mappedKind } = mapDictionaryKind(parsed.data)\n await invalidateDictionaryCache(cache, {\n tenantId,\n mappedKind,\n organizationIds: [organizationId],\n })\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, tenantId, organizationId } = await resolveSettingsContext(req)\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n return NextResponse.json({\n dictionarySortModes: normalizeDictionarySortModes(record?.dictionarySortModes),\n })\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.settings.dictionary-sort-modes.get failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.lookup_failed', 'Failed to load settings') },\n { status: 400 },\n )\n }\n}\n\nexport async function PATCH(req: Request) {\n try {\n const { ctx, tenantId, organizationId, translate, cache, em } = await resolveSettingsContext(req)\n const payload = dictionarySortModesPayloadSchema.parse(await readJsonSafe(req, {}))\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n const dictionarySortModes = {\n ...normalizeDictionarySortModes(record?.dictionarySortModes),\n ...payload.dictionarySortModes,\n }\n const scoped = withScopedPayload({ dictionarySortModes }, ctx, translate)\n const input = customerDictionarySortModesUpsertSchema.parse(scoped)\n const userId = resolveActorId(ctx)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<\n CustomerDictionarySortModesUpsertInput,\n { settingsId: string; dictionarySortModes: CustomerDictionarySortModesUpsertInput['dictionarySortModes'] }\n >('customers.settings.save_dictionary_sort_modes', { input, ctx })\n\n await invalidateCustomerDictionarySortCache(cache, tenantId, organizationId, input.dictionarySortModes)\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n dictionarySortModes: normalizeDictionarySortModes(result?.dictionarySortModes ?? input.dictionarySortModes),\n })\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.settings.dictionary-sort-modes.patch failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.save_failed', 'Failed to save settings') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer dictionary entry sort modes',\n methods: {\n GET: {\n summary: 'Retrieve dictionary sort modes',\n description: 'Returns entry sort preferences for customer dictionaries in the selected organization.',\n responses: [\n { status: 200, description: 'Current dictionary sort modes', schema: dictionarySortModesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: dictionarySortModesErrorSchema },\n { status: 400, description: 'Organization context missing', schema: dictionarySortModesErrorSchema },\n ],\n },\n PATCH: {\n summary: 'Update dictionary sort modes',\n description: 'Updates entry sort preferences for customer dictionaries in the selected organization.',\n requestBody: {\n contentType: 'application/json',\n schema: dictionarySortModesPayloadSchema,\n },\n responses: [\n { status: 200, description: 'Updated dictionary sort modes', schema: dictionarySortModesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: dictionarySortModesErrorSchema },\n { status: 400, description: 'Invalid payload or organization context', schema: dictionarySortModesErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,4BAA4B;AACrC,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iCAAiC;AAE1C,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\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 type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport {\n dictionaryEntrySortModeSchema,\n type DictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n customerDictionarySortModesUpsertSchema,\n type CustomerDictionarySortModesUpsertInput,\n} from '../../../data/validators'\nimport { loadCustomerSettings } from '../../../commands/settings'\nimport { withScopedPayload } from '../../utils'\nimport {\n BUILTIN_DICTIONARY_ROUTE_KINDS,\n dictionaryKindSchema,\n mapDictionaryKind,\n} from '../../dictionaries/context'\nimport { invalidateDictionaryCache } from '../../dictionaries/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n PATCH: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n cache?: CacheStrategy\n}\n\nconst dictionarySortModesPayloadSchema = z.object({\n dictionarySortModes: z.record(dictionaryKindSchema, dictionaryEntrySortModeSchema),\n})\n\nconst dictionarySortModesResponseSchema = z.object({\n dictionarySortModes: z.record(z.string(), dictionaryEntrySortModeSchema),\n})\n\nconst dictionarySortModesErrorSchema = z.object({\n error: z.string(),\n})\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, {\n error: translate('customers.errors.unauthorized', 'Unauthorized'),\n })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('customers.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n let cache: CacheStrategy | undefined\n try {\n cache = container.resolve('cache') as CacheStrategy\n } catch {}\n\n return {\n ctx,\n tenantId: auth.tenantId,\n organizationId,\n translate,\n em: container.resolve('em') as EntityManager,\n cache,\n }\n}\n\nfunction resolveActorId(ctx: CommandRuntimeContext): string {\n const auth = ctx.auth\n if (auth && typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (auth && typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (auth && typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nfunction normalizeDictionarySortModes(value: unknown): Partial<Record<string, DictionaryEntrySortMode>> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return {}\n const result: Partial<Record<string, DictionaryEntrySortMode>> = {}\n for (const [rawKind, rawMode] of Object.entries(value as Record<string, unknown>)) {\n const kind = dictionaryKindSchema.safeParse(rawKind)\n const mode = dictionaryEntrySortModeSchema.safeParse(rawMode)\n if (kind.success && mode.success) {\n result[kind.data] = mode.data\n }\n }\n return result\n}\n\nasync function invalidateCustomerDictionarySortCache(\n cache: CacheStrategy | undefined,\n tenantId: string,\n organizationId: string,\n dictionarySortModes: Partial<Record<string, DictionaryEntrySortMode>>,\n) {\n const routeKinds = new Set<string>([\n ...BUILTIN_DICTIONARY_ROUTE_KINDS,\n ...Object.keys(dictionarySortModes),\n ])\n for (const routeKind of routeKinds) {\n const parsed = dictionaryKindSchema.safeParse(routeKind)\n if (!parsed.success) continue\n const { mappedKind } = mapDictionaryKind(parsed.data)\n await invalidateDictionaryCache(cache, {\n tenantId,\n mappedKind,\n organizationIds: [organizationId],\n })\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, tenantId, organizationId } = await resolveSettingsContext(req)\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n return NextResponse.json({\n dictionarySortModes: normalizeDictionarySortModes(record?.dictionarySortModes),\n })\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.settings.dictionary-sort-modes.get failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.lookup_failed', 'Failed to load settings') },\n { status: 400 },\n )\n }\n}\n\nexport async function PATCH(req: Request) {\n try {\n const { ctx, tenantId, organizationId, translate, cache, em } = await resolveSettingsContext(req)\n const payload = dictionarySortModesPayloadSchema.parse(await readJsonSafe(req, {}))\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n const dictionarySortModes = {\n ...normalizeDictionarySortModes(record?.dictionarySortModes),\n ...payload.dictionarySortModes,\n }\n const scoped = withScopedPayload({ dictionarySortModes }, ctx, translate)\n const input = customerDictionarySortModesUpsertSchema.parse(scoped)\n const userId = resolveActorId(ctx)\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<\n CustomerDictionarySortModesUpsertInput,\n { settingsId: string; dictionarySortModes: CustomerDictionarySortModesUpsertInput['dictionarySortModes'] }\n >('customers.settings.save_dictionary_sort_modes', { input, ctx })\n\n await invalidateCustomerDictionarySortCache(cache, tenantId, organizationId, input.dictionarySortModes)\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n dictionarySortModes: normalizeDictionarySortModes(result?.dictionarySortModes ?? input.dictionarySortModes),\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('customers.settings.dictionary-sort-modes.patch failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.save_failed', 'Failed to save settings') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer dictionary entry sort modes',\n methods: {\n GET: {\n summary: 'Retrieve dictionary sort modes',\n description: 'Returns entry sort preferences for customer dictionaries in the selected organization.',\n responses: [\n { status: 200, description: 'Current dictionary sort modes', schema: dictionarySortModesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: dictionarySortModesErrorSchema },\n { status: 400, description: 'Organization context missing', schema: dictionarySortModesErrorSchema },\n ],\n },\n PATCH: {\n summary: 'Update dictionary sort modes',\n description: 'Updates entry sort preferences for customer dictionaries in the selected organization.',\n requestBody: {\n contentType: 'application/json',\n schema: dictionarySortModesPayloadSchema,\n },\n responses: [\n { status: 200, description: 'Updated dictionary sort modes', schema: dictionarySortModesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: dictionarySortModesErrorSchema },\n { status: 400, description: 'Invalid payload or organization context', schema: dictionarySortModesErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { loadCustomerSettings } from "../../../commands/settings.js";
|
|
17
17
|
import { withScopedPayload } from "../../utils.js";
|
|
18
18
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
19
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
19
20
|
const logger = createLogger("customers");
|
|
20
21
|
const DEFAULT_THRESHOLD = 14;
|
|
21
22
|
const metadata = {
|
|
@@ -120,6 +121,10 @@ async function PUT(req) {
|
|
|
120
121
|
if (isCrudHttpError(err)) {
|
|
121
122
|
return NextResponse.json(err.body, { status: err.status });
|
|
122
123
|
}
|
|
124
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
125
|
+
if (interceptorRejection) {
|
|
126
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
127
|
+
}
|
|
123
128
|
const { translate } = await resolveTranslations();
|
|
124
129
|
logger.error("customers.settings.stuck-threshold.put failed", { err });
|
|
125
130
|
return NextResponse.json(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/settings/stuck-threshold/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n customerStuckThresholdUpsertSchema,\n type CustomerStuckThresholdUpsertInput,\n} from '../../../data/validators'\nimport { loadCustomerSettings } from '../../../commands/settings'\nimport { withScopedPayload } from '../../utils'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nconst DEFAULT_THRESHOLD = 14\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, {\n error: translate('customers.errors.unauthorized', 'Unauthorized'),\n })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('customers.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = container.resolve('em') as EntityManager\n return {\n ctx,\n tenantId: auth.tenantId,\n organizationId,\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, tenantId, organizationId } = await resolveSettingsContext(req)\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n return NextResponse.json({\n stuckThresholdDays: record?.stuckThresholdDays ?? DEFAULT_THRESHOLD,\n })\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.settings.stuck-threshold.get failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.lookup_failed', 'Failed to load settings') },\n { status: 400 },\n )\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, tenantId, organizationId, translate } = await resolveSettingsContext(req)\n const payload = await readJsonSafe(req, {})\n const scoped = withScopedPayload(payload, ctx, translate)\n const input = customerStuckThresholdUpsertSchema.parse(scoped)\n\n // Mutation-guard contract for custom write routes. The resource is the customer\n // settings row scoped to the organization; we use the organizationId as the\n // resourceId because the (tenant, organization) pair uniquely identifies it.\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n // `resolveSettingsContext` throws 401 when auth is missing, so by the time we reach\n // here `ctx.auth.sub` is guaranteed to be a string per `AuthContext`.\n userId: ctx.auth!.sub,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<\n CustomerStuckThresholdUpsertInput,\n { settingsId: string; stuckThresholdDays: number }\n >('customers.settings.save_stuck_threshold', { input, ctx })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n // `resolveSettingsContext` throws 401 when auth is missing, so by the time we reach\n // here `ctx.auth.sub` is guaranteed to be a string per `AuthContext`.\n userId: ctx.auth!.sub,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n stuckThresholdDays: result?.stuckThresholdDays ?? input.stuckThresholdDays,\n })\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.settings.stuck-threshold.put failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.save_failed', 'Failed to save settings') },\n { status: 400 },\n )\n }\n}\n\nconst stuckThresholdResponseSchema = z.object({\n stuckThresholdDays: z.number(),\n})\n\nconst stuckThresholdErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer deals stuck-threshold setting',\n methods: {\n GET: {\n summary: 'Retrieve stuck-threshold days',\n description: 'Returns the current stuck-deal threshold (in days) for the selected organization.',\n responses: [\n { status: 200, description: 'Current threshold', schema: stuckThresholdResponseSchema },\n { status: 401, description: 'Unauthorized', schema: stuckThresholdErrorSchema },\n { status: 400, description: 'Organization context missing', schema: stuckThresholdErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update stuck-threshold days',\n description: 'Updates the stuck-deal threshold for the selected organization.',\n requestBody: {\n contentType: 'application/json',\n schema: customerStuckThresholdUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Updated threshold', schema: stuckThresholdResponseSchema },\n { status: 401, description: 'Unauthorized', schema: stuckThresholdErrorSchema },\n { status: 400, description: 'Invalid payload', schema: stuckThresholdErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAGnD,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,4BAA4B;AACrC,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\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 type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n customerStuckThresholdUpsertSchema,\n type CustomerStuckThresholdUpsertInput,\n} from '../../../data/validators'\nimport { loadCustomerSettings } from '../../../commands/settings'\nimport { withScopedPayload } from '../../utils'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('customers')\n\nconst DEFAULT_THRESHOLD = 14\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.deals.manage'] },\n}\n\ntype SettingsRouteContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n translate: (key: string, fallback?: string) => string\n em: EntityManager\n}\n\nasync function resolveSettingsContext(req: Request): Promise<SettingsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, {\n error: translate('customers.errors.unauthorized', 'Unauthorized'),\n })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('customers.errors.organization_required', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n const em = container.resolve('em') as EntityManager\n return {\n ctx,\n tenantId: auth.tenantId,\n organizationId,\n translate,\n em,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const { em, tenantId, organizationId } = await resolveSettingsContext(req)\n const record = await loadCustomerSettings(em, { tenantId, organizationId })\n return NextResponse.json({\n stuckThresholdDays: record?.stuckThresholdDays ?? DEFAULT_THRESHOLD,\n })\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.settings.stuck-threshold.get failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.lookup_failed', 'Failed to load settings') },\n { status: 400 },\n )\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx, tenantId, organizationId, translate } = await resolveSettingsContext(req)\n const payload = await readJsonSafe(req, {})\n const scoped = withScopedPayload(payload, ctx, translate)\n const input = customerStuckThresholdUpsertSchema.parse(scoped)\n\n // Mutation-guard contract for custom write routes. The resource is the customer\n // settings row scoped to the organization; we use the organizationId as the\n // resourceId because the (tenant, organization) pair uniquely identifies it.\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId,\n organizationId,\n // `resolveSettingsContext` throws 401 when auth is missing, so by the time we reach\n // here `ctx.auth.sub` is guaranteed to be a string per `AuthContext`.\n userId: ctx.auth!.sub,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result } = await commandBus.execute<\n CustomerStuckThresholdUpsertInput,\n { settingsId: string; stuckThresholdDays: number }\n >('customers.settings.save_stuck_threshold', { input, ctx })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId,\n organizationId,\n // `resolveSettingsContext` throws 401 when auth is missing, so by the time we reach\n // here `ctx.auth.sub` is guaranteed to be a string per `AuthContext`.\n userId: ctx.auth!.sub,\n resourceKind: 'customers.settings',\n resourceId: organizationId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n stuckThresholdDays: result?.stuckThresholdDays ?? input.stuckThresholdDays,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('customers.settings.stuck-threshold.put failed', { err })\n return NextResponse.json(\n { error: translate('customers.errors.save_failed', 'Failed to save settings') },\n { status: 400 },\n )\n }\n}\n\nconst stuckThresholdResponseSchema = z.object({\n stuckThresholdDays: z.number(),\n})\n\nconst stuckThresholdErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Customer deals stuck-threshold setting',\n methods: {\n GET: {\n summary: 'Retrieve stuck-threshold days',\n description: 'Returns the current stuck-deal threshold (in days) for the selected organization.',\n responses: [\n { status: 200, description: 'Current threshold', schema: stuckThresholdResponseSchema },\n { status: 401, description: 'Unauthorized', schema: stuckThresholdErrorSchema },\n { status: 400, description: 'Organization context missing', schema: stuckThresholdErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update stuck-threshold days',\n description: 'Updates the stuck-deal threshold for the selected organization.',\n requestBody: {\n contentType: 'application/json',\n schema: customerStuckThresholdUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Updated threshold', schema: stuckThresholdResponseSchema },\n { status: 401, description: 'Unauthorized', schema: stuckThresholdErrorSchema },\n { status: 400, description: 'Invalid payload', schema: stuckThresholdErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
validateCrudMutationGuard
|
|
14
14
|
} from "@open-mercato/shared/lib/crud/mutation-guard";
|
|
15
15
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
16
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
16
17
|
const logger = createLogger("customers");
|
|
17
18
|
const TAG_ASSIGNMENT_RESOURCE_KIND = "customers.tagAssignment";
|
|
18
19
|
const metadata = {
|
|
@@ -97,6 +98,10 @@ async function POST(req) {
|
|
|
97
98
|
if (isCrudHttpError(err)) {
|
|
98
99
|
return NextResponse.json(err.body, { status: err.status });
|
|
99
100
|
}
|
|
101
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
102
|
+
if (interceptorRejection) {
|
|
103
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
104
|
+
}
|
|
100
105
|
const { translate } = await resolveTranslations();
|
|
101
106
|
logger.error("customers.tags.assign failed", { err });
|
|
102
107
|
return NextResponse.json({ error: translate("customers.errors.assign_failed", "Failed to assign tag") }, { status: 400 });
|