@open-mercato/core 0.6.6-develop.6184.1.b7e55f8d61 → 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.
Files changed (63) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/frontend/login.js +5 -5
  3. package/dist/modules/auth/frontend/login.js.map +2 -2
  4. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +3 -2
  5. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
  6. package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +26 -14
  7. package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
  8. package/dist/modules/customers/api/deals/[id]/route.js +169 -149
  9. package/dist/modules/customers/api/deals/[id]/route.js.map +3 -3
  10. package/dist/modules/customers/components/detail/InlineEditors.js +9 -4
  11. package/dist/modules/customers/components/detail/InlineEditors.js.map +2 -2
  12. package/dist/modules/integrations/backend/integrations/[id]/page.js +13 -9
  13. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  14. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +81 -37
  15. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  16. package/dist/modules/integrations/backend/integrations/detail-page-refresh.js +17 -0
  17. package/dist/modules/integrations/backend/integrations/detail-page-refresh.js.map +7 -0
  18. package/dist/modules/integrations/backend/integrations/page.js +45 -24
  19. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  20. package/dist/modules/notifications/api/[id]/action/route.js +18 -3
  21. package/dist/modules/notifications/api/[id]/action/route.js.map +2 -2
  22. package/dist/modules/notifications/api/[id]/restore/route.js +19 -3
  23. package/dist/modules/notifications/api/[id]/restore/route.js.map +2 -2
  24. package/dist/modules/notifications/api/mark-all-read/route.js +18 -4
  25. package/dist/modules/notifications/api/mark-all-read/route.js.map +2 -2
  26. package/dist/modules/notifications/api/route.js +17 -4
  27. package/dist/modules/notifications/api/route.js.map +2 -2
  28. package/dist/modules/notifications/api/settings/route.js +26 -5
  29. package/dist/modules/notifications/api/settings/route.js.map +2 -2
  30. package/dist/modules/notifications/lib/routeHelpers.js +64 -5
  31. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  32. package/dist/modules/sales/components/documents/SalesDocumentForm.js +299 -297
  33. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  34. package/dist/modules/workflows/components/EdgeEditDialog.js +120 -141
  35. package/dist/modules/workflows/components/EdgeEditDialog.js.map +2 -2
  36. package/dist/modules/workflows/components/NodeEditDialog.js +114 -118
  37. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  38. package/dist/modules/workflows/frontend/checkout-demo/page.js +155 -260
  39. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  40. package/package.json +7 -7
  41. package/src/modules/auth/frontend/login.tsx +5 -5
  42. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -3
  43. package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +27 -17
  44. package/src/modules/customers/api/deals/[id]/route.ts +208 -174
  45. package/src/modules/customers/components/detail/InlineEditors.tsx +6 -10
  46. package/src/modules/integrations/backend/integrations/[id]/page.tsx +13 -9
  47. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +78 -35
  48. package/src/modules/integrations/backend/integrations/detail-page-refresh.ts +36 -0
  49. package/src/modules/integrations/backend/integrations/page.tsx +42 -21
  50. package/src/modules/notifications/api/[id]/action/route.ts +17 -2
  51. package/src/modules/notifications/api/[id]/restore/route.ts +19 -3
  52. package/src/modules/notifications/api/mark-all-read/route.ts +18 -4
  53. package/src/modules/notifications/api/route.ts +16 -3
  54. package/src/modules/notifications/api/settings/route.ts +26 -5
  55. package/src/modules/notifications/lib/routeHelpers.ts +101 -4
  56. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +351 -327
  57. package/src/modules/workflows/components/EdgeEditDialog.tsx +132 -157
  58. package/src/modules/workflows/components/NodeEditDialog.tsx +116 -121
  59. package/src/modules/workflows/frontend/checkout-demo/page.tsx +172 -239
  60. package/src/modules/workflows/i18n/de.json +2 -0
  61. package/src/modules/workflows/i18n/en.json +2 -0
  62. package/src/modules/workflows/i18n/es.json +2 -0
  63. package/src/modules/workflows/i18n/pl.json +2 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/notifications/api/%5Bid%5D/action/route.ts"],
