@open-mercato/core 0.6.6-develop.6229.1.ebe6706732 → 0.6.6-develop.6241.1.47c01ad4ed
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/commands/users.js +1 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/communication_channels/data/enrichers.js +9 -1
- package/dist/modules/communication_channels/data/enrichers.js.map +2 -2
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +3 -5
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +2 -2
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +39 -1
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/route.js +107 -12
- package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
- package/dist/modules/customers/api/pipelines/route.js +107 -12
- package/dist/modules/customers/api/pipelines/route.js.map +2 -2
- package/dist/modules/customers/api/settings/address-format/route.js +33 -1
- package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
- package/dist/modules/customers/api/tags/assign/route.js +37 -0
- package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
- package/dist/modules/customers/api/tags/unassign/route.js +37 -0
- package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
- package/dist/modules/entities/api/encryption.js +109 -38
- package/dist/modules/entities/api/encryption.js.map +2 -2
- package/dist/modules/entities/components/EncryptionManager.js +22 -9
- package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
- package/dist/modules/integrations/api/[id]/credentials/route.js +23 -1
- package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/route.js +10 -3
- package/dist/modules/integrations/api/[id]/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/state/route.js +20 -5
- package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/version/route.js +17 -1
- package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
- package/dist/modules/integrations/api/route.js +2 -0
- package/dist/modules/integrations/api/route.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +36 -19
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +13 -9
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/page.js +4 -3
- package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
- package/dist/modules/integrations/data/entities.js +2 -2
- package/dist/modules/integrations/data/entities.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-service.js +32 -0
- package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
- package/dist/modules/integrations/lib/state-service.js +4 -2
- package/dist/modules/integrations/lib/state-service.js.map +2 -2
- package/dist/modules/notifications/lib/notificationService.js +42 -10
- package/dist/modules/notifications/lib/notificationService.js.map +2 -2
- package/dist/modules/payment_gateways/api/status/route.js +91 -6
- package/dist/modules/payment_gateways/api/status/route.js.map +2 -2
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +25 -11
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +25 -7
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +25 -7
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js +52 -24
- package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
- package/dist/modules/sales/components/DocumentNumberSettings.js +26 -9
- package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
- package/dist/modules/sales/components/OrderEditingSettings.js +26 -9
- package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
- package/dist/modules/sales/components/StatusSettings.js +66 -31
- package/dist/modules/sales/components/StatusSettings.js.map +2 -2
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +25 -7
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js +38 -0
- package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +12 -5
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +7 -8
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/timerErrors.js +12 -0
- package/dist/modules/staff/lib/timesheets-ui/timerErrors.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +4 -3
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/page.js +19 -10
- package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/commands/users.ts +1 -0
- package/src/modules/communication_channels/data/enrichers.ts +25 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +7 -6
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +5 -2
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +41 -1
- package/src/modules/customers/api/pipeline-stages/route.ts +112 -13
- package/src/modules/customers/api/pipelines/route.ts +112 -13
- package/src/modules/customers/api/settings/address-format/route.ts +36 -1
- package/src/modules/customers/api/tags/assign/route.ts +39 -0
- package/src/modules/customers/api/tags/unassign/route.ts +39 -0
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/entities/api/encryption.ts +122 -39
- package/src/modules/entities/components/EncryptionManager.tsx +28 -9
- package/src/modules/integrations/api/[id]/credentials/route.ts +23 -0
- package/src/modules/integrations/api/[id]/route.ts +8 -1
- package/src/modules/integrations/api/[id]/state/route.ts +20 -4
- package/src/modules/integrations/api/[id]/version/route.ts +18 -1
- package/src/modules/integrations/api/route.ts +3 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +39 -20
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +19 -11
- package/src/modules/integrations/backend/integrations/page.tsx +8 -5
- package/src/modules/integrations/data/entities.ts +2 -2
- package/src/modules/integrations/lib/credentials-service.ts +35 -0
- package/src/modules/integrations/lib/state-service.ts +3 -0
- package/src/modules/notifications/lib/notificationService.ts +74 -11
- package/src/modules/payment_gateways/api/status/route.ts +97 -5
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +27 -11
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +31 -7
- package/src/modules/sales/backend/sales/channels/page.tsx +31 -7
- package/src/modules/sales/components/AdjustmentKindSettings.tsx +60 -24
- package/src/modules/sales/components/DocumentNumberSettings.tsx +32 -10
- package/src/modules/sales/components/OrderEditingSettings.tsx +32 -10
- package/src/modules/sales/components/StatusSettings.tsx +74 -33
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +30 -6
- package/src/modules/shipping_carriers/api/cancel/route.ts +46 -0
- package/src/modules/shipping_carriers/api/shipments/route.ts +21 -6
- package/src/modules/staff/i18n/de.json +5 -5
- package/src/modules/staff/i18n/es.json +5 -5
- package/src/modules/staff/i18n/pl.json +5 -5
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +7 -8
- package/src/modules/staff/lib/timesheets-ui/timerErrors.ts +23 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +4 -3
- package/src/modules/workflows/backend/definitions/page.tsx +19 -10
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
|
|
5
5
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
|
-
import { sanitizeChannelHtml } from '../../../lib/sanitize-channel-html'
|
|
7
6
|
|
|
8
7
|
type ChannelPayloadEnrichment = {
|
|
9
8
|
channelContentType: string | null
|
|
10
9
|
channelPayload: Record<string, unknown> | null
|
|
11
10
|
interactiveState: Record<string, unknown> | null
|
|
12
11
|
channelMetadata: Record<string, unknown> | null
|
|
12
|
+
// Email HTML is sanitized server-side by the channel-payload enricher and
|
|
13
|
+
// delivered here ready to render — the client never imports `sanitize-html`.
|
|
14
|
+
sanitizedHtml: string | null
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
type MessageWithPayload = Record<string, unknown> & {
|
|
@@ -24,11 +26,10 @@ export default function ChannelPayloadRendererWidget({
|
|
|
24
26
|
const payload = data?._channelPayload ?? null
|
|
25
27
|
if (!payload || !payload.channelContentType) return null
|
|
26
28
|
|
|
27
|
-
const { channelContentType, channelPayload } = payload
|
|
29
|
+
const { channelContentType, channelPayload, sanitizedHtml } = payload
|
|
28
30
|
|
|
29
|
-
// Email / HTML —
|
|
30
|
-
if (channelContentType.startsWith('email/') && typeof
|
|
31
|
-
const sanitized = sanitizeChannelHtml(channelPayload.html as string)
|
|
31
|
+
// Email / HTML — render the HTML the server already sanitized.
|
|
32
|
+
if (channelContentType.startsWith('email/') && typeof sanitizedHtml === 'string') {
|
|
32
33
|
return (
|
|
33
34
|
<section
|
|
34
35
|
className="rounded-md border bg-card p-4 text-sm"
|
|
@@ -37,7 +38,7 @@ export default function ChannelPayloadRendererWidget({
|
|
|
37
38
|
'Channel payload — email',
|
|
38
39
|
)}
|
|
39
40
|
>
|
|
40
|
-
<div dangerouslySetInnerHTML={{ __html:
|
|
41
|
+
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
|
|
41
42
|
</section>
|
|
42
43
|
)
|
|
43
44
|
}
|
package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts
CHANGED
|
@@ -6,8 +6,11 @@ import ChannelPayloadRendererWidget from './widget.client'
|
|
|
6
6
|
* (Slack Block Kit, WhatsApp interactive, email MIME, etc.) below the body in the
|
|
7
7
|
* messages detail page (`detail:messages:message:body:after`).
|
|
8
8
|
*
|
|
9
|
-
* For `email/*` content types, HTML is sanitized
|
|
10
|
-
*
|
|
9
|
+
* For `email/*` content types, HTML is sanitized server-side by the
|
|
10
|
+
* channel-payload enricher (`data/enrichers.ts`, via `sanitizeChannelHtml`) and
|
|
11
|
+
* delivered as `_channelPayload.sanitizedHtml`, which this widget renders with
|
|
12
|
+
* `dangerouslySetInnerHTML` — keeping `sanitize-html` off the client bundle.
|
|
13
|
+
* See SPEC-045d §4.6.
|
|
11
14
|
*
|
|
12
15
|
* Provider packages override this via UMES component replacement (handle
|
|
13
16
|
* `widget:communication_channels.injection.channel-payload-renderer`) to render
|
|
@@ -8,8 +8,14 @@ import { pipelineStageReorderSchema, type PipelineStageReorderInput } from '../.
|
|
|
8
8
|
import { withScopedPayload } from '../../utils'
|
|
9
9
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
10
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
|
+
import {
|
|
12
|
+
runCrudMutationGuardAfterSuccess,
|
|
13
|
+
validateCrudMutationGuard,
|
|
14
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
11
15
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
16
|
|
|
17
|
+
const PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'
|
|
18
|
+
|
|
13
19
|
export const metadata = {
|
|
14
20
|
POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
|
|
15
21
|
}
|
|
@@ -29,15 +35,49 @@ export async function POST(req: Request) {
|
|
|
29
35
|
organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
|
|
30
36
|
request: req,
|
|
31
37
|
}
|
|
38
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
39
|
+
const tenantId = auth.tenantId ?? null
|
|
40
|
+
if (!organizationId || !tenantId) {
|
|
41
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
42
|
+
}
|
|
32
43
|
|
|
33
44
|
const body = await req.json().catch(() => ({}))
|
|
34
45
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
46
|
+
const input = pipelineStageReorderSchema.parse(scoped)
|
|
47
|
+
|
|
48
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
49
|
+
tenantId,
|
|
50
|
+
organizationId,
|
|
51
|
+
userId: auth.sub,
|
|
52
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
53
|
+
resourceId: organizationId,
|
|
54
|
+
operation: 'custom',
|
|
55
|
+
requestMethod: req.method,
|
|
56
|
+
requestHeaders: req.headers,
|
|
57
|
+
mutationPayload: input,
|
|
58
|
+
})
|
|
59
|
+
if (guardResult && !guardResult.ok) {
|
|
60
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
61
|
+
}
|
|
35
62
|
|
|
36
63
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
37
64
|
await commandBus.execute<PipelineStageReorderInput, void>(
|
|
38
65
|
'customers.pipeline-stages.reorder',
|
|
39
|
-
{ input
|
|
66
|
+
{ input, ctx },
|
|
40
67
|
)
|
|
68
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
69
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
70
|
+
tenantId,
|
|
71
|
+
organizationId,
|
|
72
|
+
userId: auth.sub,
|
|
73
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
74
|
+
resourceId: organizationId,
|
|
75
|
+
operation: 'custom',
|
|
76
|
+
requestMethod: req.method,
|
|
77
|
+
requestHeaders: req.headers,
|
|
78
|
+
metadata: guardResult.metadata ?? null,
|
|
79
|
+
})
|
|
80
|
+
}
|
|
41
81
|
return NextResponse.json({ ok: true })
|
|
42
82
|
} catch (err) {
|
|
43
83
|
if (isCrudHttpError(err)) {
|
|
@@ -18,8 +18,14 @@ import { withScopedPayload } from '../utils'
|
|
|
18
18
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
19
19
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
20
20
|
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
21
|
+
import {
|
|
22
|
+
runCrudMutationGuardAfterSuccess,
|
|
23
|
+
validateCrudMutationGuard,
|
|
24
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
21
25
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
22
26
|
|
|
27
|
+
const PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'
|
|
28
|
+
|
|
23
29
|
export const metadata = {
|
|
24
30
|
GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },
|
|
25
31
|
POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
|
|
@@ -29,7 +35,7 @@ export const metadata = {
|
|
|
29
35
|
|
|
30
36
|
async function buildContext(
|
|
31
37
|
req: Request
|
|
32
|
-
): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null }> {
|
|
38
|
+
): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null; translate: (key: string, fallback?: string) => string }> {
|
|
33
39
|
const container = await createRequestContainer()
|
|
34
40
|
const auth = await getAuthFromRequest(req)
|
|
35
41
|
const { translate } = await resolveTranslations()
|
|
@@ -45,14 +51,14 @@ async function buildContext(
|
|
|
45
51
|
}
|
|
46
52
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
47
53
|
const tenantId = auth.tenantId ?? null
|
|
48
|
-
return { ctx, organizationId, tenantId }
|
|
54
|
+
return { ctx, organizationId, tenantId, translate }
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export async function GET(req: Request) {
|
|
52
58
|
try {
|
|
53
|
-
const { ctx, organizationId, tenantId } = await buildContext(req)
|
|
59
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
54
60
|
if (!organizationId || !tenantId) {
|
|
55
|
-
return NextResponse.json({ error: 'Organization and tenant context required' }, { status: 400 })
|
|
61
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
56
62
|
}
|
|
57
63
|
const url = new URL(req.url)
|
|
58
64
|
const pipelineId = url.searchParams.get('pipelineId')
|
|
@@ -102,16 +108,47 @@ export async function GET(req: Request) {
|
|
|
102
108
|
|
|
103
109
|
export async function POST(req: Request) {
|
|
104
110
|
try {
|
|
105
|
-
const { ctx } = await buildContext(req)
|
|
111
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
112
|
+
if (!organizationId || !tenantId) {
|
|
113
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
114
|
+
}
|
|
106
115
|
const body = await req.json().catch(() => ({}))
|
|
107
|
-
const { translate } = await resolveTranslations()
|
|
108
116
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
117
|
+
const input = pipelineStageCreateSchema.parse(scoped)
|
|
118
|
+
|
|
119
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
120
|
+
tenantId,
|
|
121
|
+
organizationId,
|
|
122
|
+
userId: ctx.auth!.sub,
|
|
123
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
124
|
+
resourceId: organizationId,
|
|
125
|
+
operation: 'create',
|
|
126
|
+
requestMethod: req.method,
|
|
127
|
+
requestHeaders: req.headers,
|
|
128
|
+
mutationPayload: input,
|
|
129
|
+
})
|
|
130
|
+
if (guardResult && !guardResult.ok) {
|
|
131
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
132
|
+
}
|
|
109
133
|
|
|
110
134
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
111
135
|
const { result, logEntry } = await commandBus.execute<PipelineStageCreateInput, { stageId: string }>(
|
|
112
136
|
'customers.pipeline-stages.create',
|
|
113
|
-
{ input
|
|
137
|
+
{ input, ctx },
|
|
114
138
|
)
|
|
139
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
140
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
141
|
+
tenantId,
|
|
142
|
+
organizationId,
|
|
143
|
+
userId: ctx.auth!.sub,
|
|
144
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
145
|
+
resourceId: result?.stageId ?? organizationId,
|
|
146
|
+
operation: 'create',
|
|
147
|
+
requestMethod: req.method,
|
|
148
|
+
requestHeaders: req.headers,
|
|
149
|
+
metadata: guardResult.metadata ?? null,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
115
152
|
const response = NextResponse.json({ id: result?.stageId ?? null }, { status: 201 })
|
|
116
153
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
117
154
|
response.headers.set(
|
|
@@ -139,16 +176,47 @@ export async function POST(req: Request) {
|
|
|
139
176
|
|
|
140
177
|
export async function PUT(req: Request) {
|
|
141
178
|
try {
|
|
142
|
-
const { ctx } = await buildContext(req)
|
|
179
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
180
|
+
if (!organizationId || !tenantId) {
|
|
181
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
182
|
+
}
|
|
143
183
|
const body = await req.json().catch(() => ({}))
|
|
144
|
-
const { translate } = await resolveTranslations()
|
|
145
184
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
185
|
+
const input = pipelineStageUpdateSchema.parse(scoped)
|
|
186
|
+
|
|
187
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
188
|
+
tenantId,
|
|
189
|
+
organizationId,
|
|
190
|
+
userId: ctx.auth!.sub,
|
|
191
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
192
|
+
resourceId: input.id,
|
|
193
|
+
operation: 'update',
|
|
194
|
+
requestMethod: req.method,
|
|
195
|
+
requestHeaders: req.headers,
|
|
196
|
+
mutationPayload: input,
|
|
197
|
+
})
|
|
198
|
+
if (guardResult && !guardResult.ok) {
|
|
199
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
200
|
+
}
|
|
146
201
|
|
|
147
202
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
148
203
|
const { logEntry } = await commandBus.execute<PipelineStageUpdateInput, void>(
|
|
149
204
|
'customers.pipeline-stages.update',
|
|
150
|
-
{ input
|
|
205
|
+
{ input, ctx },
|
|
151
206
|
)
|
|
207
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
208
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
209
|
+
tenantId,
|
|
210
|
+
organizationId,
|
|
211
|
+
userId: ctx.auth!.sub,
|
|
212
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
213
|
+
resourceId: input.id,
|
|
214
|
+
operation: 'update',
|
|
215
|
+
requestMethod: req.method,
|
|
216
|
+
requestHeaders: req.headers,
|
|
217
|
+
metadata: guardResult.metadata ?? null,
|
|
218
|
+
})
|
|
219
|
+
}
|
|
152
220
|
const response = NextResponse.json({ ok: true })
|
|
153
221
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
154
222
|
response.headers.set(
|
|
@@ -176,16 +244,47 @@ export async function PUT(req: Request) {
|
|
|
176
244
|
|
|
177
245
|
export async function DELETE(req: Request) {
|
|
178
246
|
try {
|
|
179
|
-
const { ctx } = await buildContext(req)
|
|
247
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
248
|
+
if (!organizationId || !tenantId) {
|
|
249
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
250
|
+
}
|
|
180
251
|
const body = await req.json().catch(() => ({}))
|
|
181
|
-
const { translate } = await resolveTranslations()
|
|
182
252
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
253
|
+
const input = pipelineStageDeleteSchema.parse(scoped)
|
|
254
|
+
|
|
255
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
256
|
+
tenantId,
|
|
257
|
+
organizationId,
|
|
258
|
+
userId: ctx.auth!.sub,
|
|
259
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
260
|
+
resourceId: input.id,
|
|
261
|
+
operation: 'delete',
|
|
262
|
+
requestMethod: req.method,
|
|
263
|
+
requestHeaders: req.headers,
|
|
264
|
+
mutationPayload: input,
|
|
265
|
+
})
|
|
266
|
+
if (guardResult && !guardResult.ok) {
|
|
267
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
268
|
+
}
|
|
183
269
|
|
|
184
270
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
185
271
|
await commandBus.execute<PipelineStageDeleteInput, void>(
|
|
186
272
|
'customers.pipeline-stages.delete',
|
|
187
|
-
{ input
|
|
273
|
+
{ input, ctx },
|
|
188
274
|
)
|
|
275
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
276
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
277
|
+
tenantId,
|
|
278
|
+
organizationId,
|
|
279
|
+
userId: ctx.auth!.sub,
|
|
280
|
+
resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
|
|
281
|
+
resourceId: input.id,
|
|
282
|
+
operation: 'delete',
|
|
283
|
+
requestMethod: req.method,
|
|
284
|
+
requestHeaders: req.headers,
|
|
285
|
+
metadata: guardResult.metadata ?? null,
|
|
286
|
+
})
|
|
287
|
+
}
|
|
189
288
|
return NextResponse.json({ ok: true })
|
|
190
289
|
} catch (err) {
|
|
191
290
|
if (isCrudHttpError(err)) {
|
|
@@ -18,8 +18,14 @@ import { withScopedPayload } from '../utils'
|
|
|
18
18
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
19
19
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
20
20
|
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
21
|
+
import {
|
|
22
|
+
runCrudMutationGuardAfterSuccess,
|
|
23
|
+
validateCrudMutationGuard,
|
|
24
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
21
25
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
22
26
|
|
|
27
|
+
const PIPELINE_RESOURCE_KIND = 'customers.pipeline'
|
|
28
|
+
|
|
23
29
|
export const metadata = {
|
|
24
30
|
GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },
|
|
25
31
|
POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
|
|
@@ -29,7 +35,7 @@ export const metadata = {
|
|
|
29
35
|
|
|
30
36
|
async function buildContext(
|
|
31
37
|
req: Request
|
|
32
|
-
): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null }> {
|
|
38
|
+
): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null; translate: (key: string, fallback?: string) => string }> {
|
|
33
39
|
const container = await createRequestContainer()
|
|
34
40
|
const auth = await getAuthFromRequest(req)
|
|
35
41
|
const { translate } = await resolveTranslations()
|
|
@@ -45,14 +51,14 @@ async function buildContext(
|
|
|
45
51
|
}
|
|
46
52
|
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
47
53
|
const tenantId = auth.tenantId ?? null
|
|
48
|
-
return { ctx, organizationId, tenantId }
|
|
54
|
+
return { ctx, organizationId, tenantId, translate }
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export async function GET(req: Request) {
|
|
52
58
|
try {
|
|
53
|
-
const { ctx, organizationId, tenantId } = await buildContext(req)
|
|
59
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
54
60
|
if (!organizationId || !tenantId) {
|
|
55
|
-
return NextResponse.json({ error: 'Organization and tenant context required' }, { status: 400 })
|
|
61
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
56
62
|
}
|
|
57
63
|
const url = new URL(req.url)
|
|
58
64
|
const isDefaultParam = url.searchParams.get('isDefault')
|
|
@@ -84,16 +90,47 @@ export async function GET(req: Request) {
|
|
|
84
90
|
|
|
85
91
|
export async function POST(req: Request) {
|
|
86
92
|
try {
|
|
87
|
-
const { ctx } = await buildContext(req)
|
|
93
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
94
|
+
if (!organizationId || !tenantId) {
|
|
95
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
96
|
+
}
|
|
88
97
|
const body = await req.json().catch(() => ({}))
|
|
89
|
-
const { translate } = await resolveTranslations()
|
|
90
98
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
99
|
+
const input = pipelineCreateSchema.parse(scoped)
|
|
100
|
+
|
|
101
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
102
|
+
tenantId,
|
|
103
|
+
organizationId,
|
|
104
|
+
userId: ctx.auth!.sub,
|
|
105
|
+
resourceKind: PIPELINE_RESOURCE_KIND,
|
|
106
|
+
resourceId: organizationId,
|
|
107
|
+
operation: 'create',
|
|
108
|
+
requestMethod: req.method,
|
|
109
|
+
requestHeaders: req.headers,
|
|
110
|
+
mutationPayload: input,
|
|
111
|
+
})
|
|
112
|
+
if (guardResult && !guardResult.ok) {
|
|
113
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
114
|
+
}
|
|
91
115
|
|
|
92
116
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
93
117
|
const { result, logEntry } = await commandBus.execute<PipelineCreateInput, { pipelineId: string }>(
|
|
94
118
|
'customers.pipelines.create',
|
|
95
|
-
{ input
|
|
119
|
+
{ input, ctx },
|
|
96
120
|
)
|
|
121
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
122
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
123
|
+
tenantId,
|
|
124
|
+
organizationId,
|
|
125
|
+
userId: ctx.auth!.sub,
|
|
126
|
+
resourceKind: PIPELINE_RESOURCE_KIND,
|
|
127
|
+
resourceId: result?.pipelineId ?? organizationId,
|
|
128
|
+
operation: 'create',
|
|
129
|
+
requestMethod: req.method,
|
|
130
|
+
requestHeaders: req.headers,
|
|
131
|
+
metadata: guardResult.metadata ?? null,
|
|
132
|
+
})
|
|
133
|
+
}
|
|
97
134
|
const response = NextResponse.json({ id: result?.pipelineId ?? null }, { status: 201 })
|
|
98
135
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
99
136
|
response.headers.set(
|
|
@@ -121,16 +158,47 @@ export async function POST(req: Request) {
|
|
|
121
158
|
|
|
122
159
|
export async function PUT(req: Request) {
|
|
123
160
|
try {
|
|
124
|
-
const { ctx } = await buildContext(req)
|
|
161
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
162
|
+
if (!organizationId || !tenantId) {
|
|
163
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
164
|
+
}
|
|
125
165
|
const body = await req.json().catch(() => ({}))
|
|
126
|
-
const { translate } = await resolveTranslations()
|
|
127
166
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
167
|
+
const input = pipelineUpdateSchema.parse(scoped)
|
|
168
|
+
|
|
169
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
170
|
+
tenantId,
|
|
171
|
+
organizationId,
|
|
172
|
+
userId: ctx.auth!.sub,
|
|
173
|
+
resourceKind: PIPELINE_RESOURCE_KIND,
|
|
174
|
+
resourceId: input.id,
|
|
175
|
+
operation: 'update',
|
|
176
|
+
requestMethod: req.method,
|
|
177
|
+
requestHeaders: req.headers,
|
|
178
|
+
mutationPayload: input,
|
|
179
|
+
})
|
|
180
|
+
if (guardResult && !guardResult.ok) {
|
|
181
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
182
|
+
}
|
|
128
183
|
|
|
129
184
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
130
185
|
const { logEntry } = await commandBus.execute<PipelineUpdateInput, void>(
|
|
131
186
|
'customers.pipelines.update',
|
|
132
|
-
{ input
|
|
187
|
+
{ input, ctx },
|
|
133
188
|
)
|
|
189
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
190
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
191
|
+
tenantId,
|
|
192
|
+
organizationId,
|
|
193
|
+
userId: ctx.auth!.sub,
|
|
194
|
+
resourceKind: PIPELINE_RESOURCE_KIND,
|
|
195
|
+
resourceId: input.id,
|
|
196
|
+
operation: 'update',
|
|
197
|
+
requestMethod: req.method,
|
|
198
|
+
requestHeaders: req.headers,
|
|
199
|
+
metadata: guardResult.metadata ?? null,
|
|
200
|
+
})
|
|
201
|
+
}
|
|
134
202
|
const response = NextResponse.json({ ok: true })
|
|
135
203
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
136
204
|
response.headers.set(
|
|
@@ -158,16 +226,47 @@ export async function PUT(req: Request) {
|
|
|
158
226
|
|
|
159
227
|
export async function DELETE(req: Request) {
|
|
160
228
|
try {
|
|
161
|
-
const { ctx } = await buildContext(req)
|
|
229
|
+
const { ctx, organizationId, tenantId, translate } = await buildContext(req)
|
|
230
|
+
if (!organizationId || !tenantId) {
|
|
231
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
232
|
+
}
|
|
162
233
|
const body = await req.json().catch(() => ({}))
|
|
163
|
-
const { translate } = await resolveTranslations()
|
|
164
234
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
235
|
+
const input = pipelineDeleteSchema.parse(scoped)
|
|
236
|
+
|
|
237
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
238
|
+
tenantId,
|
|
239
|
+
organizationId,
|
|
240
|
+
userId: ctx.auth!.sub,
|
|
241
|
+
resourceKind: PIPELINE_RESOURCE_KIND,
|
|
242
|
+
resourceId: input.id,
|
|
243
|
+
operation: 'delete',
|
|
244
|
+
requestMethod: req.method,
|
|
245
|
+
requestHeaders: req.headers,
|
|
246
|
+
mutationPayload: input,
|
|
247
|
+
})
|
|
248
|
+
if (guardResult && !guardResult.ok) {
|
|
249
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
250
|
+
}
|
|
165
251
|
|
|
166
252
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
167
253
|
await commandBus.execute<PipelineDeleteInput, void>(
|
|
168
254
|
'customers.pipelines.delete',
|
|
169
|
-
{ input
|
|
255
|
+
{ input, ctx },
|
|
170
256
|
)
|
|
257
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
258
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
259
|
+
tenantId,
|
|
260
|
+
organizationId,
|
|
261
|
+
userId: ctx.auth!.sub,
|
|
262
|
+
resourceKind: PIPELINE_RESOURCE_KIND,
|
|
263
|
+
resourceId: input.id,
|
|
264
|
+
operation: 'delete',
|
|
265
|
+
requestMethod: req.method,
|
|
266
|
+
requestHeaders: req.headers,
|
|
267
|
+
metadata: guardResult.metadata ?? null,
|
|
268
|
+
})
|
|
269
|
+
}
|
|
171
270
|
return NextResponse.json({ ok: true })
|
|
172
271
|
} catch (err) {
|
|
173
272
|
if (isCrudHttpError(err)) {
|
|
@@ -7,12 +7,18 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
|
|
|
7
7
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
8
8
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
9
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
|
+
import {
|
|
11
|
+
runCrudMutationGuardAfterSuccess,
|
|
12
|
+
validateCrudMutationGuard,
|
|
13
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
10
14
|
import { customerSettingsUpsertSchema, type CustomerSettingsUpsertInput } from '../../../data/validators'
|
|
11
15
|
import { loadCustomerSettings } from '../../../commands/settings'
|
|
12
16
|
import type { CustomerAddressFormat } from '../../../data/entities'
|
|
13
17
|
import { withScopedPayload } from '../../utils'
|
|
14
18
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
15
19
|
|
|
20
|
+
const SETTINGS_RESOURCE_KIND = 'customers.settings'
|
|
21
|
+
|
|
16
22
|
export const metadata = {
|
|
17
23
|
GET: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },
|
|
18
24
|
PUT: { requireAuth: true, requireFeatures: ['customers.settings.manage'] },
|
|
@@ -78,17 +84,46 @@ export async function GET(req: Request) {
|
|
|
78
84
|
|
|
79
85
|
export async function PUT(req: Request) {
|
|
80
86
|
try {
|
|
81
|
-
const { ctx, translate } = await resolveSettingsContext(req)
|
|
87
|
+
const { ctx, tenantId, organizationId, translate } = await resolveSettingsContext(req)
|
|
82
88
|
const payload = await req.json().catch(() => ({}))
|
|
83
89
|
const scoped = withScopedPayload(payload, ctx, translate)
|
|
84
90
|
const input = customerSettingsUpsertSchema.parse(scoped)
|
|
85
91
|
|
|
92
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
93
|
+
tenantId,
|
|
94
|
+
organizationId,
|
|
95
|
+
userId: ctx.auth!.sub,
|
|
96
|
+
resourceKind: SETTINGS_RESOURCE_KIND,
|
|
97
|
+
resourceId: organizationId,
|
|
98
|
+
operation: 'update',
|
|
99
|
+
requestMethod: req.method,
|
|
100
|
+
requestHeaders: req.headers,
|
|
101
|
+
mutationPayload: input,
|
|
102
|
+
})
|
|
103
|
+
if (guardResult && !guardResult.ok) {
|
|
104
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
87
108
|
const { result } = await commandBus.execute<CustomerSettingsUpsertInput, { settingsId: string; addressFormat: CustomerAddressFormat }>(
|
|
88
109
|
'customers.settings.save',
|
|
89
110
|
{ input, ctx },
|
|
90
111
|
)
|
|
91
112
|
|
|
113
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
114
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
115
|
+
tenantId,
|
|
116
|
+
organizationId,
|
|
117
|
+
userId: ctx.auth!.sub,
|
|
118
|
+
resourceKind: SETTINGS_RESOURCE_KIND,
|
|
119
|
+
resourceId: organizationId,
|
|
120
|
+
operation: 'update',
|
|
121
|
+
requestMethod: req.method,
|
|
122
|
+
requestHeaders: req.headers,
|
|
123
|
+
metadata: guardResult.metadata ?? null,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
92
127
|
return NextResponse.json({
|
|
93
128
|
addressFormat: result?.addressFormat ?? input.addressFormat,
|
|
94
129
|
})
|
|
@@ -9,8 +9,14 @@ import { tagAssignmentSchema, type TagAssignmentInput } from '../../../data/vali
|
|
|
9
9
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
10
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
11
|
import { withScopedPayload } from '../../utils'
|
|
12
|
+
import {
|
|
13
|
+
runCrudMutationGuardAfterSuccess,
|
|
14
|
+
validateCrudMutationGuard,
|
|
15
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
12
16
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
13
17
|
|
|
18
|
+
const TAG_ASSIGNMENT_RESOURCE_KIND = 'customers.tagAssignment'
|
|
19
|
+
|
|
14
20
|
export const metadata = {
|
|
15
21
|
POST: { requireAuth: true, requireFeatures: ['customers.activities.manage'] },
|
|
16
22
|
}
|
|
@@ -37,15 +43,48 @@ async function buildContext(
|
|
|
37
43
|
export async function POST(req: Request) {
|
|
38
44
|
try {
|
|
39
45
|
const { ctx, auth, translate } = await buildContext(req)
|
|
46
|
+
const tenantId = auth!.tenantId
|
|
47
|
+
const organizationId = ctx.selectedOrganizationId
|
|
48
|
+
if (!tenantId || !organizationId) {
|
|
49
|
+
return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
|
|
50
|
+
}
|
|
40
51
|
const body = await req.json().catch(() => ({}))
|
|
41
52
|
const scoped = withScopedPayload(body, ctx, translate)
|
|
42
53
|
const input = tagAssignmentSchema.parse(scoped)
|
|
43
54
|
|
|
55
|
+
const guardResult = await validateCrudMutationGuard(ctx.container, {
|
|
56
|
+
tenantId,
|
|
57
|
+
organizationId,
|
|
58
|
+
userId: auth!.sub,
|
|
59
|
+
resourceKind: TAG_ASSIGNMENT_RESOURCE_KIND,
|
|
60
|
+
resourceId: input.entityId,
|
|
61
|
+
operation: 'custom',
|
|
62
|
+
requestMethod: req.method,
|
|
63
|
+
requestHeaders: req.headers,
|
|
64
|
+
mutationPayload: input,
|
|
65
|
+
})
|
|
66
|
+
if (guardResult && !guardResult.ok) {
|
|
67
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
68
|
+
}
|
|
69
|
+
|
|
44
70
|
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
45
71
|
const { result, logEntry } = await commandBus.execute<TagAssignmentInput, { assignmentId: string }>(
|
|
46
72
|
'customers.tags.assign',
|
|
47
73
|
{ input, ctx },
|
|
48
74
|
)
|
|
75
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
76
|
+
await runCrudMutationGuardAfterSuccess(ctx.container, {
|
|
77
|
+
tenantId,
|
|
78
|
+
organizationId,
|
|
79
|
+
userId: auth!.sub,
|
|
80
|
+
resourceKind: TAG_ASSIGNMENT_RESOURCE_KIND,
|
|
81
|
+
resourceId: input.entityId,
|
|
82
|
+
operation: 'custom',
|
|
83
|
+
requestMethod: req.method,
|
|
84
|
+
requestHeaders: req.headers,
|
|
85
|
+
metadata: guardResult.metadata ?? null,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
49
88
|
const response = NextResponse.json({ id: result?.assignmentId ?? null }, { status: 201 })
|
|
50
89
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
51
90
|
response.headers.set(
|