@open-mercato/core 0.6.6-develop.6198.1.0822f97cc6 → 0.6.6-develop.6201.1.8ceb502c4b
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/integrations/backend/integrations/bundle/[id]/page.js +81 -37
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/page.js +45 -24
- package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
- package/dist/modules/notifications/api/[id]/action/route.js +18 -3
- package/dist/modules/notifications/api/[id]/action/route.js.map +2 -2
- package/dist/modules/notifications/api/[id]/restore/route.js +19 -3
- package/dist/modules/notifications/api/[id]/restore/route.js.map +2 -2
- package/dist/modules/notifications/api/mark-all-read/route.js +18 -4
- package/dist/modules/notifications/api/mark-all-read/route.js.map +2 -2
- package/dist/modules/notifications/api/route.js +17 -4
- package/dist/modules/notifications/api/route.js.map +2 -2
- package/dist/modules/notifications/api/settings/route.js +26 -5
- package/dist/modules/notifications/api/settings/route.js.map +2 -2
- package/dist/modules/notifications/lib/routeHelpers.js +64 -5
- package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +78 -35
- package/src/modules/integrations/backend/integrations/page.tsx +42 -21
- package/src/modules/notifications/api/[id]/action/route.ts +17 -2
- package/src/modules/notifications/api/[id]/restore/route.ts +19 -3
- package/src/modules/notifications/api/mark-all-read/route.ts +18 -4
- package/src/modules/notifications/api/route.ts +16 -3
- package/src/modules/notifications/api/settings/route.ts +26 -5
- package/src/modules/notifications/lib/routeHelpers.ts +101 -4
|
@@ -9,6 +9,7 @@ import { Input } from '@open-mercato/ui/primitives/input'
|
|
|
9
9
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
10
10
|
import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
11
11
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
12
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
12
13
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
13
14
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
14
15
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -145,6 +146,9 @@ export default function IntegrationsMarketplacePage() {
|
|
|
145
146
|
const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())
|
|
146
147
|
const scopeVersion = useOrganizationScopeVersion()
|
|
147
148
|
const t = useT()
|
|
149
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({
|
|
150
|
+
contextId: 'integrations.marketplace',
|
|
151
|
+
})
|
|
148
152
|
|
|
149
153
|
React.useEffect(() => {
|
|
150
154
|
const timer = setTimeout(() => setDebouncedSearch(searchInput.trim()), 300)
|
|
@@ -202,30 +206,47 @@ export default function IntegrationsMarketplacePage() {
|
|
|
202
206
|
|
|
203
207
|
const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean, updatedAt?: string | null) => {
|
|
204
208
|
setTogglingIds((prev) => new Set(prev).add(integrationId))
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
try {
|
|
210
|
+
const call = await runMutation({
|
|
211
|
+
mutationPayload: { integrationId, isEnabled: enabled },
|
|
212
|
+
context: {
|
|
213
|
+
formId: 'integrations.marketplace',
|
|
214
|
+
operation: 'update',
|
|
215
|
+
actionId: 'toggle-state',
|
|
216
|
+
resourceKind: 'integrations.integration',
|
|
217
|
+
resourceId: integrationId,
|
|
218
|
+
integrationId,
|
|
219
|
+
retryLastMutation,
|
|
220
|
+
},
|
|
221
|
+
operation: () => withScopedApiRequestHeaders(
|
|
222
|
+
buildOptimisticLockHeader(updatedAt),
|
|
223
|
+
() => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
|
|
224
|
+
method: 'PUT',
|
|
225
|
+
headers: { 'Content-Type': 'application/json' },
|
|
226
|
+
body: JSON.stringify({ isEnabled: enabled }),
|
|
227
|
+
}, { fallback: null }),
|
|
228
|
+
),
|
|
229
|
+
})
|
|
213
230
|
|
|
214
|
-
|
|
231
|
+
if (!call.ok) {
|
|
232
|
+
flash(t('integrations.detail.stateError'), 'error')
|
|
233
|
+
} else {
|
|
234
|
+
setData((prev) => {
|
|
235
|
+
if (!prev) return prev
|
|
236
|
+
return {
|
|
237
|
+
...prev,
|
|
238
|
+
items: prev.items.map((item) =>
|
|
239
|
+
item.id === integrationId ? { ...item, isEnabled: enabled } : item,
|
|
240
|
+
),
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
215
245
|
flash(t('integrations.detail.stateError'), 'error')
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (!prev) return prev
|
|
219
|
-
return {
|
|
220
|
-
...prev,
|
|
221
|
-
items: prev.items.map((item) =>
|
|
222
|
-
item.id === integrationId ? { ...item, isEnabled: enabled } : item,
|
|
223
|
-
),
|
|
224
|
-
}
|
|
225
|
-
})
|
|
246
|
+
} finally {
|
|
247
|
+
setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })
|
|
226
248
|
}
|
|
227
|
-
|
|
228
|
-
}, [t])
|
|
249
|
+
}, [retryLastMutation, runMutation, t])
|
|
229
250
|
|
|
230
251
|
const grouped = React.useMemo(() => {
|
|
231
252
|
if (!data) return { bundles: [] as Array<BundleItem & { integrations: IntegrationItem[] }>, standalone: [] as IntegrationItem[] }
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { executeActionSchema } from '../../../data/validators'
|
|
2
2
|
import { actionResultResponseSchema, errorResponseSchema } from '../../openapi'
|
|
3
3
|
import {
|
|
4
|
+
NOTIFICATION_RESOURCE_KIND,
|
|
4
5
|
notificationCrudErrorResponse,
|
|
5
6
|
notificationValidationErrorResponse,
|
|
6
7
|
resolveNotificationContext,
|
|
8
|
+
runGuardedNotificationWrite,
|
|
7
9
|
} from '../../../lib/routeHelpers'
|
|
8
10
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
11
|
|
|
@@ -13,7 +15,7 @@ export const metadata = {
|
|
|
13
15
|
|
|
14
16
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
15
17
|
const { id } = await params
|
|
16
|
-
const { service, scope } = await resolveNotificationContext(req)
|
|
18
|
+
const { service, scope, ctx } = await resolveNotificationContext(req)
|
|
17
19
|
|
|
18
20
|
const body = await req.json().catch(() => ({}))
|
|
19
21
|
const parsed = executeActionSchema.safeParse(body)
|
|
@@ -23,7 +25,20 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
23
25
|
const input = parsed.data
|
|
24
26
|
|
|
25
27
|
try {
|
|
26
|
-
const
|
|
28
|
+
const guarded = await runGuardedNotificationWrite(
|
|
29
|
+
ctx.container,
|
|
30
|
+
scope,
|
|
31
|
+
req,
|
|
32
|
+
{
|
|
33
|
+
resourceKind: NOTIFICATION_RESOURCE_KIND,
|
|
34
|
+
resourceId: id,
|
|
35
|
+
operation: 'custom',
|
|
36
|
+
payload: input as Record<string, unknown>,
|
|
37
|
+
},
|
|
38
|
+
() => service.executeAction(id, input, scope),
|
|
39
|
+
)
|
|
40
|
+
if (!guarded.ok) return guarded.response
|
|
41
|
+
const { notification, result } = guarded.result
|
|
27
42
|
|
|
28
43
|
const action = notification.actionData?.actions?.find((a) => a.id === input.actionId)
|
|
29
44
|
const href = action?.href?.replace('{sourceEntityId}', notification.sourceEntityId ?? '')
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { restoreNotificationSchema } from '../../../data/validators'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
NOTIFICATION_RESOURCE_KIND,
|
|
5
|
+
resolveNotificationContext,
|
|
6
|
+
runGuardedNotificationWrite,
|
|
7
|
+
} from '../../../lib/routeHelpers'
|
|
4
8
|
|
|
5
9
|
export const metadata = {
|
|
6
10
|
PUT: { requireAuth: true },
|
|
@@ -8,12 +12,24 @@ export const metadata = {
|
|
|
8
12
|
|
|
9
13
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
14
|
const { id } = await params
|
|
11
|
-
const { service, scope } = await resolveNotificationContext(req)
|
|
15
|
+
const { service, scope, ctx } = await resolveNotificationContext(req)
|
|
12
16
|
|
|
13
17
|
const body = await req.json().catch(() => ({}))
|
|
14
18
|
const input = restoreNotificationSchema.parse(body)
|
|
15
19
|
|
|
16
|
-
await
|
|
20
|
+
const guarded = await runGuardedNotificationWrite(
|
|
21
|
+
ctx.container,
|
|
22
|
+
scope,
|
|
23
|
+
req,
|
|
24
|
+
{
|
|
25
|
+
resourceKind: NOTIFICATION_RESOURCE_KIND,
|
|
26
|
+
resourceId: id,
|
|
27
|
+
operation: 'update',
|
|
28
|
+
payload: input as Record<string, unknown>,
|
|
29
|
+
},
|
|
30
|
+
() => service.restoreDismissed(id, input.status, scope),
|
|
31
|
+
)
|
|
32
|
+
if (!guarded.ok) return guarded.response
|
|
17
33
|
|
|
18
34
|
return Response.json({ ok: true })
|
|
19
35
|
}
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
NOTIFICATION_RESOURCE_KIND,
|
|
4
|
+
resolveNotificationContext,
|
|
5
|
+
runGuardedNotificationWrite,
|
|
6
|
+
} from '../../lib/routeHelpers'
|
|
3
7
|
|
|
4
8
|
export const metadata = {
|
|
5
9
|
PUT: { requireAuth: true },
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
export async function PUT(req: Request) {
|
|
9
|
-
const { service, scope } = await resolveNotificationContext(req)
|
|
13
|
+
const { service, scope, ctx } = await resolveNotificationContext(req)
|
|
10
14
|
|
|
11
|
-
const
|
|
15
|
+
const guarded = await runGuardedNotificationWrite(
|
|
16
|
+
ctx.container,
|
|
17
|
+
scope,
|
|
18
|
+
req,
|
|
19
|
+
{
|
|
20
|
+
resourceKind: NOTIFICATION_RESOURCE_KIND,
|
|
21
|
+
operation: 'update',
|
|
22
|
+
},
|
|
23
|
+
() => service.markAllAsRead(scope),
|
|
24
|
+
)
|
|
25
|
+
if (!guarded.ok) return guarded.response
|
|
12
26
|
|
|
13
|
-
return Response.json({ ok: true, count })
|
|
27
|
+
return Response.json({ ok: true, count: guarded.result })
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
export const openApi = {
|
|
@@ -4,9 +4,11 @@ import { Notification } from '../data/entities'
|
|
|
4
4
|
import { listNotificationsSchema, createNotificationSchema } from '../data/validators'
|
|
5
5
|
import { toNotificationDto } from '../lib/notificationMapper'
|
|
6
6
|
import {
|
|
7
|
+
NOTIFICATION_RESOURCE_KIND,
|
|
7
8
|
notificationCrudErrorResponse,
|
|
8
9
|
notificationValidationErrorResponse,
|
|
9
10
|
resolveNotificationContext,
|
|
11
|
+
runGuardedNotificationWrite,
|
|
10
12
|
} from '../lib/routeHelpers'
|
|
11
13
|
import {
|
|
12
14
|
buildNotificationsCrudOpenApi,
|
|
@@ -74,7 +76,7 @@ export async function GET(req: Request) {
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
export async function POST(req: Request) {
|
|
77
|
-
const { service, scope } = await resolveNotificationContext(req)
|
|
79
|
+
const { service, scope, ctx } = await resolveNotificationContext(req)
|
|
78
80
|
|
|
79
81
|
const body = await req.json().catch(() => ({}))
|
|
80
82
|
const parsed = createNotificationSchema.safeParse(body)
|
|
@@ -83,9 +85,20 @@ export async function POST(req: Request) {
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
try {
|
|
86
|
-
const
|
|
88
|
+
const guarded = await runGuardedNotificationWrite(
|
|
89
|
+
ctx.container,
|
|
90
|
+
scope,
|
|
91
|
+
req,
|
|
92
|
+
{
|
|
93
|
+
resourceKind: NOTIFICATION_RESOURCE_KIND,
|
|
94
|
+
operation: 'create',
|
|
95
|
+
payload: parsed.data as Record<string, unknown>,
|
|
96
|
+
},
|
|
97
|
+
() => service.create(parsed.data, scope),
|
|
98
|
+
)
|
|
99
|
+
if (!guarded.ok) return guarded.response
|
|
87
100
|
|
|
88
|
-
return Response.json({ id:
|
|
101
|
+
return Response.json({ id: guarded.result.id }, { status: 201 })
|
|
89
102
|
} catch (error) {
|
|
90
103
|
const errorResponse = notificationCrudErrorResponse(error)
|
|
91
104
|
if (errorResponse) return errorResponse
|
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
resolveNotificationDeliveryConfig,
|
|
14
14
|
saveNotificationDeliveryConfig,
|
|
15
15
|
} from '../../lib/deliveryConfig'
|
|
16
|
+
import {
|
|
17
|
+
NOTIFICATION_SETTINGS_RESOURCE_KIND,
|
|
18
|
+
runGuardedNotificationWrite,
|
|
19
|
+
} from '../../lib/routeHelpers'
|
|
16
20
|
|
|
17
21
|
export const metadata = {
|
|
18
22
|
GET: { requireAuth: true, requireFeatures: ['notifications.manage'] },
|
|
@@ -67,11 +71,28 @@ export async function POST(req: Request) {
|
|
|
67
71
|
|
|
68
72
|
const container = await createRequestContainer()
|
|
69
73
|
try {
|
|
70
|
-
await
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const guarded = await runGuardedNotificationWrite(
|
|
75
|
+
container,
|
|
76
|
+
{
|
|
77
|
+
tenantId: auth.tenantId ?? '',
|
|
78
|
+
organizationId: auth.orgId ?? null,
|
|
79
|
+
userId: auth.sub ?? null,
|
|
80
|
+
},
|
|
81
|
+
req,
|
|
82
|
+
{
|
|
83
|
+
resourceKind: NOTIFICATION_SETTINGS_RESOURCE_KIND,
|
|
84
|
+
operation: 'update',
|
|
85
|
+
payload: parsed.data as Record<string, unknown>,
|
|
86
|
+
},
|
|
87
|
+
async () => {
|
|
88
|
+
await saveNotificationDeliveryConfig(container, parsed.data)
|
|
89
|
+
return resolveNotificationDeliveryConfig(container, {
|
|
90
|
+
defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
if (!guarded.ok) return guarded.response
|
|
95
|
+
return NextResponse.json({ ok: true, settings: guarded.result })
|
|
75
96
|
} catch (error) {
|
|
76
97
|
return NextResponse.json(
|
|
77
98
|
{ error: error instanceof Error ? error.message : t('api.errors.internal', 'Internal error') },
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
+
import type { AwilixContainer } from 'awilix'
|
|
2
3
|
import { resolveRequestContext } from '@open-mercato/shared/lib/api/context'
|
|
3
4
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
|
+
import {
|
|
6
|
+
runCrudMutationGuardAfterSuccess,
|
|
7
|
+
validateCrudMutationGuard,
|
|
8
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
4
9
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
5
10
|
import { resolveNotificationService, type NotificationService } from './notificationService'
|
|
6
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Mutation-guard resource kind for notification rows.
|
|
14
|
+
*/
|
|
15
|
+
export const NOTIFICATION_RESOURCE_KIND = 'notifications.notification'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Mutation-guard resource kind for notification delivery settings.
|
|
19
|
+
*/
|
|
20
|
+
export const NOTIFICATION_SETTINGS_RESOURCE_KIND = 'notifications.settings'
|
|
21
|
+
|
|
7
22
|
/**
|
|
8
23
|
* Notification scope context for service calls
|
|
9
24
|
*/
|
|
@@ -63,6 +78,65 @@ export async function resolveNotificationContext(req: Request): Promise<Notifica
|
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Mutation-guard options for a notification write.
|
|
83
|
+
*/
|
|
84
|
+
export interface NotificationMutationGuardOptions {
|
|
85
|
+
resourceKind: string
|
|
86
|
+
resourceId?: string | null
|
|
87
|
+
operation: 'create' | 'update' | 'delete' | 'custom'
|
|
88
|
+
payload?: Record<string, unknown> | null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type GuardedNotificationWriteResult<T> =
|
|
92
|
+
| { ok: true; result: T }
|
|
93
|
+
| { ok: false; response: Response }
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Run a notification write through the mutation guard lifecycle.
|
|
97
|
+
* Validates before the mutation, performs the write, then runs after-success
|
|
98
|
+
* hooks only when the write succeeded and the guard requested them. Returns the
|
|
99
|
+
* guard's own block response when validation fails so authorization behavior and
|
|
100
|
+
* conflict shapes are preserved.
|
|
101
|
+
*/
|
|
102
|
+
export async function runGuardedNotificationWrite<T>(
|
|
103
|
+
container: AwilixContainer,
|
|
104
|
+
scope: NotificationScope,
|
|
105
|
+
req: Request,
|
|
106
|
+
options: NotificationMutationGuardOptions,
|
|
107
|
+
write: () => Promise<T>,
|
|
108
|
+
): Promise<GuardedNotificationWriteResult<T>> {
|
|
109
|
+
const guardScope = {
|
|
110
|
+
tenantId: scope.tenantId,
|
|
111
|
+
organizationId: scope.organizationId,
|
|
112
|
+
userId: scope.userId ?? '',
|
|
113
|
+
resourceKind: options.resourceKind,
|
|
114
|
+
resourceId: options.resourceId ?? '',
|
|
115
|
+
operation: options.operation,
|
|
116
|
+
requestMethod: req.method,
|
|
117
|
+
requestHeaders: req.headers,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
121
|
+
...guardScope,
|
|
122
|
+
mutationPayload: options.payload ?? null,
|
|
123
|
+
})
|
|
124
|
+
if (guardResult && !guardResult.ok) {
|
|
125
|
+
return { ok: false, response: Response.json(guardResult.body, { status: guardResult.status }) }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = await write()
|
|
129
|
+
|
|
130
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
131
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
132
|
+
...guardScope,
|
|
133
|
+
metadata: guardResult.metadata ?? null,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { ok: true, result }
|
|
138
|
+
}
|
|
139
|
+
|
|
66
140
|
/**
|
|
67
141
|
* Create a POST handler for bulk notification creation routes.
|
|
68
142
|
* Used by batch, role, and feature notification endpoints.
|
|
@@ -72,7 +146,7 @@ export function createBulkNotificationRoute<TSchema extends z.ZodTypeAny>(
|
|
|
72
146
|
serviceMethod: 'createBatch' | 'createForRole' | 'createForFeature'
|
|
73
147
|
) {
|
|
74
148
|
return async function POST(req: Request) {
|
|
75
|
-
const { service, scope } = await resolveNotificationContext(req)
|
|
149
|
+
const { service, scope, ctx } = await resolveNotificationContext(req)
|
|
76
150
|
|
|
77
151
|
const body = await req.json().catch(() => ({}))
|
|
78
152
|
const parsed = schema.safeParse(body)
|
|
@@ -81,7 +155,19 @@ export function createBulkNotificationRoute<TSchema extends z.ZodTypeAny>(
|
|
|
81
155
|
}
|
|
82
156
|
|
|
83
157
|
try {
|
|
84
|
-
const
|
|
158
|
+
const guarded = await runGuardedNotificationWrite(
|
|
159
|
+
ctx.container,
|
|
160
|
+
scope,
|
|
161
|
+
req,
|
|
162
|
+
{
|
|
163
|
+
resourceKind: NOTIFICATION_RESOURCE_KIND,
|
|
164
|
+
operation: 'create',
|
|
165
|
+
payload: parsed.data as Record<string, unknown>,
|
|
166
|
+
},
|
|
167
|
+
() => service[serviceMethod](parsed.data as never, scope),
|
|
168
|
+
)
|
|
169
|
+
if (!guarded.ok) return guarded.response
|
|
170
|
+
const notifications = guarded.result
|
|
85
171
|
|
|
86
172
|
return Response.json({
|
|
87
173
|
ok: true,
|
|
@@ -144,10 +230,21 @@ export function createSingleNotificationActionRoute(
|
|
|
144
230
|
) {
|
|
145
231
|
return async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
146
232
|
const { id } = await params
|
|
147
|
-
const { service, scope } = await resolveNotificationContext(req)
|
|
233
|
+
const { service, scope, ctx } = await resolveNotificationContext(req)
|
|
148
234
|
|
|
149
235
|
try {
|
|
150
|
-
await
|
|
236
|
+
const guarded = await runGuardedNotificationWrite(
|
|
237
|
+
ctx.container,
|
|
238
|
+
scope,
|
|
239
|
+
req,
|
|
240
|
+
{
|
|
241
|
+
resourceKind: NOTIFICATION_RESOURCE_KIND,
|
|
242
|
+
resourceId: id,
|
|
243
|
+
operation: 'update',
|
|
244
|
+
},
|
|
245
|
+
() => service[serviceMethod](id, scope),
|
|
246
|
+
)
|
|
247
|
+
if (!guarded.ok) return guarded.response
|
|
151
248
|
} catch (error) {
|
|
152
249
|
const errorResponse = notificationCrudErrorResponse(error)
|
|
153
250
|
if (errorResponse) return errorResponse
|