4
- "sourcesContent": ["import { executeActionSchema } from '../../../data/validators'\nimport { actionResultResponseSchema, errorResponseSchema } from '../../openapi'\nimport {\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n} from '../../../lib/routeHelpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = executeActionSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n const input = parsed.data\n\n try {\n const { notification, result } = await service.executeAction(id, input, scope)\n\n const action = notification.actionData?.actions?.find((a) => a.id === input.actionId)\n const href = action?.href?.replace('{sourceEntityId}', notification.sourceEntityId ?? '')\n\n return Response.json({\n ok: true,\n result,\n href,\n })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n\n const { t } = await resolveTranslations()\n const fallback = t('notifications.error.action', 'Failed to execute action')\n const message = error instanceof Error && error.message ? error.message : fallback\n return Response.json({ error: message }, { status: 400 })\n }\n}\n\nexport const openApi = {\n POST: {\n summary: 'Execute notification action',\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: executeActionSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Action executed successfully',\n content: {\n 'application/json': {\n schema: actionResultResponseSchema,\n },\n },\n },\n 400: {\n description: 'Action not found or failed',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,2BAA2B;AAChE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,KAAK;AAC5B;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAAwC;AACxF,QAAM,EAAE,GAAG,IAAI,MAAM;AACrB,QAAM,EAAE,SAAS,MAAM,IAAI,MAAM,2BAA2B,GAAG;AAE/D,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,SAAS,oBAAoB,UAAU,IAAI;AACjD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,oCAAoC,OAAO,KAAK;AAAA,EACzD;AACA,QAAM,QAAQ,OAAO;AAErB,MAAI;AACF,UAAM,EAAE,cAAc,OAAO,IAAI,MAAM,QAAQ,cAAc,IAAI,OAAO,KAAK;AAE7E,UAAM,SAAS,aAAa,YAAY,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,QAAQ;AACpF,UAAM,OAAO,QAAQ,MAAM,QAAQ,oBAAoB,aAAa,kBAAkB,EAAE;AAExF,WAAO,SAAS,KAAK;AAAA,MACnB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,gBAAgB,8BAA8B,KAAK;AACzD,QAAI,cAAe,QAAO;AAE1B,UAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,UAAM,WAAW,EAAE,8BAA8B,0BAA0B;AAC3E,UAAM,UAAU,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU;AAC1E,WAAO,SAAS,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB;AAAA,UAClB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { executeActionSchema } from '../../../data/validators'\nimport { actionResultResponseSchema, errorResponseSchema } from '../../openapi'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n} from '../../../lib/routeHelpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = executeActionSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n const input = parsed.data\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n resourceId: id,\n operation: 'custom',\n payload: input as Record<string, unknown>,\n },\n () => service.executeAction(id, input, scope),\n )\n if (!guarded.ok) return guarded.response\n const { notification, result } = guarded.result\n\n const action = notification.actionData?.actions?.find((a) => a.id === input.actionId)\n const href = action?.href?.replace('{sourceEntityId}', notification.sourceEntityId ?? '')\n\n return Response.json({\n ok: true,\n result,\n href,\n })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n\n const { t } = await resolveTranslations()\n const fallback = t('notifications.error.action', 'Failed to execute action')\n const message = error instanceof Error && error.message ? error.message : fallback\n return Response.json({ error: message }, { status: 400 })\n }\n}\n\nexport const openApi = {\n POST: {\n summary: 'Execute notification action',\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: executeActionSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Action executed successfully',\n content: {\n 'application/json': {\n schema: actionResultResponseSchema,\n },\n },\n },\n 400: {\n description: 'Action not found or failed',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,2BAA2B;AAChE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,KAAK;AAC5B;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAAwC;AACxF,QAAM,EAAE,GAAG,IAAI,MAAM;AACrB,QAAM,EAAE,SAAS,OAAO,IAAI,IAAI,MAAM,2BAA2B,GAAG;AAEpE,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,SAAS,oBAAoB,UAAU,IAAI;AACjD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,oCAAoC,OAAO,KAAK;AAAA,EACzD;AACA,QAAM,QAAQ,OAAO;AAErB,MAAI;AACF,UAAM,UAAU,MAAM;AAAA,MACpB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,QACE,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,SAAS;AAAA,MACX;AAAA,MACA,MAAM,QAAQ,cAAc,IAAI,OAAO,KAAK;AAAA,IAC9C;AACA,QAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAChC,UAAM,EAAE,cAAc,OAAO,IAAI,QAAQ;AAEzC,UAAM,SAAS,aAAa,YAAY,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,QAAQ;AACpF,UAAM,OAAO,QAAQ,MAAM,QAAQ,oBAAoB,aAAa,kBAAkB,EAAE;AAExF,WAAO,SAAS,KAAK;AAAA,MACnB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,gBAAgB,8BAA8B,KAAK;AACzD,QAAI,cAAe,QAAO;AAE1B,UAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,UAAM,WAAW,EAAE,8BAA8B,0BAA0B;AAC3E,UAAM,UAAU,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU;AAC1E,WAAO,SAAS,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB;AAAA,UAClB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,15 +1,31 @@
1
1
  import { z } from "zod";
2
2
  import { restoreNotificationSchema } from "../../../data/validators.js";
3
- import { resolveNotificationContext } from "../../../lib/routeHelpers.js";
3
+ import {
4
+ NOTIFICATION_RESOURCE_KIND,
5
+ resolveNotificationContext,
6
+ runGuardedNotificationWrite
7
+ } from "../../../lib/routeHelpers.js";
4
8
  const metadata = {
5
9
  PUT: { requireAuth: true }
6
10
  };
7
11
  async function PUT(req, { params }) {
8
12
  const { id } = await params;
9
- const { service, scope } = await resolveNotificationContext(req);
13
+ const { service, scope, ctx } = await resolveNotificationContext(req);
10
14
  const body = await req.json().catch(() => ({}));
11
15
  const input = restoreNotificationSchema.parse(body);
12
- await service.restoreDismissed(id, input.status, scope);
16
+ const guarded = await runGuardedNotificationWrite(
17
+ ctx.container,
18
+ scope,
19
+ req,
20
+ {
21
+ resourceKind: NOTIFICATION_RESOURCE_KIND,
22
+ resourceId: id,
23
+ operation: "update",
24
+ payload: input
25
+ },
26
+ () => service.restoreDismissed(id, input.status, scope)
27
+ );
28
+ if (!guarded.ok) return guarded.response;
13
29
  return Response.json({ ok: true });
14
30
  }
15
31
  const openApi = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/notifications/api/%5Bid%5D/restore/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport { restoreNotificationSchema } from '../../../data/validators'\nimport { resolveNotificationContext } from '../../../lib/routeHelpers'\n\nexport const metadata = {\n PUT: { requireAuth: true },\n}\n\nexport async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const input = restoreNotificationSchema.parse(body)\n\n await service.restoreDismissed(id, input.status, scope)\n\n return Response.json({ ok: true })\n}\n\nexport const openApi = {\n PUT: {\n summary: 'Restore dismissed notification',\n description: 'Undo a dismissal and restore a notification to read or unread.',\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n requestBody: {\n required: false,\n content: {\n 'application/json': {\n schema: restoreNotificationSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Notification restored',\n content: {\n 'application/json': {\n schema: z.object({ ok: z.boolean() }),\n },\n },\n },\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,iCAAiC;AAC1C,SAAS,kCAAkC;AAEpC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAAwC;AACvF,QAAM,EAAE,GAAG,IAAI,MAAM;AACrB,QAAM,EAAE,SAAS,MAAM,IAAI,MAAM,2BAA2B,GAAG;AAE/D,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,QAAQ,0BAA0B,MAAM,IAAI;AAElD,QAAM,QAAQ,iBAAiB,IAAI,MAAM,QAAQ,KAAK;AAEtD,SAAO,SAAS,KAAK,EAAE,IAAI,KAAK,CAAC;AACnC;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,aAAa;AAAA,IACb,MAAM,CAAC,eAAe;AAAA,IACtB,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB;AAAA,UAClB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { restoreNotificationSchema } from '../../../data/validators'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n} from '../../../lib/routeHelpers'\n\nexport const metadata = {\n PUT: { requireAuth: true },\n}\n\nexport async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const input = restoreNotificationSchema.parse(body)\n\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n resourceId: id,\n operation: 'update',\n payload: input as Record<string, unknown>,\n },\n () => service.restoreDismissed(id, input.status, scope),\n )\n if (!guarded.ok) return guarded.response\n\n return Response.json({ ok: true })\n}\n\nexport const openApi = {\n PUT: {\n summary: 'Restore dismissed notification',\n description: 'Undo a dismissal and restore a notification to read or unread.',\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n requestBody: {\n required: false,\n content: {\n 'application/json': {\n schema: restoreNotificationSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Notification restored',\n content: {\n 'application/json': {\n schema: z.object({ ok: z.boolean() }),\n },\n },\n },\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,iCAAiC;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAAwC;AACvF,QAAM,EAAE,GAAG,IAAI,MAAM;AACrB,QAAM,EAAE,SAAS,OAAO,IAAI,IAAI,MAAM,2BAA2B,GAAG;AAEpE,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,QAAQ,0BAA0B,MAAM,IAAI;AAElD,QAAM,UAAU,MAAM;AAAA,IACpB,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,SAAS;AAAA,IACX;AAAA,IACA,MAAM,QAAQ,iBAAiB,IAAI,MAAM,QAAQ,KAAK;AAAA,EACxD;AACA,MAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAEhC,SAAO,SAAS,KAAK,EAAE,IAAI,KAAK,CAAC;AACnC;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,aAAa;AAAA,IACb,MAAM,CAAC,eAAe;AAAA,IACtB,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB;AAAA,UAClB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,12 +1,26 @@
1
1
  import { z } from "zod";
2
- import { resolveNotificationContext } from "../../lib/routeHelpers.js";
2
+ import {
3
+ NOTIFICATION_RESOURCE_KIND,
4
+ resolveNotificationContext,
5
+ runGuardedNotificationWrite
6
+ } from "../../lib/routeHelpers.js";
3
7
  const metadata = {
4
8
  PUT: { requireAuth: true }
5
9
  };
6
10
  async function PUT(req) {
7
- const { service, scope } = await resolveNotificationContext(req);
8
- const count = await service.markAllAsRead(scope);
9
- return Response.json({ ok: true, count });
11
+ const { service, scope, ctx } = await resolveNotificationContext(req);
12
+ const guarded = await runGuardedNotificationWrite(
13
+ ctx.container,
14
+ scope,
15
+ req,
16
+ {
17
+ resourceKind: NOTIFICATION_RESOURCE_KIND,
18
+ operation: "update"
19
+ },
20
+ () => service.markAllAsRead(scope)
21
+ );
22
+ if (!guarded.ok) return guarded.response;
23
+ return Response.json({ ok: true, count: guarded.result });
10
24
  }
11
25
  const openApi = {
12
26
  PUT: {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/notifications/api/mark-all-read/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport { resolveNotificationContext } from '../../lib/routeHelpers'\n\nexport const metadata = {\n PUT: { requireAuth: true },\n}\n\nexport async function PUT(req: Request) {\n const { service, scope } = await resolveNotificationContext(req)\n\n const count = await service.markAllAsRead(scope)\n\n return Response.json({ ok: true, count })\n}\n\nexport const openApi = {\n PUT: {\n summary: 'Mark all notifications as read',\n tags: ['Notifications'],\n responses: {\n 200: {\n description: 'All notifications marked as read',\n content: {\n 'application/json': {\n schema: z.object({\n ok: z.boolean(),\n count: z.number(),\n }),\n },\n },\n },\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,kCAAkC;AAEpC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,EAAE,SAAS,MAAM,IAAI,MAAM,2BAA2B,GAAG;AAE/D,QAAM,QAAQ,MAAM,QAAQ,cAAc,KAAK;AAE/C,SAAO,SAAS,KAAK,EAAE,IAAI,MAAM,MAAM,CAAC;AAC1C;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ,EAAE,OAAO;AAAA,cACf,IAAI,EAAE,QAAQ;AAAA,cACd,OAAO,EAAE,OAAO;AAAA,YAClB,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n} from '../../lib/routeHelpers'\n\nexport const metadata = {\n PUT: { requireAuth: true },\n}\n\nexport async function PUT(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'update',\n },\n () => service.markAllAsRead(scope),\n )\n if (!guarded.ok) return guarded.response\n\n return Response.json({ ok: true, count: guarded.result })\n}\n\nexport const openApi = {\n PUT: {\n summary: 'Mark all notifications as read',\n tags: ['Notifications'],\n responses: {\n 200: {\n description: 'All notifications marked as read',\n content: {\n 'application/json': {\n schema: z.object({\n ok: z.boolean(),\n count: z.number(),\n }),\n },\n },\n },\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,EAAE,SAAS,OAAO,IAAI,IAAI,MAAM,2BAA2B,GAAG;AAEpE,QAAM,UAAU,MAAM;AAAA,IACpB,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,WAAW;AAAA,IACb;AAAA,IACA,MAAM,QAAQ,cAAc,KAAK;AAAA,EACnC;AACA,MAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAEhC,SAAO,SAAS,KAAK,EAAE,IAAI,MAAM,OAAO,QAAQ,OAAO,CAAC;AAC1D;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ,EAAE,OAAO;AAAA,cACf,IAAI,EAAE,QAAQ;AAAA,cACd,OAAO,EAAE,OAAO;AAAA,YAClB,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -3,9 +3,11 @@ import { Notification } from "../data/entities.js";
3
3
  import { listNotificationsSchema, createNotificationSchema } from "../data/validators.js";
4
4
  import { toNotificationDto } from "../lib/notificationMapper.js";
5
5
  import {
6
+ NOTIFICATION_RESOURCE_KIND,
6
7
  notificationCrudErrorResponse,
7
8
  notificationValidationErrorResponse,
8
- resolveNotificationContext
9
+ resolveNotificationContext,
10
+ runGuardedNotificationWrite
9
11
  } from "../lib/routeHelpers.js";
10
12
  import {
11
13
  buildNotificationsCrudOpenApi,
@@ -64,15 +66,26 @@ async function GET(req) {
64
66
  });
65
67
  }
66
68
  async function POST(req) {
67
- const { service, scope } = await resolveNotificationContext(req);
69
+ const { service, scope, ctx } = await resolveNotificationContext(req);
68
70
  const body = await req.json().catch(() => ({}));
69
71
  const parsed = createNotificationSchema.safeParse(body);
70
72
  if (!parsed.success) {
71
73
  return notificationValidationErrorResponse(parsed.error);
72
74
  }
73
75
  try {
74
- const notification = await service.create(parsed.data, scope);
75
- return Response.json({ id: notification.id }, { status: 201 });
76
+ const guarded = await runGuardedNotificationWrite(
77
+ ctx.container,
78
+ scope,
79
+ req,
80
+ {
81
+ resourceKind: NOTIFICATION_RESOURCE_KIND,
82
+ operation: "create",
83
+ payload: parsed.data
84
+ },
85
+ () => service.create(parsed.data, scope)
86
+ );
87
+ if (!guarded.ok) return guarded.response;
88
+ return Response.json({ id: guarded.result.id }, { status: 201 });
76
89
  } catch (error) {
77
90
  const errorResponse = notificationCrudErrorResponse(error);
78
91
  if (errorResponse) return errorResponse;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/api/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { Notification } from '../data/entities'\nimport { listNotificationsSchema, createNotificationSchema } from '../data/validators'\nimport { toNotificationDto } from '../lib/notificationMapper'\nimport {\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n} from '../lib/routeHelpers'\nimport {\n buildNotificationsCrudOpenApi,\n createPagedListResponseSchema,\n notificationItemSchema,\n} from './openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['notifications.create'] },\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveNotificationContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n\n const url = new URL(req.url)\n const queryParams = Object.fromEntries(url.searchParams.entries())\n const input = listNotificationsSchema.parse(queryParams)\n\n const filters: Record<string, unknown> = {\n recipientUserId: scope.userId,\n tenantId: scope.tenantId,\n }\n\n if (input.status) {\n filters.status = Array.isArray(input.status) ? { $in: input.status } : input.status\n } else {\n filters.status = { $ne: 'dismissed' }\n }\n if (input.type) {\n filters.type = input.type\n }\n if (input.severity) {\n filters.severity = input.severity\n }\n if (input.sourceEntityType) {\n filters.sourceEntityType = input.sourceEntityType\n }\n if (input.sourceEntityId) {\n filters.sourceEntityId = input.sourceEntityId\n }\n if (input.since) {\n filters.createdAt = { $gt: new Date(input.since) }\n }\n\n const [notifications, total] = await Promise.all([\n em.find(Notification, filters, {\n orderBy: { createdAt: 'desc' },\n limit: input.pageSize,\n offset: (input.page - 1) * input.pageSize,\n }),\n em.count(Notification, filters),\n ])\n\n const items = notifications.map(toNotificationDto)\n\n return Response.json({\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n totalPages: Math.ceil(total / input.pageSize),\n })\n}\n\nexport async function POST(req: Request) {\n const { service, scope } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = createNotificationSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const notification = await service.create(parsed.data, scope)\n\n return Response.json({ id: notification.id }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n}\n\nexport const openApi = buildNotificationsCrudOpenApi({\n resourceName: 'Notification',\n querySchema: listNotificationsSchema,\n listResponseSchema: createPagedListResponseSchema(notificationItemSchema),\n create: {\n schema: createNotificationSchema,\n responseSchema: z.object({ id: z.string().uuid() }),\n description: 'Creates a notification for a user.',\n },\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { Notification } from '../data/entities'\nimport { listNotificationsSchema, createNotificationSchema } from '../data/validators'\nimport { toNotificationDto } from '../lib/notificationMapper'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n} from '../lib/routeHelpers'\nimport {\n buildNotificationsCrudOpenApi,\n createPagedListResponseSchema,\n notificationItemSchema,\n} from './openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['notifications.create'] },\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveNotificationContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n\n const url = new URL(req.url)\n const queryParams = Object.fromEntries(url.searchParams.entries())\n const input = listNotificationsSchema.parse(queryParams)\n\n const filters: Record<string, unknown> = {\n recipientUserId: scope.userId,\n tenantId: scope.tenantId,\n }\n\n if (input.status) {\n filters.status = Array.isArray(input.status) ? { $in: input.status } : input.status\n } else {\n filters.status = { $ne: 'dismissed' }\n }\n if (input.type) {\n filters.type = input.type\n }\n if (input.severity) {\n filters.severity = input.severity\n }\n if (input.sourceEntityType) {\n filters.sourceEntityType = input.sourceEntityType\n }\n if (input.sourceEntityId) {\n filters.sourceEntityId = input.sourceEntityId\n }\n if (input.since) {\n filters.createdAt = { $gt: new Date(input.since) }\n }\n\n const [notifications, total] = await Promise.all([\n em.find(Notification, filters, {\n orderBy: { createdAt: 'desc' },\n limit: input.pageSize,\n offset: (input.page - 1) * input.pageSize,\n }),\n em.count(Notification, filters),\n ])\n\n const items = notifications.map(toNotificationDto)\n\n return Response.json({\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n totalPages: Math.ceil(total / input.pageSize),\n })\n}\n\nexport async function POST(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = createNotificationSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'create',\n payload: parsed.data as Record<string, unknown>,\n },\n () => service.create(parsed.data, scope),\n )\n if (!guarded.ok) return guarded.response\n\n return Response.json({ id: guarded.result.id }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n}\n\nexport const openApi = buildNotificationsCrudOpenApi({\n resourceName: 'Notification',\n querySchema: listNotificationsSchema,\n listResponseSchema: createPagedListResponseSchema(notificationItemSchema),\n create: {\n schema: createNotificationSchema,\n responseSchema: z.object({ id: z.string().uuid() }),\n description: 'Creates a notification for a user.',\n },\n})\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB,gCAAgC;AAClE,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAAA,EACzB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACvE;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,EAAE,KAAK,MAAM,IAAI,MAAM,2BAA2B,GAAG;AAC3D,QAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AAErC,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,cAAc,OAAO,YAAY,IAAI,aAAa,QAAQ,CAAC;AACjE,QAAM,QAAQ,wBAAwB,MAAM,WAAW;AAEvD,QAAM,UAAmC;AAAA,IACvC,iBAAiB,MAAM;AAAA,IACvB,UAAU,MAAM;AAAA,EAClB;AAEA,MAAI,MAAM,QAAQ;AAChB,YAAQ,SAAS,MAAM,QAAQ,MAAM,MAAM,IAAI,EAAE,KAAK,MAAM,OAAO,IAAI,MAAM;AAAA,EAC/E,OAAO;AACL,YAAQ,SAAS,EAAE,KAAK,YAAY;AAAA,EACtC;AACA,MAAI,MAAM,MAAM;AACd,YAAQ,OAAO,MAAM;AAAA,EACvB;AACA,MAAI,MAAM,UAAU;AAClB,YAAQ,WAAW,MAAM;AAAA,EAC3B;AACA,MAAI,MAAM,kBAAkB;AAC1B,YAAQ,mBAAmB,MAAM;AAAA,EACnC;AACA,MAAI,MAAM,gBAAgB;AACxB,YAAQ,iBAAiB,MAAM;AAAA,EACjC;AACA,MAAI,MAAM,OAAO;AACf,YAAQ,YAAY,EAAE,KAAK,IAAI,KAAK,MAAM,KAAK,EAAE;AAAA,EACnD;AAEA,QAAM,CAAC,eAAe,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/C,GAAG,KAAK,cAAc,SAAS;AAAA,MAC7B,SAAS,EAAE,WAAW,OAAO;AAAA,MAC7B,OAAO,MAAM;AAAA,MACb,SAAS,MAAM,OAAO,KAAK,MAAM;AAAA,IACnC,CAAC;AAAA,IACD,GAAG,MAAM,cAAc,OAAO;AAAA,EAChC,CAAC;AAED,QAAM,QAAQ,cAAc,IAAI,iBAAiB;AAEjD,SAAO,SAAS,KAAK;AAAA,IACnB;AAAA,IACA;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM;AAAA,IAChB,YAAY,KAAK,KAAK,QAAQ,MAAM,QAAQ;AAAA,EAC9C,CAAC;AACH;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,EAAE,SAAS,OAAO,IAAI,IAAI,MAAM,2BAA2B,GAAG;AAEpE,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,SAAS,yBAAyB,UAAU,IAAI;AACtD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,oCAAoC,OAAO,KAAK;AAAA,EACzD;AAEA,MAAI;AACF,UAAM,UAAU,MAAM;AAAA,MACpB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,QACE,cAAc;AAAA,QACd,WAAW;AAAA,QACX,SAAS,OAAO;AAAA,MAClB;AAAA,MACA,MAAM,QAAQ,OAAO,OAAO,MAAM,KAAK;AAAA,IACzC;AACA,QAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAEhC,WAAO,SAAS,KAAK,EAAE,IAAI,QAAQ,OAAO,GAAG,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjE,SAAS,OAAO;AACd,UAAM,gBAAgB,8BAA8B,KAAK;AACzD,QAAI,cAAe,QAAO;AAC1B,UAAM;AAAA,EACR;AACF;AAEO,MAAM,UAAU,8BAA8B;AAAA,EACnD,cAAc;AAAA,EACd,aAAa;AAAA,EACb,oBAAoB,8BAA8B,sBAAsB;AAAA,EACxE,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,IAClD,aAAa;AAAA,EACf;AACF,CAAC;",
6
6
  "names": []
7
7
  }
@@ -13,6 +13,10 @@ import {
13
13
  resolveNotificationDeliveryConfig,
14
14
  saveNotificationDeliveryConfig
15
15
  } from "../../lib/deliveryConfig.js";
16
+ import {
17
+ NOTIFICATION_SETTINGS_RESOURCE_KIND,
18
+ runGuardedNotificationWrite
19
+ } from "../../lib/routeHelpers.js";
16
20
  const metadata = {
17
21
  GET: { requireAuth: true, requireFeatures: ["notifications.manage"] },
18
22
  POST: { requireAuth: true, requireFeatures: ["notifications.manage"] }
@@ -59,11 +63,28 @@ async function POST(req) {
59
63
  }
60
64
  const container = await createRequestContainer();
61
65
  try {
62
- await saveNotificationDeliveryConfig(container, parsed.data);
63
- const settings = await resolveNotificationDeliveryConfig(container, {
64
- defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG
65
- });
66
- return NextResponse.json({ ok: true, settings });
66
+ const guarded = await runGuardedNotificationWrite(
67
+ container,
68
+ {
69
+ tenantId: auth.tenantId ?? "",
70
+ organizationId: auth.orgId ?? null,
71
+ userId: auth.sub ?? null
72
+ },
73
+ req,
74
+ {
75
+ resourceKind: NOTIFICATION_SETTINGS_RESOURCE_KIND,
76
+ operation: "update",
77
+ payload: parsed.data
78
+ },
79
+ async () => {
80
+ await saveNotificationDeliveryConfig(container, parsed.data);
81
+ return resolveNotificationDeliveryConfig(container, {
82
+ defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG
83
+ });
84
+ }
85
+ );
86
+ if (!guarded.ok) return guarded.response;
87
+ return NextResponse.json({ ok: true, settings: guarded.result });
67
88
  } catch (error) {
68
89
  return NextResponse.json(
69
90
  { error: error instanceof Error ? error.message : t("api.errors.internal", "Internal error") },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/notifications/api/settings/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { notificationDeliveryConfigSchema } from '../../data/validators'\nimport {\n errorResponseSchema,\n notificationSettingsResponseSchema,\n notificationSettingsUpdateResponseSchema,\n} from '../openapi'\nimport {\n DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n resolveNotificationDeliveryConfig,\n saveNotificationDeliveryConfig,\n} from '../../lib/deliveryConfig'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['notifications.manage'] },\n POST: { requireAuth: true, requireFeatures: ['notifications.manage'] },\n}\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) return await unauthorized()\n\n const container = await createRequestContainer()\n try {\n const settings = await resolveNotificationDeliveryConfig(container, {\n defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n })\n return NextResponse.json({ settings })\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) return await unauthorized()\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json(\n { error: t('api.errors.invalidPayload', 'Invalid request body') },\n { status: 400 }\n )\n }\n\n const parsed = notificationDeliveryConfigSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n { error: t('notifications.delivery.settings.invalid', 'Invalid delivery settings') },\n { status: 400 }\n )\n }\n\n const container = await createRequestContainer()\n try {\n await saveNotificationDeliveryConfig(container, parsed.data)\n const settings = await resolveNotificationDeliveryConfig(container, {\n defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n })\n return NextResponse.json({ ok: true, settings })\n } catch (error) {\n return NextResponse.json(\n { error: error instanceof Error ? error.message : t('api.errors.internal', 'Internal error') },\n { status: 500 }\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get notification delivery settings',\n tags: ['Notifications'],\n responses: {\n 200: {\n description: 'Current delivery settings',\n content: {\n 'application/json': {\n schema: notificationSettingsResponseSchema,\n },\n },\n },\n 401: {\n description: 'Unauthorized',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n POST: {\n summary: 'Update notification delivery settings',\n tags: ['Notifications'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: notificationDeliveryConfigSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Delivery settings updated',\n content: {\n 'application/json': {\n schema: notificationSettingsUpdateResponseSchema,\n },\n },\n },\n 400: {\n description: 'Invalid request body',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n 401: {\n description: 'Unauthorized',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Internal error',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { notificationDeliveryConfigSchema } from '../../data/validators'\nimport {\n errorResponseSchema,\n notificationSettingsResponseSchema,\n notificationSettingsUpdateResponseSchema,\n} from '../openapi'\nimport {\n DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n resolveNotificationDeliveryConfig,\n saveNotificationDeliveryConfig,\n} from '../../lib/deliveryConfig'\nimport {\n NOTIFICATION_SETTINGS_RESOURCE_KIND,\n runGuardedNotificationWrite,\n} from '../../lib/routeHelpers'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['notifications.manage'] },\n POST: { requireAuth: true, requireFeatures: ['notifications.manage'] },\n}\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) return await unauthorized()\n\n const container = await createRequestContainer()\n try {\n const settings = await resolveNotificationDeliveryConfig(container, {\n defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n })\n return NextResponse.json({ settings })\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) return await unauthorized()\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json(\n { error: t('api.errors.invalidPayload', 'Invalid request body') },\n { status: 400 }\n )\n }\n\n const parsed = notificationDeliveryConfigSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n { error: t('notifications.delivery.settings.invalid', 'Invalid delivery settings') },\n { status: 400 }\n )\n }\n\n const container = await createRequestContainer()\n try {\n const guarded = await runGuardedNotificationWrite(\n container,\n {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub ?? null,\n },\n req,\n {\n resourceKind: NOTIFICATION_SETTINGS_RESOURCE_KIND,\n operation: 'update',\n payload: parsed.data as Record<string, unknown>,\n },\n async () => {\n await saveNotificationDeliveryConfig(container, parsed.data)\n return resolveNotificationDeliveryConfig(container, {\n defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG,\n })\n },\n )\n if (!guarded.ok) return guarded.response\n return NextResponse.json({ ok: true, settings: guarded.result })\n } catch (error) {\n return NextResponse.json(\n { error: error instanceof Error ? error.message : t('api.errors.internal', 'Internal error') },\n { status: 500 }\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get notification delivery settings',\n tags: ['Notifications'],\n responses: {\n 200: {\n description: 'Current delivery settings',\n content: {\n 'application/json': {\n schema: notificationSettingsResponseSchema,\n },\n },\n },\n 401: {\n description: 'Unauthorized',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n POST: {\n summary: 'Update notification delivery settings',\n tags: ['Notifications'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: notificationDeliveryConfigSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Delivery settings updated',\n content: {\n 'application/json': {\n schema: notificationSettingsUpdateResponseSchema,\n },\n },\n },\n 400: {\n description: 'Invalid request body',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n 401: {\n description: 'Unauthorized',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Internal error',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,wCAAwC;AACjD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AAAA,EACpE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACvE;AAEA,MAAM,eAAe,YAAY;AAC/B,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,SAAO,aAAa,KAAK,EAAE,OAAO,EAAE,2BAA2B,cAAc,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AACnG;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,IAAK,QAAO,MAAM,aAAa;AAE1C,QAAM,YAAY,MAAM,uBAAuB;AAC/C,MAAI;AACF,UAAM,WAAW,MAAM,kCAAkC,WAAW;AAAA,MAClE,cAAc;AAAA,IAChB,CAAC;AACD,WAAO,aAAa,KAAK,EAAE,SAAS,CAAC;AAAA,EACvC,UAAE;AACA,UAAM,aAAa;AACnB,QAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,YAAM,WAAW,QAAQ;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,IAAK,QAAO,MAAM,aAAa;AAE1C,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,EAAE,6BAA6B,sBAAsB,EAAE;AAAA,MAChE,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,iCAAiC,UAAU,IAAI;AAC9D,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,EAAE,2CAA2C,2BAA2B,EAAE;AAAA,MACnF,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,MAAI;AACF,UAAM,UAAU,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,QACE,UAAU,KAAK,YAAY;AAAA,QAC3B,gBAAgB,KAAK,SAAS;AAAA,QAC9B,QAAQ,KAAK,OAAO;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,QACE,cAAc;AAAA,QACd,WAAW;AAAA,QACX,SAAS,OAAO;AAAA,MAClB;AAAA,MACA,YAAY;AACV,cAAM,+BAA+B,WAAW,OAAO,IAAI;AAC3D,eAAO,kCAAkC,WAAW;AAAA,UAClD,cAAc;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAChC,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,UAAU,QAAQ,OAAO,CAAC;AAAA,EACjE,SAAS,OAAO;AACd,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,EAAE,uBAAuB,gBAAgB,EAAE;AAAA,MAC7F,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF,UAAE;AACA,UAAM,aAAa;AACnB,QAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,YAAM,WAAW,QAAQ;AAAA,IAC3B;AAAA,EACF;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,aAAa;AAAA,MACX,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB;AAAA,UAClB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,14 @@
1
1
  import { z } from "zod";
2
2
  import { resolveRequestContext } from "@open-mercato/shared/lib/api/context";
3
3
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
4
+ import {
5
+ runCrudMutationGuardAfterSuccess,
6
+ validateCrudMutationGuard
7
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
4
8
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
5
9
  import { resolveNotificationService } from "./notificationService.js";
10
+ const NOTIFICATION_RESOURCE_KIND = "notifications.notification";
11
+ const NOTIFICATION_SETTINGS_RESOURCE_KIND = "notifications.settings";
6
12
  function formatZodIssues(error) {
7
13
  return error.issues.map((issue) => {
8
14
  const path = issue.path.length ? `${issue.path.join(".")}: ` : "";
@@ -34,16 +40,55 @@ async function resolveNotificationContext(req) {
34
40
  ctx
35
41
  };
36
42
  }
43
+ async function runGuardedNotificationWrite(container, scope, req, options, write) {
44
+ const guardScope = {
45
+ tenantId: scope.tenantId,
46
+ organizationId: scope.organizationId,
47
+ userId: scope.userId ?? "",
48
+ resourceKind: options.resourceKind,
49
+ resourceId: options.resourceId ?? "",
50
+ operation: options.operation,
51
+ requestMethod: req.method,
52
+ requestHeaders: req.headers
53
+ };
54
+ const guardResult = await validateCrudMutationGuard(container, {
55
+ ...guardScope,
56
+ mutationPayload: options.payload ?? null
57
+ });
58
+ if (guardResult && !guardResult.ok) {
59
+ return { ok: false, response: Response.json(guardResult.body, { status: guardResult.status }) };
60
+ }
61
+ const result = await write();
62
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
63
+ await runCrudMutationGuardAfterSuccess(container, {
64
+ ...guardScope,
65
+ metadata: guardResult.metadata ?? null
66
+ });
67
+ }
68
+ return { ok: true, result };
69
+ }
37
70
  function createBulkNotificationRoute(schema, serviceMethod) {
38
71
  return async function POST(req) {
39
- const { service, scope } = await resolveNotificationContext(req);
72
+ const { service, scope, ctx } = await resolveNotificationContext(req);
40
73
  const body = await req.json().catch(() => ({}));
41
74
  const parsed = schema.safeParse(body);
42
75
  if (!parsed.success) {
43
76
  return notificationValidationErrorResponse(parsed.error);
44
77
  }
45
78
  try {
46
- const notifications = await service[serviceMethod](parsed.data, scope);
79
+ const guarded = await runGuardedNotificationWrite(
80
+ ctx.container,
81
+ scope,
82
+ req,
83
+ {
84
+ resourceKind: NOTIFICATION_RESOURCE_KIND,
85
+ operation: "create",
86
+ payload: parsed.data
87
+ },
88
+ () => service[serviceMethod](parsed.data, scope)
89
+ );
90
+ if (!guarded.ok) return guarded.response;
91
+ const notifications = guarded.result;
47
92
  return Response.json({
48
93
  ok: true,
49
94
  count: notifications.length,
@@ -90,9 +135,20 @@ function createBulkNotificationOpenApi(schema, summary, description) {
90
135
  function createSingleNotificationActionRoute(serviceMethod) {
91
136
  return async function PUT(req, { params }) {
92
137
  const { id } = await params;
93
- const { service, scope } = await resolveNotificationContext(req);
138
+ const { service, scope, ctx } = await resolveNotificationContext(req);
94
139
  try {
95
- await service[serviceMethod](id, scope);
140
+ const guarded = await runGuardedNotificationWrite(
141
+ ctx.container,
142
+ scope,
143
+ req,
144
+ {
145
+ resourceKind: NOTIFICATION_RESOURCE_KIND,
146
+ resourceId: id,
147
+ operation: "update"
148
+ },
149
+ () => service[serviceMethod](id, scope)
150
+ );
151
+ if (!guarded.ok) return guarded.response;
96
152
  } catch (error) {
97
153
  const errorResponse = notificationCrudErrorResponse(error);
98
154
  if (errorResponse) return errorResponse;
@@ -128,12 +184,15 @@ function createSingleNotificationActionOpenApi(summary, description) {
128
184
  };
129
185
  }
130
186
  export {
187
+ NOTIFICATION_RESOURCE_KIND,
188
+ NOTIFICATION_SETTINGS_RESOURCE_KIND,
131
189
  createBulkNotificationOpenApi,
132
190
  createBulkNotificationRoute,
133
191
  createSingleNotificationActionOpenApi,
134
192
  createSingleNotificationActionRoute,
135
193
  notificationCrudErrorResponse,
136
194
  notificationValidationErrorResponse,
137
- resolveNotificationContext
195
+ resolveNotificationContext,
196
+ runGuardedNotificationWrite
138
197
  };
139
198
  //# sourceMappingURL=routeHelpers.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/lib/routeHelpers.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport { resolveRequestContext } from '@open-mercato/shared/lib/api/context'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveNotificationService, type NotificationService } from './notificationService'\n\n/**\n * Notification scope context for service calls\n */\nexport interface NotificationScope {\n tenantId: string\n organizationId: string | null\n userId: string | null\n}\n\n/**\n * Resolved notification context from a request\n */\nexport interface NotificationRequestContext {\n service: NotificationService\n scope: NotificationScope\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx']\n}\n\nfunction formatZodIssues(error: z.ZodError): string {\n return error.issues\n .map((issue) => {\n const path = issue.path.length ? `${issue.path.join('.')}: ` : ''\n return `${path}${issue.message}`\n })\n .join('; ')\n}\n\nexport async function notificationValidationErrorResponse(error: z.ZodError): Promise<Response> {\n const { t } = await resolveTranslations()\n const prefix = t('api.errors.invalidPayload', 'Invalid request body')\n const details = formatZodIssues(error)\n return Response.json(\n { error: details ? `${prefix}: ${details}` : prefix },\n { status: 400 },\n )\n}\n\nexport function notificationCrudErrorResponse(error: unknown): Response | null {\n if (!isCrudHttpError(error)) return null\n return Response.json(error.body ?? { error: 'Notification request failed' }, { status: error.status })\n}\n\n/**\n * Resolve notification service and scope from a request.\n * Centralizes the common pattern used across all notification API routes.\n */\nexport async function resolveNotificationContext(req: Request): Promise<NotificationRequestContext> {\n const { ctx } = await resolveRequestContext(req)\n return {\n service: resolveNotificationService(ctx.container),\n scope: {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? null,\n userId: ctx.auth?.sub ?? null,\n },\n ctx,\n }\n}\n\n/**\n * Create a POST handler for bulk notification creation routes.\n * Used by batch, role, and feature notification endpoints.\n */\nexport function createBulkNotificationRoute<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n serviceMethod: 'createBatch' | 'createForRole' | 'createForFeature'\n) {\n return async function POST(req: Request) {\n const { service, scope } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = schema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const notifications = await service[serviceMethod](parsed.data as never, scope)\n\n return Response.json({\n ok: true,\n count: notifications.length,\n ids: notifications.map((n) => n.id),\n }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n }\n}\n\n/**\n * Create OpenAPI spec for bulk notification creation routes.\n */\nexport function createBulkNotificationOpenApi<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n summary: string,\n description?: string\n) {\n return {\n POST: {\n summary,\n description,\n tags: ['Notifications'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema,\n },\n },\n },\n responses: {\n 201: {\n description: 'Notifications created',\n content: {\n 'application/json': {\n schema: z.object({\n ok: z.boolean(),\n count: z.number(),\n ids: z.array(z.string().uuid()),\n }),\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * Create a PUT handler for single notification action routes.\n * Used by read and dismiss endpoints.\n */\nexport function createSingleNotificationActionRoute(\n serviceMethod: 'markAsRead' | 'dismiss'\n) {\n return async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope } = await resolveNotificationContext(req)\n\n try {\n await service[serviceMethod](id, scope)\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n\n return Response.json({ ok: true })\n }\n}\n\n/**\n * Create OpenAPI spec for single notification action routes.\n */\nexport function createSingleNotificationActionOpenApi(\n summary: string,\n description: string\n) {\n return {\n PUT: {\n summary,\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n responses: {\n 200: {\n description,\n content: {\n 'application/json': {\n schema: z.object({ ok: z.boolean() }),\n },\n },\n },\n },\n },\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { AwilixContainer } from 'awilix'\nimport { resolveRequestContext } from '@open-mercato/shared/lib/api/context'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveNotificationService, type NotificationService } from './notificationService'\n\n/**\n * Mutation-guard resource kind for notification rows.\n */\nexport const NOTIFICATION_RESOURCE_KIND = 'notifications.notification'\n\n/**\n * Mutation-guard resource kind for notification delivery settings.\n */\nexport const NOTIFICATION_SETTINGS_RESOURCE_KIND = 'notifications.settings'\n\n/**\n * Notification scope context for service calls\n */\nexport interface NotificationScope {\n tenantId: string\n organizationId: string | null\n userId: string | null\n}\n\n/**\n * Resolved notification context from a request\n */\nexport interface NotificationRequestContext {\n service: NotificationService\n scope: NotificationScope\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx']\n}\n\nfunction formatZodIssues(error: z.ZodError): string {\n return error.issues\n .map((issue) => {\n const path = issue.path.length ? `${issue.path.join('.')}: ` : ''\n return `${path}${issue.message}`\n })\n .join('; ')\n}\n\nexport async function notificationValidationErrorResponse(error: z.ZodError): Promise<Response> {\n const { t } = await resolveTranslations()\n const prefix = t('api.errors.invalidPayload', 'Invalid request body')\n const details = formatZodIssues(error)\n return Response.json(\n { error: details ? `${prefix}: ${details}` : prefix },\n { status: 400 },\n )\n}\n\nexport function notificationCrudErrorResponse(error: unknown): Response | null {\n if (!isCrudHttpError(error)) return null\n return Response.json(error.body ?? { error: 'Notification request failed' }, { status: error.status })\n}\n\n/**\n * Resolve notification service and scope from a request.\n * Centralizes the common pattern used across all notification API routes.\n */\nexport async function resolveNotificationContext(req: Request): Promise<NotificationRequestContext> {\n const { ctx } = await resolveRequestContext(req)\n return {\n service: resolveNotificationService(ctx.container),\n scope: {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? null,\n userId: ctx.auth?.sub ?? null,\n },\n ctx,\n }\n}\n\n/**\n * Mutation-guard options for a notification write.\n */\nexport interface NotificationMutationGuardOptions {\n resourceKind: string\n resourceId?: string | null\n operation: 'create' | 'update' | 'delete' | 'custom'\n payload?: Record<string, unknown> | null\n}\n\nexport type GuardedNotificationWriteResult<T> =\n | { ok: true; result: T }\n | { ok: false; response: Response }\n\n/**\n * Run a notification write through the mutation guard lifecycle.\n * Validates before the mutation, performs the write, then runs after-success\n * hooks only when the write succeeded and the guard requested them. Returns the\n * guard's own block response when validation fails so authorization behavior and\n * conflict shapes are preserved.\n */\nexport async function runGuardedNotificationWrite<T>(\n container: AwilixContainer,\n scope: NotificationScope,\n req: Request,\n options: NotificationMutationGuardOptions,\n write: () => Promise<T>,\n): Promise<GuardedNotificationWriteResult<T>> {\n const guardScope = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId ?? '',\n resourceKind: options.resourceKind,\n resourceId: options.resourceId ?? '',\n operation: options.operation,\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n ...guardScope,\n mutationPayload: options.payload ?? null,\n })\n if (guardResult && !guardResult.ok) {\n return { ok: false, response: Response.json(guardResult.body, { status: guardResult.status }) }\n }\n\n const result = await write()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n ...guardScope,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return { ok: true, result }\n}\n\n/**\n * Create a POST handler for bulk notification creation routes.\n * Used by batch, role, and feature notification endpoints.\n */\nexport function createBulkNotificationRoute<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n serviceMethod: 'createBatch' | 'createForRole' | 'createForFeature'\n) {\n return async function POST(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = schema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'create',\n payload: parsed.data as Record<string, unknown>,\n },\n () => service[serviceMethod](parsed.data as never, scope),\n )\n if (!guarded.ok) return guarded.response\n const notifications = guarded.result\n\n return Response.json({\n ok: true,\n count: notifications.length,\n ids: notifications.map((n) => n.id),\n }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n }\n}\n\n/**\n * Create OpenAPI spec for bulk notification creation routes.\n */\nexport function createBulkNotificationOpenApi<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n summary: string,\n description?: string\n) {\n return {\n POST: {\n summary,\n description,\n tags: ['Notifications'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema,\n },\n },\n },\n responses: {\n 201: {\n description: 'Notifications created',\n content: {\n 'application/json': {\n schema: z.object({\n ok: z.boolean(),\n count: z.number(),\n ids: z.array(z.string().uuid()),\n }),\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * Create a PUT handler for single notification action routes.\n * Used by read and dismiss endpoints.\n */\nexport function createSingleNotificationActionRoute(\n serviceMethod: 'markAsRead' | 'dismiss'\n) {\n return async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n resourceId: id,\n operation: 'update',\n },\n () => service[serviceMethod](id, scope),\n )\n if (!guarded.ok) return guarded.response\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n\n return Response.json({ ok: true })\n }\n}\n\n/**\n * Create OpenAPI spec for single notification action routes.\n */\nexport function createSingleNotificationActionOpenApi(\n summary: string,\n description: string\n) {\n return {\n PUT: {\n summary,\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n responses: {\n 200: {\n description,\n content: {\n 'application/json': {\n schema: z.object({ ok: z.boolean() }),\n },\n },\n },\n },\n },\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }