@open-mercato/core 0.10.1-develop.7785.1.ec0898da36 → 0.10.1-develop.7787.1.96c7cfc66f
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/configs/api/cache/route.js +18 -45
- package/dist/modules/configs/api/cache/route.js.map +2 -2
- package/dist/modules/integrations/api/guards.js +37 -7
- package/dist/modules/integrations/api/guards.js.map +2 -2
- package/dist/modules/messages/api/guards.js +37 -7
- package/dist/modules/messages/api/guards.js.map +2 -2
- package/dist/modules/payment_gateways/api/guards.js +37 -7
- package/dist/modules/payment_gateways/api/guards.js.map +2 -2
- package/dist/modules/push_notifications/api/custom-send/route.js +18 -38
- package/dist/modules/push_notifications/api/custom-send/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/convert/route.js +17 -49
- package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/send/route.js +17 -49
- package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/configs/api/cache/route.ts +19 -48
- package/src/modules/integrations/api/guards.ts +45 -7
- package/src/modules/messages/api/guards.ts +45 -7
- package/src/modules/payment_gateways/api/guards.ts +45 -7
- package/src/modules/push_notifications/api/custom-send/route.ts +18 -52
- package/src/modules/sales/api/quotes/convert/route.ts +17 -75
- package/src/modules/sales/api/quotes/send/route.ts +17 -75
|
@@ -5,12 +5,18 @@ import {
|
|
|
5
5
|
type MutationGuard,
|
|
6
6
|
type MutationGuardInput,
|
|
7
7
|
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
8
|
+
import { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'
|
|
9
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
10
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
11
|
+
|
|
12
|
+
const logger = createLogger('messages')
|
|
8
13
|
|
|
9
14
|
type GuardAfterCallback = {
|
|
10
15
|
guard: MutationGuard
|
|
11
16
|
metadata: Record<string, unknown> | null
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
/** @deprecated Mutation guard grants are resolved from RBAC by `runMessageMutationGuards`. */
|
|
14
20
|
export function resolveUserFeatures(auth: unknown): string[] {
|
|
15
21
|
const features = (auth as { features?: unknown })?.features
|
|
16
22
|
if (!Array.isArray(features)) return []
|
|
@@ -20,7 +26,7 @@ export function resolveUserFeatures(auth: unknown): string[] {
|
|
|
20
26
|
export async function runMessageMutationGuards(
|
|
21
27
|
container: AwilixContainer,
|
|
22
28
|
input: MutationGuardInput,
|
|
23
|
-
|
|
29
|
+
extraFeatures: string[] = [],
|
|
24
30
|
): Promise<{
|
|
25
31
|
ok: boolean
|
|
26
32
|
errorBody?: Record<string, unknown>
|
|
@@ -28,12 +34,35 @@ export async function runMessageMutationGuards(
|
|
|
28
34
|
modifiedPayload?: Record<string, unknown>
|
|
29
35
|
afterSuccessCallbacks: GuardAfterCallback[]
|
|
30
36
|
}> {
|
|
37
|
+
const allGuards: MutationGuard[] = [...getAllMutationGuardInstances()]
|
|
31
38
|
const legacyGuard = bridgeLegacyGuard(container)
|
|
32
|
-
if (
|
|
39
|
+
if (legacyGuard) allGuards.push(legacyGuard)
|
|
40
|
+
if (!allGuards.length) {
|
|
33
41
|
return { ok: true, afterSuccessCallbacks: [] }
|
|
34
42
|
}
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
let grantedFeatures: string[] = []
|
|
45
|
+
try {
|
|
46
|
+
const rbacService = container.resolve('rbacService') as {
|
|
47
|
+
getGrantedFeatures?: (
|
|
48
|
+
userId: string,
|
|
49
|
+
scope: { tenantId: string | null; organizationId: string | null },
|
|
50
|
+
) => Promise<string[]>
|
|
51
|
+
} | undefined
|
|
52
|
+
grantedFeatures = await rbacService?.getGrantedFeatures?.(input.userId, {
|
|
53
|
+
tenantId: input.tenantId,
|
|
54
|
+
organizationId: input.organizationId,
|
|
55
|
+
}) ?? []
|
|
56
|
+
} catch (err) {
|
|
57
|
+
logger.error('Failed to resolve mutation guard feature grants', { err })
|
|
58
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
59
|
+
module: 'messages',
|
|
60
|
+
code: 'messages.mutation_guard_feature_resolution_failed',
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const userFeatures = Array.from(new Set([...grantedFeatures, ...extraFeatures]))
|
|
65
|
+
return runMutationGuards(allGuards, input, { userFeatures })
|
|
37
66
|
}
|
|
38
67
|
|
|
39
68
|
export async function runMessageMutationGuardAfterSuccess(
|
|
@@ -51,9 +80,18 @@ export async function runMessageMutationGuardAfterSuccess(
|
|
|
51
80
|
): Promise<void> {
|
|
52
81
|
for (const callback of callbacks) {
|
|
53
82
|
if (!callback.guard.afterSuccess) continue
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
83
|
+
try {
|
|
84
|
+
await callback.guard.afterSuccess({
|
|
85
|
+
...input,
|
|
86
|
+
metadata: callback.metadata ?? null,
|
|
87
|
+
})
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.error('Mutation guard afterSuccess failed', { guardId: callback.guard.id, err })
|
|
90
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
91
|
+
module: 'messages',
|
|
92
|
+
code: 'messages.mutation_guard_after_success_failed',
|
|
93
|
+
attributes: { guardId: callback.guard.id },
|
|
94
|
+
})
|
|
95
|
+
}
|
|
58
96
|
}
|
|
59
97
|
}
|
|
@@ -5,12 +5,18 @@ import {
|
|
|
5
5
|
type MutationGuard,
|
|
6
6
|
type MutationGuardInput,
|
|
7
7
|
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
8
|
+
import { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'
|
|
9
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
10
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
11
|
+
|
|
12
|
+
const logger = createLogger('payment_gateways')
|
|
8
13
|
|
|
9
14
|
type GuardAfterCallback = {
|
|
10
15
|
guard: MutationGuard
|
|
11
16
|
metadata: Record<string, unknown> | null
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
/** @deprecated Mutation guard grants are resolved from RBAC by `runPaymentGatewayMutationGuards`. */
|
|
14
20
|
export function resolveUserFeatures(auth: unknown): string[] {
|
|
15
21
|
const features = (auth as { features?: unknown })?.features
|
|
16
22
|
if (!Array.isArray(features)) return []
|
|
@@ -20,7 +26,7 @@ export function resolveUserFeatures(auth: unknown): string[] {
|
|
|
20
26
|
export async function runPaymentGatewayMutationGuards(
|
|
21
27
|
container: AwilixContainer,
|
|
22
28
|
input: MutationGuardInput,
|
|
23
|
-
|
|
29
|
+
extraFeatures: string[] = [],
|
|
24
30
|
): Promise<{
|
|
25
31
|
ok: boolean
|
|
26
32
|
errorBody?: Record<string, unknown>
|
|
@@ -28,12 +34,35 @@ export async function runPaymentGatewayMutationGuards(
|
|
|
28
34
|
modifiedPayload?: Record<string, unknown>
|
|
29
35
|
afterSuccessCallbacks: GuardAfterCallback[]
|
|
30
36
|
}> {
|
|
37
|
+
const allGuards: MutationGuard[] = [...getAllMutationGuardInstances()]
|
|
31
38
|
const legacyGuard = bridgeLegacyGuard(container)
|
|
32
|
-
if (
|
|
39
|
+
if (legacyGuard) allGuards.push(legacyGuard)
|
|
40
|
+
if (!allGuards.length) {
|
|
33
41
|
return { ok: true, afterSuccessCallbacks: [] }
|
|
34
42
|
}
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
let grantedFeatures: string[] = []
|
|
45
|
+
try {
|
|
46
|
+
const rbacService = container.resolve('rbacService') as {
|
|
47
|
+
getGrantedFeatures?: (
|
|
48
|
+
userId: string,
|
|
49
|
+
scope: { tenantId: string | null; organizationId: string | null },
|
|
50
|
+
) => Promise<string[]>
|
|
51
|
+
} | undefined
|
|
52
|
+
grantedFeatures = await rbacService?.getGrantedFeatures?.(input.userId, {
|
|
53
|
+
tenantId: input.tenantId,
|
|
54
|
+
organizationId: input.organizationId,
|
|
55
|
+
}) ?? []
|
|
56
|
+
} catch (err) {
|
|
57
|
+
logger.error('Failed to resolve mutation guard feature grants', { err })
|
|
58
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
59
|
+
module: 'payment_gateways',
|
|
60
|
+
code: 'payment_gateways.mutation_guard_feature_resolution_failed',
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const userFeatures = Array.from(new Set([...grantedFeatures, ...extraFeatures]))
|
|
65
|
+
return runMutationGuards(allGuards, input, { userFeatures })
|
|
37
66
|
}
|
|
38
67
|
|
|
39
68
|
export async function runPaymentGatewayMutationGuardAfterSuccess(
|
|
@@ -51,9 +80,18 @@ export async function runPaymentGatewayMutationGuardAfterSuccess(
|
|
|
51
80
|
): Promise<void> {
|
|
52
81
|
for (const callback of callbacks) {
|
|
53
82
|
if (!callback.guard.afterSuccess) continue
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
83
|
+
try {
|
|
84
|
+
await callback.guard.afterSuccess({
|
|
85
|
+
...input,
|
|
86
|
+
metadata: callback.metadata ?? null,
|
|
87
|
+
})
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.error('Mutation guard afterSuccess failed', { guardId: callback.guard.id, err })
|
|
90
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
91
|
+
module: 'payment_gateways',
|
|
92
|
+
code: 'payment_gateways.mutation_guard_after_success_failed',
|
|
93
|
+
attributes: { guardId: callback.guard.id },
|
|
94
|
+
})
|
|
95
|
+
}
|
|
58
96
|
}
|
|
59
97
|
}
|
|
@@ -7,13 +7,7 @@ import { resolveSingleOrganizationIdOrDeny } from '@open-mercato/core/modules/di
|
|
|
7
7
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
8
8
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
9
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
10
|
-
import {
|
|
11
|
-
bridgeLegacyGuard,
|
|
12
|
-
runMutationGuards,
|
|
13
|
-
type MutationGuard,
|
|
14
|
-
type MutationGuardInput,
|
|
15
|
-
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
16
|
-
import type { AwilixContainer } from 'awilix'
|
|
10
|
+
import { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
17
11
|
import {
|
|
18
12
|
customSendSchema,
|
|
19
13
|
customSendResponseSchema,
|
|
@@ -32,27 +26,6 @@ export const metadata = {
|
|
|
32
26
|
POST: { requireAuth: true, requireFeatures: ['push_notifications.send_custom'] },
|
|
33
27
|
}
|
|
34
28
|
|
|
35
|
-
function resolveUserFeatures(auth: unknown): string[] {
|
|
36
|
-
const features = (auth as { features?: unknown })?.features
|
|
37
|
-
if (!Array.isArray(features)) return []
|
|
38
|
-
return features.filter((value): value is string => typeof value === 'string')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function runGuards(
|
|
42
|
-
container: AwilixContainer,
|
|
43
|
-
userFeatures: string[],
|
|
44
|
-
input: MutationGuardInput,
|
|
45
|
-
): Promise<{
|
|
46
|
-
ok: boolean
|
|
47
|
-
errorBody?: Record<string, unknown>
|
|
48
|
-
errorStatus?: number
|
|
49
|
-
afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
|
|
50
|
-
}> {
|
|
51
|
-
const legacyGuard = bridgeLegacyGuard(container)
|
|
52
|
-
if (!legacyGuard) return { ok: true, afterSuccessCallbacks: [] }
|
|
53
|
-
return runMutationGuards([legacyGuard], input, { userFeatures })
|
|
54
|
-
}
|
|
55
|
-
|
|
56
29
|
export async function POST(req: Request) {
|
|
57
30
|
const { translate } = await resolveTranslations()
|
|
58
31
|
try {
|
|
@@ -71,23 +44,23 @@ export async function POST(req: Request) {
|
|
|
71
44
|
|
|
72
45
|
// Custom write route → wire the mutation-guard registry (AGENTS → API Routes). The send creates
|
|
73
46
|
// append-only delivery rows; map it to a `create` on the delivery resource keyed by recipient.
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
47
|
+
const guardResult = await runRouteMutationGuards({
|
|
48
|
+
container,
|
|
49
|
+
req,
|
|
50
|
+
auth: {
|
|
51
|
+
tenantId: auth.tenantId,
|
|
52
|
+
organizationId,
|
|
53
|
+
userId: auth.sub,
|
|
54
|
+
},
|
|
55
|
+
input: {
|
|
56
|
+
resourceKind: RESOURCE_KIND,
|
|
57
|
+
resourceId: body.recipientUserId,
|
|
58
|
+
operation: 'create',
|
|
59
|
+
mutationPayload: body,
|
|
60
|
+
},
|
|
61
|
+
})
|
|
86
62
|
if (!guardResult.ok) {
|
|
87
|
-
return
|
|
88
|
-
guardResult.errorBody ?? { error: translate('push_notifications.errors.send_failed', 'Operation blocked') },
|
|
89
|
-
{ status: guardResult.errorStatus ?? 422 },
|
|
90
|
-
)
|
|
63
|
+
return guardResult.response
|
|
91
64
|
}
|
|
92
65
|
|
|
93
66
|
const service = container.resolve('pushNotificationService') as PushNotificationService
|
|
@@ -104,14 +77,7 @@ export async function POST(req: Request) {
|
|
|
104
77
|
silent: body.silent ?? false,
|
|
105
78
|
})
|
|
106
79
|
|
|
107
|
-
|
|
108
|
-
if (!callback.guard.afterSuccess) continue
|
|
109
|
-
await callback.guard.afterSuccess({
|
|
110
|
-
...guardInput,
|
|
111
|
-
resourceId: body.recipientUserId,
|
|
112
|
-
metadata: callback.metadata ?? null,
|
|
113
|
-
})
|
|
114
|
-
}
|
|
80
|
+
await guardResult.runAfterSuccess()
|
|
115
81
|
|
|
116
82
|
// A well-formed request that enqueued nothing (no push channel, no in-scope device, or no device
|
|
117
83
|
// whose provider matches an active channel) previously returned a bare 201 — a silent
|
|
@@ -9,12 +9,7 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
|
|
|
9
9
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
10
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
11
11
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
|
-
import {
|
|
13
|
-
bridgeLegacyGuard,
|
|
14
|
-
runMutationGuards,
|
|
15
|
-
type MutationGuard,
|
|
16
|
-
type MutationGuardInput,
|
|
17
|
-
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
12
|
+
import { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
18
13
|
import { withScopedPayload } from '../../utils'
|
|
19
14
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
20
15
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
@@ -35,53 +30,6 @@ type RequestContext = {
|
|
|
35
30
|
ctx: CommandRuntimeContext
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
function resolveUserFeatures(auth: unknown): string[] {
|
|
39
|
-
const features = (auth as { features?: unknown })?.features
|
|
40
|
-
if (!Array.isArray(features)) return []
|
|
41
|
-
return features.filter((value): value is string => typeof value === 'string')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function runGuards(
|
|
45
|
-
ctx: CommandRuntimeContext,
|
|
46
|
-
input: MutationGuardInput,
|
|
47
|
-
): Promise<{
|
|
48
|
-
ok: boolean
|
|
49
|
-
errorBody?: Record<string, unknown>
|
|
50
|
-
errorStatus?: number
|
|
51
|
-
afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
|
|
52
|
-
}> {
|
|
53
|
-
const legacyGuard = bridgeLegacyGuard(ctx.container)
|
|
54
|
-
if (!legacyGuard) {
|
|
55
|
-
return { ok: true, afterSuccessCallbacks: [] }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return runMutationGuards([legacyGuard], input, {
|
|
59
|
-
userFeatures: resolveUserFeatures(ctx.auth),
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function runGuardAfterSuccessCallbacks(
|
|
64
|
-
callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,
|
|
65
|
-
input: {
|
|
66
|
-
tenantId: string
|
|
67
|
-
organizationId: string | null
|
|
68
|
-
userId: string
|
|
69
|
-
resourceKind: string
|
|
70
|
-
resourceId: string
|
|
71
|
-
operation: 'create' | 'update' | 'delete'
|
|
72
|
-
requestMethod: string
|
|
73
|
-
requestHeaders: Headers
|
|
74
|
-
},
|
|
75
|
-
): Promise<void> {
|
|
76
|
-
for (const callback of callbacks) {
|
|
77
|
-
if (!callback.guard.afterSuccess) continue
|
|
78
|
-
await callback.guard.afterSuccess({
|
|
79
|
-
...input,
|
|
80
|
-
metadata: callback.metadata ?? null,
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
33
|
async function resolveRequestContext(req: Request): Promise<RequestContext> {
|
|
86
34
|
const container = await createRequestContainer()
|
|
87
35
|
const auth = await getAuthFromRequest(req)
|
|
@@ -118,18 +66,23 @@ export async function POST(req: Request) {
|
|
|
118
66
|
const payload = await req.json().catch(() => ({}))
|
|
119
67
|
const scoped = withScopedPayload(payload ?? {}, ctx, translate)
|
|
120
68
|
const input = convertSchema.parse(scoped)
|
|
121
|
-
const guardResult = await
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
69
|
+
const guardResult = await runRouteMutationGuards({
|
|
70
|
+
container: ctx.container,
|
|
71
|
+
req,
|
|
72
|
+
auth: {
|
|
73
|
+
tenantId: ctx.auth?.tenantId ?? '',
|
|
74
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
75
|
+
userId: ctx.auth?.sub ?? '',
|
|
76
|
+
},
|
|
77
|
+
input: {
|
|
78
|
+
resourceKind: 'sales.quote',
|
|
79
|
+
resourceId: input.quoteId,
|
|
80
|
+
operation: 'update',
|
|
81
|
+
mutationPayload: input,
|
|
82
|
+
},
|
|
130
83
|
})
|
|
131
84
|
if (!guardResult.ok) {
|
|
132
|
-
return
|
|
85
|
+
return guardResult.response
|
|
133
86
|
}
|
|
134
87
|
const commandBus = ctx.container.resolve('commandBus') as CommandBus
|
|
135
88
|
const { result, logEntry } = await commandBus.execute<
|
|
@@ -159,18 +112,7 @@ export async function POST(req: Request) {
|
|
|
159
112
|
)
|
|
160
113
|
}
|
|
161
114
|
|
|
162
|
-
|
|
163
|
-
await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
|
|
164
|
-
tenantId: ctx.auth?.tenantId ?? '',
|
|
165
|
-
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
166
|
-
userId: ctx.auth?.sub ?? '',
|
|
167
|
-
resourceKind: 'sales.quote',
|
|
168
|
-
resourceId: input.quoteId,
|
|
169
|
-
operation: 'update',
|
|
170
|
-
requestMethod: req.method,
|
|
171
|
-
requestHeaders: req.headers,
|
|
172
|
-
})
|
|
173
|
-
}
|
|
115
|
+
await guardResult.runAfterSuccess()
|
|
174
116
|
|
|
175
117
|
return jsonResponse
|
|
176
118
|
} catch (err) {
|
|
@@ -8,12 +8,7 @@ import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
|
8
8
|
import { resolveTranslations, detectLocale } from '@open-mercato/shared/lib/i18n/server'
|
|
9
9
|
import { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
|
|
10
10
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
11
|
-
import {
|
|
12
|
-
bridgeLegacyGuard,
|
|
13
|
-
runMutationGuards,
|
|
14
|
-
type MutationGuard,
|
|
15
|
-
type MutationGuardInput,
|
|
16
|
-
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
11
|
+
import { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
17
12
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
18
13
|
import crypto from 'node:crypto'
|
|
19
14
|
import { withScopedPayload } from '../../utils'
|
|
@@ -37,53 +32,6 @@ type RequestContext = {
|
|
|
37
32
|
ctx: CommandRuntimeContext
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
function resolveUserFeatures(auth: unknown): string[] {
|
|
41
|
-
const features = (auth as { features?: unknown })?.features
|
|
42
|
-
if (!Array.isArray(features)) return []
|
|
43
|
-
return features.filter((value): value is string => typeof value === 'string')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function runGuards(
|
|
47
|
-
ctx: CommandRuntimeContext,
|
|
48
|
-
input: MutationGuardInput,
|
|
49
|
-
): Promise<{
|
|
50
|
-
ok: boolean
|
|
51
|
-
errorBody?: Record<string, unknown>
|
|
52
|
-
errorStatus?: number
|
|
53
|
-
afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
|
|
54
|
-
}> {
|
|
55
|
-
const legacyGuard = bridgeLegacyGuard(ctx.container)
|
|
56
|
-
if (!legacyGuard) {
|
|
57
|
-
return { ok: true, afterSuccessCallbacks: [] }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return runMutationGuards([legacyGuard], input, {
|
|
61
|
-
userFeatures: resolveUserFeatures(ctx.auth),
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function runGuardAfterSuccessCallbacks(
|
|
66
|
-
callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,
|
|
67
|
-
input: {
|
|
68
|
-
tenantId: string
|
|
69
|
-
organizationId: string | null
|
|
70
|
-
userId: string
|
|
71
|
-
resourceKind: string
|
|
72
|
-
resourceId: string
|
|
73
|
-
operation: 'create' | 'update' | 'delete'
|
|
74
|
-
requestMethod: string
|
|
75
|
-
requestHeaders: Headers
|
|
76
|
-
},
|
|
77
|
-
): Promise<void> {
|
|
78
|
-
for (const callback of callbacks) {
|
|
79
|
-
if (!callback.guard.afterSuccess) continue
|
|
80
|
-
await callback.guard.afterSuccess({
|
|
81
|
-
...input,
|
|
82
|
-
metadata: callback.metadata ?? null,
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
35
|
async function resolveRequestContext(req: Request): Promise<RequestContext> {
|
|
88
36
|
const container = await createRequestContainer()
|
|
89
37
|
const auth = await getAuthFromRequest(req)
|
|
@@ -143,18 +91,23 @@ export async function POST(req: Request) {
|
|
|
143
91
|
const payload = await req.json().catch(() => ({}))
|
|
144
92
|
const scoped = withScopedPayload(payload ?? {}, ctx, translate)
|
|
145
93
|
const input = quoteSendSchema.parse(scoped)
|
|
146
|
-
const guardResult = await
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
94
|
+
const guardResult = await runRouteMutationGuards({
|
|
95
|
+
container: ctx.container,
|
|
96
|
+
req,
|
|
97
|
+
auth: {
|
|
98
|
+
tenantId: ctx.auth?.tenantId ?? '',
|
|
99
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
100
|
+
userId: ctx.auth?.sub ?? '',
|
|
101
|
+
},
|
|
102
|
+
input: {
|
|
103
|
+
resourceKind: 'sales.quote',
|
|
104
|
+
resourceId: input.quoteId,
|
|
105
|
+
operation: 'update',
|
|
106
|
+
mutationPayload: input,
|
|
107
|
+
},
|
|
155
108
|
})
|
|
156
109
|
if (!guardResult.ok) {
|
|
157
|
-
return
|
|
110
|
+
return guardResult.response
|
|
158
111
|
}
|
|
159
112
|
|
|
160
113
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
@@ -232,18 +185,7 @@ export async function POST(req: Request) {
|
|
|
232
185
|
organizationId: quote.organizationId,
|
|
233
186
|
})
|
|
234
187
|
|
|
235
|
-
|
|
236
|
-
await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
|
|
237
|
-
tenantId: ctx.auth?.tenantId ?? '',
|
|
238
|
-
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
239
|
-
userId: ctx.auth?.sub ?? '',
|
|
240
|
-
resourceKind: 'sales.quote',
|
|
241
|
-
resourceId: input.quoteId,
|
|
242
|
-
operation: 'update',
|
|
243
|
-
requestMethod: req.method,
|
|
244
|
-
requestHeaders: req.headers,
|
|
245
|
-
})
|
|
246
|
-
}
|
|
188
|
+
await guardResult.runAfterSuccess()
|
|
247
189
|
|
|
248
190
|
return NextResponse.json({ ok: true })
|
|
249
191
|
} catch (err) {
|