@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/feature_toggles/api/overrides/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { getOverrides } from '../../lib/queries'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { FeatureToggleOverride } from '../../data/entities'\nimport { buildContext } from '../../lib/utils'\nimport { resolveFeatureCheckContext } from \"@open-mercato/core/modules/directory/utils/organizationScope\"\nimport { ProcessedChangeOverrideStateInput } from '../../data/validators'\nimport {\n changeOverrideStateBaseSchema,\n overrideListQuerySchema,\n featureTogglesTag,\n featureToggleOverrideListResponseSchema,\n featureToggleErrorSchema,\n changeOverrideStateResponseSchema,\n} from '../openapi'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('feature_toggles').child({ component: 'overrides' })\n\nexport async function GET(req: Request) {\n const { auth, ctx, scope } = await buildContext(req)\n\n const url = new URL(req.url)\n const parsed = overrideListQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n const em = ctx.container.resolve('em') as EntityManager\n\n const tenant = await em.findOne(Tenant, {\n id: scope.tenantId,\n })\n\n if (!tenant) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n const result = await getOverrides(em, tenant, parsed.data)\n\n await logCrudAccess({\n container: ctx.container,\n auth,\n request: req,\n items: [...result.raw.toggles, ...result.raw.overrides],\n idField: 'id',\n resourceKind: 'feature_toggles.override_list',\n tenantId: scope.tenantId ?? null,\n query: parsed.data,\n accessType: 'read:list',\n fields: ['id', 'toggleId', 'tenantId', 'identifier', 'name', 'category']\n })\n\n return NextResponse.json({\n items: result.items,\n total: result.total,\n totalPages: result.totalPages,\n page: result.page,\n pageSize: result.pageSize,\n isSuperAdmin: auth.isSuperAdmin ?? false\n })\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n\n const parsed = changeOverrideStateBaseSchema.safeParse(await req.json())\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const { scope, organizationId } = await resolveFeatureCheckContext({\n container: ctx.container,\n auth: ctx.auth,\n request: req\n })\n\n if (!scope.tenantId) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale override overwrite when two admins edit the\n // same global toggle override in parallel. Only enforced when an override row\n // already exists (first set has no prior version). Strictly additive.\n const em = ctx.container.resolve('em') as EntityManager\n const existingOverride = await em.findOne(FeatureToggleOverride, {\n tenantId: scope.tenantId,\n toggle: { id: parsed.data.toggleId },\n })\n if (existingOverride) {\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'feature_toggles.feature_toggle_override',\n resourceId: existingOverride.id,\n current: existingOverride.updatedAt ?? null,\n request: req,\n })\n }\n\n // Run the CRUD mutation guard lifecycle so this custom write route honors\n // module-level guard injections (and after-success hooks) like every other\n // mutating endpoint. The override either updates an existing row or creates a\n // new one, so the resource id falls back to the toggle id when no row exists.\n const guardUserId = ctx.auth?.userId ?? ctx.auth?.sub ?? null\n if (!guardUserId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const guardResourceKind = 'feature_toggles.feature_toggle_override'\n const guardResourceId = existingOverride?.id ?? parsed.data.toggleId\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\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<ProcessedChangeOverrideStateInput, { overrideToggleId: string | null }>('feature_toggles.overrides.changeState', {\n input: {\n toggleId: parsed.data.toggleId,\n tenantId: scope.tenantId,\n isOverride: parsed.data.isOverride,\n overrideValue: parsed.data.overrideValue,\n },\n ctx,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: result?.overrideToggleId ?? guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({\n ok: true,\n overrideToggleId: result?.overrideToggleId ?? null,\n })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'feature_toggles.override',\n resourceId: logEntry.resourceId ?? result?.overrideToggleId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n const message = error instanceof Error ? error.message : 'Unknown error'\n if (message === 'NOT_FOUND') return NextResponse.json({ error: 'Not found' }, { status: 404 })\n if (message === 'INVALID_STATE' || message === 'INVALID_TOGGLE_ID' || message === 'INVALID_TENANT_ID') {\n return NextResponse.json({ error: message }, { status: 400 })\n }\n logger.error('Override state change failed', { err: error })\n return NextResponse.json({ error: 'Failed to update override' }, { status: 500 })\n }\n}\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst overrideGetDoc: OpenApiMethodDoc = {\n summary: 'List overrides',\n description: 'Returns list of feature toggle overrides.',\n tags: [featureTogglesTag],\n query: overrideListQuerySchema,\n responses: [\n { status: 200, description: 'List of overrides', schema: featureToggleOverrideListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n ],\n}\n\nconst overridePutDoc: OpenApiMethodDoc = {\n summary: 'Change override state',\n description: 'Enable, disable or inherit a feature toggle for a specific tenant.',\n tags: [featureTogglesTag],\n requestBody: {\n contentType: 'application/json',\n schema: changeOverrideStateBaseSchema,\n description: 'Override details.',\n },\n responses: [\n { status: 200, description: 'Override updated', schema: changeOverrideStateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Not found', schema: featureToggleErrorSchema },\n { status: 500, description: 'Internal server error', schema: featureToggleErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Manage feature toggle overrides',\n methods: {\n GET: overrideGetDoc,\n PUT: overridePutDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,cAAc;AAEvB,SAAS,kCAAkC;AAC3C,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,8CAA8C;AACvD;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAC7B,SAAS,kCAAkC;AAE3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { getOverrides } from '../../lib/queries'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { FeatureToggleOverride } from '../../data/entities'\nimport { buildContext } from '../../lib/utils'\nimport { resolveFeatureCheckContext } from \"@open-mercato/core/modules/directory/utils/organizationScope\"\nimport { ProcessedChangeOverrideStateInput } from '../../data/validators'\nimport {\n changeOverrideStateBaseSchema,\n overrideListQuerySchema,\n featureTogglesTag,\n featureToggleOverrideListResponseSchema,\n featureToggleErrorSchema,\n changeOverrideStateResponseSchema,\n} from '../openapi'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } 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('feature_toggles').child({ component: 'overrides' })\n\nexport async function GET(req: Request) {\n const { auth, ctx, scope } = await buildContext(req)\n\n const url = new URL(req.url)\n const parsed = overrideListQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n const em = ctx.container.resolve('em') as EntityManager\n\n const tenant = await em.findOne(Tenant, {\n id: scope.tenantId,\n })\n\n if (!tenant) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n const result = await getOverrides(em, tenant, parsed.data)\n\n await logCrudAccess({\n container: ctx.container,\n auth,\n request: req,\n items: [...result.raw.toggles, ...result.raw.overrides],\n idField: 'id',\n resourceKind: 'feature_toggles.override_list',\n tenantId: scope.tenantId ?? null,\n query: parsed.data,\n accessType: 'read:list',\n fields: ['id', 'toggleId', 'tenantId', 'identifier', 'name', 'category']\n })\n\n return NextResponse.json({\n items: result.items,\n total: result.total,\n totalPages: result.totalPages,\n page: result.page,\n pageSize: result.pageSize,\n isSuperAdmin: auth.isSuperAdmin ?? false\n })\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n\n const parsed = changeOverrideStateBaseSchema.safeParse(await req.json())\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const { scope, organizationId } = await resolveFeatureCheckContext({\n container: ctx.container,\n auth: ctx.auth,\n request: req\n })\n\n if (!scope.tenantId) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale override overwrite when two admins edit the\n // same global toggle override in parallel. Only enforced when an override row\n // already exists (first set has no prior version). Strictly additive.\n const em = ctx.container.resolve('em') as EntityManager\n const existingOverride = await em.findOne(FeatureToggleOverride, {\n tenantId: scope.tenantId,\n toggle: { id: parsed.data.toggleId },\n })\n if (existingOverride) {\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'feature_toggles.feature_toggle_override',\n resourceId: existingOverride.id,\n current: existingOverride.updatedAt ?? null,\n request: req,\n })\n }\n\n // Run the CRUD mutation guard lifecycle so this custom write route honors\n // module-level guard injections (and after-success hooks) like every other\n // mutating endpoint. The override either updates an existing row or creates a\n // new one, so the resource id falls back to the toggle id when no row exists.\n const guardUserId = ctx.auth?.userId ?? ctx.auth?.sub ?? null\n if (!guardUserId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const guardResourceKind = 'feature_toggles.feature_toggle_override'\n const guardResourceId = existingOverride?.id ?? parsed.data.toggleId\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\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<ProcessedChangeOverrideStateInput, { overrideToggleId: string | null }>('feature_toggles.overrides.changeState', {\n input: {\n toggleId: parsed.data.toggleId,\n tenantId: scope.tenantId,\n isOverride: parsed.data.isOverride,\n overrideValue: parsed.data.overrideValue,\n },\n ctx,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: result?.overrideToggleId ?? guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({\n ok: true,\n overrideToggleId: result?.overrideToggleId ?? null,\n })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'feature_toggles.override',\n resourceId: logEntry.resourceId ?? result?.overrideToggleId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const message = error instanceof Error ? error.message : 'Unknown error'\n if (message === 'NOT_FOUND') return NextResponse.json({ error: 'Not found' }, { status: 404 })\n if (message === 'INVALID_STATE' || message === 'INVALID_TOGGLE_ID' || message === 'INVALID_TENANT_ID') {\n return NextResponse.json({ error: message }, { status: 400 })\n }\n logger.error('Override state change failed', { err: error })\n return NextResponse.json({ error: 'Failed to update override' }, { status: 500 })\n }\n}\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst overrideGetDoc: OpenApiMethodDoc = {\n summary: 'List overrides',\n description: 'Returns list of feature toggle overrides.',\n tags: [featureTogglesTag],\n query: overrideListQuerySchema,\n responses: [\n { status: 200, description: 'List of overrides', schema: featureToggleOverrideListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n ],\n}\n\nconst overridePutDoc: OpenApiMethodDoc = {\n summary: 'Change override state',\n description: 'Enable, disable or inherit a feature toggle for a specific tenant.',\n tags: [featureTogglesTag],\n requestBody: {\n contentType: 'application/json',\n schema: changeOverrideStateBaseSchema,\n description: 'Override details.',\n },\n responses: [\n { status: 200, description: 'Override updated', schema: changeOverrideStateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Not found', schema: featureToggleErrorSchema },\n { status: 500, description: 'Internal server error', schema: featureToggleErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Manage feature toggle overrides',\n methods: {\n GET: overrideGetDoc,\n PUT: overridePutDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,7 @@ import { attachOperationMetadataHeader } from "../../../../lib/operationMetadata
|
|
|
4
4
|
import { resolveMessageContext } from "../../../../lib/routeHelpers.js";
|
|
5
5
|
import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "../../../guards.js";
|
|
6
6
|
import { actionResultResponseSchema } from "../../../openapi.js";
|
|
7
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
7
8
|
const metadata = {
|
|
8
9
|
POST: { requireAuth: true, requireFeatures: ["messages.actions"] }
|
|
9
10
|
};
|
|
@@ -77,6 +78,10 @@ async function POST(req, { params }) {
|
|
|
77
78
|
if (isCrudHttpError(error)) {
|
|
78
79
|
return Response.json(error.body, { status: error.status });
|
|
79
80
|
}
|
|
81
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
82
|
+
if (interceptorRejection) {
|
|
83
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
84
|
+
}
|
|
80
85
|
if (error instanceof Error) {
|
|
81
86
|
if (error.message === "Message not found") {
|
|
82
87
|
return Response.json({ error: "Message not found" }, { status: 404 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/messages/api/%5Bid%5D/actions/%5BactionId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { attachOperationMetadataHeader, type OperationLogEntryLike } from '../../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../../guards'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { actionResultResponseSchema } from '../../../openapi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.actions'] },\n}\n\nexport async function POST(\n req: Request,\n { params }: { params: { id: string; actionId: string } }\n) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const rawBody = await req.json().catch(() => ({}))\n const body = (typeof rawBody === 'object' && rawBody && !Array.isArray(rawBody)\n ? rawBody\n : {}) as Record<string, unknown>\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: body,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const commandResult = await commandBus.execute('messages.actions.execute', {\n input: {\n messageId: params.id,\n actionId: params.actionId,\n payload: body,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const result = commandResult.result as {\n ok: boolean\n actionId: string\n result: Record<string, unknown>\n operationLogEntry?: OperationLogEntryLike | null\n }\n const response = Response.json({\n ok: result.ok,\n actionId: result.actionId,\n result: result.result,\n })\n attachOperationMetadataHeader(response, result.operationLogEntry ?? null, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Action not found') {\n return Response.json({ error: 'Action not found' }, { status: 404 })\n }\n if (error.message === 'Action command is not allowed') {\n return Response.json({ error: 'Action command is not allowed' }, { status: 403 })\n }\n if (error.message === 'Action already taken') {\n const actionTaken = (error as Error & { actionTaken?: string }).actionTaken ?? null\n return Response.json({ error: 'Action already taken', actionTaken }, { status: 409 })\n }\n if (error.message === 'Actions have expired') {\n return Response.json({ error: 'Actions have expired' }, { status: 410 })\n }\n if (error.message === 'Action has no executable target') {\n return Response.json({ error: 'Action has no executable target' }, { status: 409 })\n }\n if (error.message === 'Action failed') {\n return Response.json({ error: 'Action failed' }, { status: 500 })\n }\n }\n throw error\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Execute message action',\n requestBody: { schema: z.record(z.string(), z.unknown()).optional() },\n responses: [\n { status: 200, description: 'Action executed', schema: actionResultResponseSchema },\n { status: 403, description: 'Access denied' },\n { status: 404, description: 'Action not found' },\n { status: 409, description: 'Action already taken, or the message was modified concurrently (optimistic lock conflict)' },\n { status: 410, description: 'Action expired' },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAChC,SAAS,qCAAiE;AAC1E,SAAS,6BAA6B;AACtC,SAAS,qBAAqB,qCAAqC,gCAAgC;AAEnG,SAAS,kCAAkC;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { attachOperationMetadataHeader, type OperationLogEntryLike } from '../../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../../guards'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { actionResultResponseSchema } from '../../../openapi'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.actions'] },\n}\n\nexport async function POST(\n req: Request,\n { params }: { params: { id: string; actionId: string } }\n) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const rawBody = await req.json().catch(() => ({}))\n const body = (typeof rawBody === 'object' && rawBody && !Array.isArray(rawBody)\n ? rawBody\n : {}) as Record<string, unknown>\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: body,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const commandResult = await commandBus.execute('messages.actions.execute', {\n input: {\n messageId: params.id,\n actionId: params.actionId,\n payload: body,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const result = commandResult.result as {\n ok: boolean\n actionId: string\n result: Record<string, unknown>\n operationLogEntry?: OperationLogEntryLike | null\n }\n const response = Response.json({\n ok: result.ok,\n actionId: result.actionId,\n result: result.result,\n })\n attachOperationMetadataHeader(response, result.operationLogEntry ?? null, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return Response.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Action not found') {\n return Response.json({ error: 'Action not found' }, { status: 404 })\n }\n if (error.message === 'Action command is not allowed') {\n return Response.json({ error: 'Action command is not allowed' }, { status: 403 })\n }\n if (error.message === 'Action already taken') {\n const actionTaken = (error as Error & { actionTaken?: string }).actionTaken ?? null\n return Response.json({ error: 'Action already taken', actionTaken }, { status: 409 })\n }\n if (error.message === 'Actions have expired') {\n return Response.json({ error: 'Actions have expired' }, { status: 410 })\n }\n if (error.message === 'Action has no executable target') {\n return Response.json({ error: 'Action has no executable target' }, { status: 409 })\n }\n if (error.message === 'Action failed') {\n return Response.json({ error: 'Action failed' }, { status: 500 })\n }\n }\n throw error\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Execute message action',\n requestBody: { schema: z.record(z.string(), z.unknown()).optional() },\n responses: [\n { status: 200, description: 'Action executed', schema: actionResultResponseSchema },\n { status: 403, description: 'Access denied' },\n { status: 404, description: 'Action not found' },\n { status: 409, description: 'Action already taken, or the message was modified concurrently (optimistic lock conflict)' },\n { status: 410, description: 'Action expired' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
conversationMutationResponseSchema,
|
|
6
6
|
errorResponseSchema
|
|
7
7
|
} from "../../../openapi.js";
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
8
9
|
const metadata = {
|
|
9
10
|
PUT: { requireAuth: true, requireFeatures: ["messages.view"] },
|
|
10
11
|
DELETE: { requireAuth: true, requireFeatures: ["messages.view"] }
|
|
@@ -67,6 +68,10 @@ async function runConversationArchiveMutation(req, id, commandId) {
|
|
|
67
68
|
});
|
|
68
69
|
return response;
|
|
69
70
|
} catch (error) {
|
|
71
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
72
|
+
if (interceptorRejection) {
|
|
73
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
74
|
+
}
|
|
70
75
|
if (error instanceof Error) {
|
|
71
76
|
if (error.message === "Message not found") {
|
|
72
77
|
return Response.json({ error: error.message }, { status: 404 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/messages/api/%5Bid%5D/conversation/archive/route.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../../guards'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../../openapi'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['messages.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nasync function runConversationArchiveMutation(\n req: Request,\n id: string,\n commandId: 'messages.conversation.archive_for_actor' | 'messages.conversation.unarchive_for_actor',\n) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const { result, logEntry } = await commandBus.execute(commandId, {\n input: {\n anchorMessageId: id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n return runConversationArchiveMutation(req, params.id, 'messages.conversation.archive_for_actor')\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n return runConversationArchiveMutation(req, params.id, 'messages.conversation.unarchive_for_actor')\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n PUT: {\n summary: 'Archive conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation archived', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Unarchive conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation unarchived', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,qCAAqC;AAC9C,SAAS,6BAA6B;AACtC,SAAS,qBAAqB,qCAAqC,gCAAgC;AACnG;AAAA,EACE;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../../guards'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../../openapi'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['messages.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nasync function runConversationArchiveMutation(\n req: Request,\n id: string,\n commandId: 'messages.conversation.archive_for_actor' | 'messages.conversation.unarchive_for_actor',\n) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const { result, logEntry } = await commandBus.execute(commandId, {\n input: {\n anchorMessageId: id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return Response.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n return runConversationArchiveMutation(req, params.id, 'messages.conversation.archive_for_actor')\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n return runConversationArchiveMutation(req, params.id, 'messages.conversation.unarchive_for_actor')\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n PUT: {\n summary: 'Archive conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation archived', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Unarchive conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation unarchived', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
conversationMutationResponseSchema,
|
|
6
6
|
errorResponseSchema
|
|
7
7
|
} from "../../../openapi.js";
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
8
9
|
const metadata = {
|
|
9
10
|
PUT: { requireAuth: true, requireFeatures: ["messages.view"] },
|
|
10
11
|
DELETE: { requireAuth: true, requireFeatures: ["messages.view"] }
|
|
@@ -67,6 +68,10 @@ async function runConversationReadMutation(req, id, commandId) {
|
|
|
67
68
|
});
|
|
68
69
|
return response;
|
|
69
70
|
} catch (error) {
|
|
71
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
72
|
+
if (interceptorRejection) {
|
|
73
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
74
|
+
}
|
|
70
75
|
if (error instanceof Error) {
|
|
71
76
|
if (error.message === "Message not found") {
|
|
72
77
|
return Response.json({ error: error.message }, { status: 404 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/messages/api/%5Bid%5D/conversation/read/route.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../../guards'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../../openapi'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['messages.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nasync function runConversationReadMutation(\n req: Request,\n id: string,\n commandId: 'messages.conversation.mark_read_for_actor' | 'messages.conversation.mark_unread_for_actor',\n) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const { result, logEntry } = await commandBus.execute(commandId, {\n input: {\n anchorMessageId: id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n return runConversationReadMutation(req, params.id, 'messages.conversation.mark_read_for_actor')\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n return runConversationReadMutation(req, params.id, 'messages.conversation.mark_unread_for_actor')\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n PUT: {\n summary: 'Mark entire conversation as read for current actor',\n responses: [\n { status: 200, description: 'Conversation marked read', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Mark entire conversation as unread for current actor',\n responses: [\n { status: 200, description: 'Conversation marked unread', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,qCAAqC;AAC9C,SAAS,6BAA6B;AACtC,SAAS,qBAAqB,qCAAqC,gCAAgC;AACnG;AAAA,EACE;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../../guards'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../../openapi'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['messages.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nasync function runConversationReadMutation(\n req: Request,\n id: string,\n commandId: 'messages.conversation.mark_read_for_actor' | 'messages.conversation.mark_unread_for_actor',\n) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const { result, logEntry } = await commandBus.execute(commandId, {\n input: {\n anchorMessageId: id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return Response.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n return runConversationReadMutation(req, params.id, 'messages.conversation.mark_read_for_actor')\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n return runConversationReadMutation(req, params.id, 'messages.conversation.mark_unread_for_actor')\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n PUT: {\n summary: 'Mark entire conversation as read for current actor',\n responses: [\n { status: 200, description: 'Conversation marked read', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Mark entire conversation as unread for current actor',\n responses: [\n { status: 200, description: 'Conversation marked unread', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
conversationMutationResponseSchema,
|
|
6
6
|
errorResponseSchema
|
|
7
7
|
} from "../../openapi.js";
|
|
8
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
8
9
|
const metadata = {
|
|
9
10
|
DELETE: { requireAuth: true, requireFeatures: ["messages.view"] }
|
|
10
11
|
};
|
|
@@ -66,6 +67,10 @@ async function DELETE(req, { params }) {
|
|
|
66
67
|
});
|
|
67
68
|
return response;
|
|
68
69
|
} catch (error) {
|
|
70
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
71
|
+
if (interceptorRejection) {
|
|
72
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
73
|
+
}
|
|
69
74
|
if (error instanceof Error) {
|
|
70
75
|
if (error.message === "Message not found") {
|
|
71
76
|
return Response.json({ error: error.message }, { status: 404 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/api/%5Bid%5D/conversation/route.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../openapi'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const { result, logEntry } = await commandBus.execute('messages.conversation.delete_for_actor', {\n input: {\n anchorMessageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n DELETE: {\n summary: 'Delete conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation deleted', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,qCAAqC;AAC9C,SAAS,6BAA6B;AACtC,SAAS,qBAAqB,qCAAqC,gCAAgC;AACnG;AAAA,EACE;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../openapi'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const { result, logEntry } = await commandBus.execute('messages.conversation.delete_for_actor', {\n input: {\n anchorMessageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return Response.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n DELETE: {\n summary: 'Delete conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation deleted', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,7 @@ import { attachOperationMetadataHeader } from "../../../lib/operationMetadata.js
|
|
|
3
3
|
import { canUseMessageEmailFeature, parseRequestBodySafe, resolveMessageContext } from "../../../lib/routeHelpers.js";
|
|
4
4
|
import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "../../guards.js";
|
|
5
5
|
import { forwardResponseSchema, forwardMessageSchema as forwardSchema } from "../../openapi.js";
|
|
6
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
6
7
|
const metadata = {
|
|
7
8
|
POST: { requireAuth: true, requireFeatures: ["messages.compose"] }
|
|
8
9
|
};
|
|
@@ -55,6 +56,10 @@ async function POST(req, { params }) {
|
|
|
55
56
|
}
|
|
56
57
|
});
|
|
57
58
|
} catch (error) {
|
|
59
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
60
|
+
if (interceptorRejection) {
|
|
61
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
62
|
+
}
|
|
58
63
|
if (error instanceof Error) {
|
|
59
64
|
if (error.message === "Message not found") {
|
|
60
65
|
return Response.json({ error: "Message not found" }, { status: 404 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/api/%5Bid%5D/forward/route.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { forwardMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader, OperationLogEntryLike } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, parseRequestBodySafe, resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { forwardResponseSchema, forwardMessageSchema as forwardSchema } from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await parseRequestBodySafe(req)\n const input = forwardMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n let commandResult: { result: MessageCommandExecuteResult; logEntry: unknown }\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.forward', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Forward is not allowed for this message type') {\n return Response.json({ error: 'Forward is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'Forward body exceeds maximum length') {\n return Response.json({ error: 'Forward body exceeds maximum length' }, { status: 413 })\n }\n }\n throw error\n }\n const newMessageId = commandResult.result.id\n\n const response = Response.json({ id: newMessageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry as OperationLogEntryLike, {\n resourceKind: 'messages.message',\n resourceId: newMessageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: newMessageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Forward a message and optionally include attachments from the forwarded conversation slice',\n requestBody: { schema: forwardSchema },\n responses: [\n {\n status: 201,\n description: 'Message forwarded',\n schema: forwardResponseSchema,\n },\n { status: 403, description: 'Access denied' },\n { status: 404, description: 'Message not found' },\n { status: 409, description: 'Forward not allowed for message type' },\n { status: 413, description: 'Forward body exceeds maximum length' },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,4BAA4B;AACrC,SAAS,qCAA4D;AACrE,SAAS,2BAA2B,sBAAsB,6BAA6B;AACvF,SAAS,qBAAqB,qCAAqC,gCAAgC;AAEnG,SAAS,uBAAuB,wBAAwB,qBAAqB;
|
|
4
|
+
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { forwardMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader, OperationLogEntryLike } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, parseRequestBodySafe, resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { forwardResponseSchema, forwardMessageSchema as forwardSchema } from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await parseRequestBodySafe(req)\n const input = forwardMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n let commandResult: { result: MessageCommandExecuteResult; logEntry: unknown }\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.forward', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return Response.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Forward is not allowed for this message type') {\n return Response.json({ error: 'Forward is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'Forward body exceeds maximum length') {\n return Response.json({ error: 'Forward body exceeds maximum length' }, { status: 413 })\n }\n }\n throw error\n }\n const newMessageId = commandResult.result.id\n\n const response = Response.json({ id: newMessageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry as OperationLogEntryLike, {\n resourceKind: 'messages.message',\n resourceId: newMessageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: newMessageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Forward a message and optionally include attachments from the forwarded conversation slice',\n requestBody: { schema: forwardSchema },\n responses: [\n {\n status: 201,\n description: 'Message forwarded',\n schema: forwardResponseSchema,\n },\n { status: 403, description: 'Access denied' },\n { status: 404, description: 'Message not found' },\n { status: 409, description: 'Forward not allowed for message type' },\n { status: 413, description: 'Forward body exceeds maximum length' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
forwardResponseSchema,
|
|
8
8
|
replyMessageSchema as replyOpenApiSchema
|
|
9
9
|
} from "../../openapi.js";
|
|
10
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
10
11
|
const metadata = {
|
|
11
12
|
POST: { requireAuth: true, requireFeatures: ["messages.compose"] }
|
|
12
13
|
};
|
|
@@ -59,6 +60,10 @@ async function POST(req, { params }) {
|
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
62
|
} catch (error) {
|
|
63
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
64
|
+
if (interceptorRejection) {
|
|
65
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
66
|
+
}
|
|
62
67
|
if (error instanceof Error) {
|
|
63
68
|
if (error.message === "Message not found") {
|
|
64
69
|
return Response.json({ error: "Message not found" }, { status: 404 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/api/%5Bid%5D/reply/route.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { replyMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport {\n errorResponseSchema,\n forwardResponseSchema,\n replyMessageSchema as replyOpenApiSchema,\n} from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n const input = replyMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n let commandResult\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.reply', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Reply is not allowed for this message type') {\n return Response.json({ error: 'Reply is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'No recipients available for reply') {\n return Response.json({ error: 'No recipients available for reply' }, { status: 409 })\n }\n }\n throw error\n }\n const messageId = commandResult.result.id\n\n const response = Response.json({ id: messageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry, {\n resourceKind: 'messages.message',\n resourceId: messageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: messageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Reply to message',\n requestBody: { schema: replyOpenApiSchema },\n responses: [\n { status: 201, description: 'Reply created', schema: forwardResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Reply not allowed for message type', schema: errorResponseSchema },\n { status: 409, description: 'No recipients available for reply', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,0BAA0B;AACnC,SAAS,qCAAqC;AAC9C,SAAS,2BAA2B,6BAA6B;AACjE,SAAS,qBAAqB,qCAAqC,gCAAgC;AACnG;AAAA,EACE;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,OACjB;
|
|
4
|
+
"sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { replyMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport {\n errorResponseSchema,\n forwardResponseSchema,\n replyMessageSchema as replyOpenApiSchema,\n} from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n const input = replyMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n let commandResult\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.reply', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return Response.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Reply is not allowed for this message type') {\n return Response.json({ error: 'Reply is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'No recipients available for reply') {\n return Response.json({ error: 'No recipients available for reply' }, { status: 409 })\n }\n }\n throw error\n }\n const messageId = commandResult.result.id\n\n const response = Response.json({ id: messageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry, {\n resourceKind: 'messages.message',\n resourceId: messageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: messageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Reply to message',\n requestBody: { schema: replyOpenApiSchema },\n responses: [\n { status: 201, description: 'Reply created', schema: forwardResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Reply not allowed for message type', schema: errorResponseSchema },\n { status: 409, description: 'No recipients available for reply', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
updateDraftSchema as updateDraftOpenApiSchema
|
|
20
20
|
} from "../openapi.js";
|
|
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("messages").child({ component: "api" });
|
|
23
24
|
const metadata = {
|
|
24
25
|
GET: { requireAuth: true },
|
|
@@ -336,6 +337,10 @@ async function PATCH(req, { params }) {
|
|
|
336
337
|
if (isCrudHttpError(error)) {
|
|
337
338
|
return Response.json(error.body, { status: error.status });
|
|
338
339
|
}
|
|
340
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
341
|
+
if (interceptorRejection) {
|
|
342
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
343
|
+
}
|
|
339
344
|
if (error instanceof Error) {
|
|
340
345
|
if (error.message === "Message type cannot be created by users") {
|
|
341
346
|
return Response.json({ error: error.message }, { status: 400 });
|
|
@@ -444,6 +449,10 @@ async function DELETE(req, { params }) {
|
|
|
444
449
|
if (isCrudHttpError(error)) {
|
|
445
450
|
return Response.json(error.body, { status: error.status });
|
|
446
451
|
}
|
|
452
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(error);
|
|
453
|
+
if (interceptorRejection) {
|
|
454
|
+
return Response.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
455
|
+
}
|
|
447
456
|
if (error instanceof Error && error.message === "Access denied") {
|
|
448
457
|
return Response.json({ error: "Access denied" }, { status: 403 });
|
|
449
458
|
}
|