@open-mercato/core 0.6.6-develop.6153.1.b4c555b820 → 0.6.6-develop.6159.1.82dfc232ca

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 (60) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +32 -0
  3. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +2 -2
  4. package/dist/modules/messages/api/[id]/archive/route.js +63 -0
  5. package/dist/modules/messages/api/[id]/archive/route.js.map +2 -2
  6. package/dist/modules/messages/api/[id]/attachments/route.js +63 -0
  7. package/dist/modules/messages/api/[id]/attachments/route.js.map +2 -2
  8. package/dist/modules/messages/api/[id]/conversation/archive/route.js +32 -0
  9. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
  10. package/dist/modules/messages/api/[id]/conversation/read/route.js +32 -0
  11. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
  12. package/dist/modules/messages/api/[id]/conversation/route.js +32 -0
  13. package/dist/modules/messages/api/[id]/conversation/route.js.map +2 -2
  14. package/dist/modules/messages/api/[id]/forward/route.js +32 -0
  15. package/dist/modules/messages/api/[id]/forward/route.js.map +2 -2
  16. package/dist/modules/messages/api/[id]/read/route.js +63 -0
  17. package/dist/modules/messages/api/[id]/read/route.js.map +2 -2
  18. package/dist/modules/messages/api/[id]/reply/route.js +32 -0
  19. package/dist/modules/messages/api/[id]/reply/route.js.map +2 -2
  20. package/dist/modules/messages/api/[id]/route.js +63 -0
  21. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  22. package/dist/modules/messages/api/guards.js +31 -0
  23. package/dist/modules/messages/api/guards.js.map +7 -0
  24. package/dist/modules/messages/api/route.js +32 -0
  25. package/dist/modules/messages/api/route.js.map +2 -2
  26. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +107 -0
  27. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +7 -0
  28. package/dist/modules/staff/commands/timesheets-entries.js +142 -1
  29. package/dist/modules/staff/commands/timesheets-entries.js.map +3 -3
  30. package/dist/modules/staff/data/validators.js +8 -0
  31. package/dist/modules/staff/data/validators.js.map +2 -2
  32. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +8 -22
  33. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
  34. package/dist/modules/staff/lib/timesheets-ui/startTimer.js +22 -0
  35. package/dist/modules/staff/lib/timesheets-ui/startTimer.js.map +7 -0
  36. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +6 -16
  37. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +2 -2
  38. package/package.json +7 -7
  39. package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +34 -0
  40. package/src/modules/messages/api/[id]/archive/route.ts +63 -0
  41. package/src/modules/messages/api/[id]/attachments/route.ts +63 -0
  42. package/src/modules/messages/api/[id]/conversation/archive/route.ts +33 -0
  43. package/src/modules/messages/api/[id]/conversation/read/route.ts +33 -0
  44. package/src/modules/messages/api/[id]/conversation/route.ts +33 -0
  45. package/src/modules/messages/api/[id]/forward/route.ts +33 -0
  46. package/src/modules/messages/api/[id]/read/route.ts +63 -0
  47. package/src/modules/messages/api/[id]/reply/route.ts +33 -0
  48. package/src/modules/messages/api/[id]/route.ts +65 -0
  49. package/src/modules/messages/api/guards.ts +59 -0
  50. package/src/modules/messages/api/route.ts +33 -0
  51. package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +110 -0
  52. package/src/modules/staff/commands/timesheets-entries.ts +166 -1
  53. package/src/modules/staff/data/validators.ts +9 -0
  54. package/src/modules/staff/i18n/de.json +1 -0
  55. package/src/modules/staff/i18n/en.json +1 -0
  56. package/src/modules/staff/i18n/es.json +1 -0
  57. package/src/modules/staff/i18n/pl.json +1 -0
  58. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +9 -25
  59. package/src/modules/staff/lib/timesheets-ui/startTimer.ts +40 -0
  60. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +9 -18
@@ -1,5 +1,6 @@
1
1
  import { attachOperationMetadataHeader } from "../../../lib/operationMetadata.js";
2
2
  import { resolveMessageContext } from "../../../lib/routeHelpers.js";
3
+ import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "../../guards.js";
3
4
  import {
4
5
  conversationMutationResponseSchema,
5
6
  errorResponseSchema
@@ -10,6 +11,27 @@ const metadata = {
10
11
  async function DELETE(req, { params }) {
11
12
  const { ctx, scope } = await resolveMessageContext(req);
12
13
  const commandBus = ctx.container.resolve("commandBus");
14
+ const guardResult = await runMessageMutationGuards(
15
+ ctx.container,
16
+ {
17
+ tenantId: scope.tenantId,
18
+ organizationId: scope.organizationId,
19
+ userId: scope.userId,
20
+ resourceKind: "messages.conversation",
21
+ resourceId: params.id,
22
+ operation: "delete",
23
+ requestMethod: req.method,
24
+ requestHeaders: req.headers,
25
+ mutationPayload: null
26
+ },
27
+ resolveUserFeatures(ctx.auth)
28
+ );
29
+ if (!guardResult.ok) {
30
+ return Response.json(
31
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
32
+ { status: guardResult.errorStatus ?? 422 }
33
+ );
34
+ }
13
35
  try {
14
36
  const { result, logEntry } = await commandBus.execute("messages.conversation.delete_for_actor", {
15
37
  input: {
@@ -32,6 +54,16 @@ async function DELETE(req, { params }) {
32
54
  resourceKind: "messages.conversation",
33
55
  resourceId: params.id
34
56
  });
57
+ await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
58
+ tenantId: scope.tenantId,
59
+ organizationId: scope.organizationId,
60
+ userId: scope.userId,
61
+ resourceKind: "messages.conversation",
62
+ resourceId: params.id,
63
+ operation: "delete",
64
+ requestMethod: req.method,
65
+ requestHeaders: req.headers
66
+ });
35
67
  return response;
36
68
  } catch (error) {
37
69
  if (error instanceof Error) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/messages/api/%5Bid%5D/conversation/route.ts"],
4
- "sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../lib/routeHelpers'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../openapi'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n try {\n const { result, logEntry } = await commandBus.execute('messages.conversation.delete_for_actor', {\n input: {\n anchorMessageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n })\n return response\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n DELETE: {\n summary: 'Delete conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation deleted', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAEA,SAAS,qCAAqC;AAC9C,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,eAAe,EAAE;AAClE;AAEA,eAAsB,OAAO,KAAc,EAAE,OAAO,GAA+B;AACjF,QAAM,EAAE,KAAK,MAAM,IAAI,MAAM,sBAAsB,GAAG;AACtD,QAAM,aAAa,IAAI,UAAU,QAAQ,YAAY;AAErD,MAAI;AACF,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW,QAAQ,0CAA0C;AAAA,MAC9F,OAAO;AAAA,QACL,iBAAiB,OAAO;AAAA,QACxB,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,QACtB,QAAQ,MAAM;AAAA,MAChB;AAAA,MACA,KAAK;AAAA,QACH,WAAW,IAAI;AAAA,QACf,MAAM,IAAI,QAAQ;AAAA,QAClB,mBAAmB;AAAA,QACnB,wBAAwB,MAAM;AAAA,QAC9B,iBAAiB,MAAM,iBAAiB,CAAC,MAAM,cAAc,IAAI;AAAA,QACjE,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAED,UAAM,WAAW,SAAS,KAAK,MAAM;AACrC,kCAA8B,UAAU,UAAU;AAAA,MAChD,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,IACrB,CAAC;AACD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,UAAI,MAAM,YAAY,qBAAqB;AACzC,eAAO,SAAS,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAChE;AACA,UAAI,MAAM,YAAY,iBAAiB;AACrC,eAAO,SAAS,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAChE;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,mCAAmC;AAAA,MACjG;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,oBAAoB;AAAA,QACzE,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,oBAAoB;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport {\n conversationMutationResponseSchema,\n errorResponseSchema,\n} from '../../openapi'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n try {\n const { result, logEntry } = await commandBus.execute('messages.conversation.delete_for_actor', {\n input: {\n anchorMessageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json(result)\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.conversation',\n resourceId: params.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: error.message }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: error.message }, { status: 403 })\n }\n }\n throw error\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n DELETE: {\n summary: 'Delete conversation for current actor',\n responses: [\n { status: 200, description: 'Conversation deleted', schema: conversationMutationResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAEA,SAAS,qCAAqC;AAC9C,SAAS,6BAA6B;AACtC,SAAS,qBAAqB,qCAAqC,gCAAgC;AACnG;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,eAAe,EAAE;AAClE;AAEA,eAAsB,OAAO,KAAc,EAAE,OAAO,GAA+B;AACjF,QAAM,EAAE,KAAK,MAAM,IAAI,MAAM,sBAAsB,GAAG;AACtD,QAAM,aAAa,IAAI,UAAU,QAAQ,YAAY;AAErD,QAAM,cAAc,MAAM;AAAA,IACxB,IAAI;AAAA,IACJ;AAAA,MACE,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ,MAAM;AAAA,MACd,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,iBAAiB;AAAA,IACnB;AAAA,IACA,oBAAoB,IAAI,IAAI;AAAA,EAC9B;AACA,MAAI,CAAC,YAAY,IAAI;AACnB,WAAO,SAAS;AAAA,MACd,YAAY,aAAa,EAAE,OAAO,6BAA6B;AAAA,MAC/D,EAAE,QAAQ,YAAY,eAAe,IAAI;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW,QAAQ,0CAA0C;AAAA,MAC9F,OAAO;AAAA,QACL,iBAAiB,OAAO;AAAA,QACxB,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,QACtB,QAAQ,MAAM;AAAA,MAChB;AAAA,MACA,KAAK;AAAA,QACH,WAAW,IAAI;AAAA,QACf,MAAM,IAAI,QAAQ;AAAA,QAClB,mBAAmB;AAAA,QACnB,wBAAwB,MAAM;AAAA,QAC9B,iBAAiB,MAAM,iBAAiB,CAAC,MAAM,cAAc,IAAI;AAAA,QACjE,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAED,UAAM,WAAW,SAAS,KAAK,MAAM;AACrC,kCAA8B,UAAU,UAAU;AAAA,MAChD,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,IACrB,CAAC;AACD,UAAM,oCAAoC,YAAY,uBAAuB;AAAA,MAC3E,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ,MAAM;AAAA,MACd,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,IACtB,CAAC;AACD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,UAAI,MAAM,YAAY,qBAAqB;AACzC,eAAO,SAAS,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAChE;AACA,UAAI,MAAM,YAAY,iBAAiB;AACrC,eAAO,SAAS,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAChE;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,mCAAmC;AAAA,MACjG;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,oBAAoB;AAAA,QACzE,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,oBAAoB;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { forwardMessageSchema } from "../../../data/validators.js";
2
2
  import { attachOperationMetadataHeader } from "../../../lib/operationMetadata.js";
3
3
  import { canUseMessageEmailFeature, parseRequestBodySafe, resolveMessageContext } from "../../../lib/routeHelpers.js";
4
+ import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "../../guards.js";
4
5
  import { forwardResponseSchema, forwardMessageSchema as forwardSchema } from "../../openapi.js";
5
6
  const metadata = {
6
7
  POST: { requireAuth: true, requireFeatures: ["messages.compose"] }
@@ -13,6 +14,27 @@ async function POST(req, { params }) {
13
14
  if (input.sendViaEmail && !await canUseMessageEmailFeature(ctx, scope)) {
14
15
  return Response.json({ error: "Missing feature: messages.email" }, { status: 403 });
15
16
  }
17
+ const guardResult = await runMessageMutationGuards(
18
+ ctx.container,
19
+ {
20
+ tenantId: scope.tenantId,
21
+ organizationId: scope.organizationId,
22
+ userId: scope.userId,
23
+ resourceKind: "messages.message",
24
+ resourceId: null,
25
+ operation: "create",
26
+ requestMethod: req.method,
27
+ requestHeaders: req.headers,
28
+ mutationPayload: input
29
+ },
30
+ resolveUserFeatures(ctx.auth)
31
+ );
32
+ if (!guardResult.ok) {
33
+ return Response.json(
34
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
35
+ { status: guardResult.errorStatus ?? 422 }
36
+ );
37
+ }
16
38
  let commandResult;
17
39
  try {
18
40
  commandResult = await commandBus.execute("messages.messages.forward", {
@@ -55,6 +77,16 @@ async function POST(req, { params }) {
55
77
  resourceKind: "messages.message",
56
78
  resourceId: newMessageId
57
79
  });
80
+ await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
81
+ tenantId: scope.tenantId,
82
+ organizationId: scope.organizationId,
83
+ userId: scope.userId,
84
+ resourceKind: "messages.message",
85
+ resourceId: newMessageId,
86
+ operation: "create",
87
+ requestMethod: req.method,
88
+ requestHeaders: req.headers
89
+ });
58
90
  return response;
59
91
  }
60
92
  const openApi = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/messages/api/%5Bid%5D/forward/route.ts"],
4
- "sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { forwardMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader, OperationLogEntryLike } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, parseRequestBodySafe, resolveMessageContext } from '../../../lib/routeHelpers'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { forwardResponseSchema, forwardMessageSchema as forwardSchema } from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await parseRequestBodySafe(req)\n const input = forwardMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n let commandResult: { result: MessageCommandExecuteResult; logEntry: unknown }\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.forward', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Forward is not allowed for this message type') {\n return Response.json({ error: 'Forward is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'Forward body exceeds maximum length') {\n return Response.json({ error: 'Forward body exceeds maximum length' }, { status: 413 })\n }\n }\n throw error\n }\n const newMessageId = commandResult.result.id\n\n const response = Response.json({ id: newMessageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry as OperationLogEntryLike, {\n resourceKind: 'messages.message',\n resourceId: newMessageId,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Forward a message and optionally include attachments from the forwarded conversation slice',\n requestBody: { schema: forwardSchema },\n responses: [\n {\n status: 201,\n description: 'Message forwarded',\n schema: forwardResponseSchema,\n },\n { status: 403, description: 'Access denied' },\n { status: 404, description: 'Message not found' },\n { status: 409, description: 'Forward not allowed for message type' },\n { status: 413, description: 'Forward body exceeds maximum length' },\n ],\n },\n },\n}\n"],
5
- "mappings": "AACA,SAAS,4BAA4B;AACrC,SAAS,qCAA4D;AACrE,SAAS,2BAA2B,sBAAsB,6BAA6B;AAEvF,SAAS,uBAAuB,wBAAwB,qBAAqB;AAGtE,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,kBAAkB,EAAE;AACnE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA+B;AAC/E,QAAM,EAAE,KAAK,MAAM,IAAI,MAAM,sBAAsB,GAAG;AACtD,QAAM,aAAa,IAAI,UAAU,QAAQ,YAAY;AACrD,QAAM,OAAO,MAAM,qBAAqB,GAAG;AAC3C,QAAM,QAAQ,qBAAqB,MAAM,IAAI;AAC7C,MAAI,MAAM,gBAAgB,CAAE,MAAM,0BAA0B,KAAK,KAAK,GAAI;AACxE,WAAO,SAAS,KAAK,EAAE,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpF;AAEA,MAAI;AACJ,MAAI;AACF,oBAAgB,MAAM,WAAW,QAA8C,6BAA6B;AAAA,MAC1G,OAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW,OAAO;AAAA,QAClB,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,QACtB,QAAQ,MAAM;AAAA,MAChB;AAAA,MACA,KAAK;AAAA,QACH,WAAW,IAAI;AAAA,QACf,MAAM,IAAI,QAAQ;AAAA,QAClB,mBAAmB;AAAA,QACnB,wBAAwB,MAAM;AAAA,QAC9B,iBAAiB,MAAM,iBAAiB,CAAC,MAAM,cAAc,IAAI;AAAA,QACjE,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,UAAI,MAAM,YAAY,qBAAqB;AACzC,eAAO,SAAS,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACtE;AACA,UAAI,MAAM,YAAY,iBAAiB;AACrC,eAAO,SAAS,KAAK,EAAE,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAClE;AACA,UAAI,MAAM,YAAY,gDAAgD;AACpE,eAAO,SAAS,KAAK,EAAE,OAAO,+CAA+C,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACjG;AACA,UAAI,MAAM,YAAY,uCAAuC;AAC3D,eAAO,SAAS,KAAK,EAAE,OAAO,sCAAsC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACxF;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACA,QAAM,eAAe,cAAc,OAAO;AAE1C,QAAM,WAAW,SAAS,KAAK,EAAE,IAAI,aAAa,GAAG,EAAE,QAAQ,IAAI,CAAC;AACpE,gCAA8B,UAAU,cAAc,UAAmC;AAAA,IACvF,cAAc;AAAA,IACd,YAAY;AAAA,EACd,CAAC;AACD,SAAO;AACT;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa,EAAE,QAAQ,cAAc;AAAA,MACrC,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,QACA,EAAE,QAAQ,KAAK,aAAa,gBAAgB;AAAA,QAC5C,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,uCAAuC;AAAA,QACnE,EAAE,QAAQ,KAAK,aAAa,sCAAsC;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { forwardMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader, OperationLogEntryLike } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, parseRequestBodySafe, resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { forwardResponseSchema, forwardMessageSchema as forwardSchema } from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await parseRequestBodySafe(req)\n const input = forwardMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n let commandResult: { result: MessageCommandExecuteResult; logEntry: unknown }\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.forward', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Forward is not allowed for this message type') {\n return Response.json({ error: 'Forward is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'Forward body exceeds maximum length') {\n return Response.json({ error: 'Forward body exceeds maximum length' }, { status: 413 })\n }\n }\n throw error\n }\n const newMessageId = commandResult.result.id\n\n const response = Response.json({ id: newMessageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry as OperationLogEntryLike, {\n resourceKind: 'messages.message',\n resourceId: newMessageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: newMessageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Forward a message and optionally include attachments from the forwarded conversation slice',\n requestBody: { schema: forwardSchema },\n responses: [\n {\n status: 201,\n description: 'Message forwarded',\n schema: forwardResponseSchema,\n },\n { status: 403, description: 'Access denied' },\n { status: 404, description: 'Message not found' },\n { status: 409, description: 'Forward not allowed for message type' },\n { status: 413, description: 'Forward body exceeds maximum length' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { Message, MessageRecipient } from "../../../data/entities.js";
2
2
  import { attachOperationMetadataHeader } from "../../../lib/operationMetadata.js";
3
3
  import { hasOrganizationAccess, resolveMessageContext } from "../../../lib/routeHelpers.js";
4
+ import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "../../guards.js";
4
5
  import { errorResponseSchema, okResponseSchema } from "../../openapi.js";
5
6
  const metadata = {
6
7
  PUT: { requireAuth: true, requireFeatures: ["messages.view"] },
@@ -35,6 +36,27 @@ async function PUT(req, { params }) {
35
36
  if ("response" in context) return context.response;
36
37
  const { ctx, scope } = context;
37
38
  const commandBus = ctx.container.resolve("commandBus");
39
+ const guardResult = await runMessageMutationGuards(
40
+ ctx.container,
41
+ {
42
+ tenantId: scope.tenantId,
43
+ organizationId: scope.organizationId,
44
+ userId: scope.userId,
45
+ resourceKind: "messages.message",
46
+ resourceId: params.id,
47
+ operation: "update",
48
+ requestMethod: req.method,
49
+ requestHeaders: req.headers,
50
+ mutationPayload: null
51
+ },
52
+ resolveUserFeatures(ctx.auth)
53
+ );
54
+ if (!guardResult.ok) {
55
+ return Response.json(
56
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
57
+ { status: guardResult.errorStatus ?? 422 }
58
+ );
59
+ }
38
60
  const { logEntry } = await commandBus.execute("messages.recipients.mark_read", {
39
61
  input: {
40
62
  messageId: params.id,
@@ -56,6 +78,16 @@ async function PUT(req, { params }) {
56
78
  resourceKind: "messages.message",
57
79
  resourceId: params.id
58
80
  });
81
+ await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
82
+ tenantId: scope.tenantId,
83
+ organizationId: scope.organizationId,
84
+ userId: scope.userId,
85
+ resourceKind: "messages.message",
86
+ resourceId: params.id,
87
+ operation: "update",
88
+ requestMethod: req.method,
89
+ requestHeaders: req.headers
90
+ });
59
91
  return response;
60
92
  }
61
93
  async function DELETE(req, { params }) {
@@ -63,6 +95,27 @@ async function DELETE(req, { params }) {
63
95
  if ("response" in context) return context.response;
64
96
  const { ctx, scope } = context;
65
97
  const commandBus = ctx.container.resolve("commandBus");
98
+ const guardResult = await runMessageMutationGuards(
99
+ ctx.container,
100
+ {
101
+ tenantId: scope.tenantId,
102
+ organizationId: scope.organizationId,
103
+ userId: scope.userId,
104
+ resourceKind: "messages.message",
105
+ resourceId: params.id,
106
+ operation: "update",
107
+ requestMethod: req.method,
108
+ requestHeaders: req.headers,
109
+ mutationPayload: null
110
+ },
111
+ resolveUserFeatures(ctx.auth)
112
+ );
113
+ if (!guardResult.ok) {
114
+ return Response.json(
115
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
116
+ { status: guardResult.errorStatus ?? 422 }
117
+ );
118
+ }
66
119
  const { logEntry } = await commandBus.execute("messages.recipients.mark_unread", {
67
120
  input: {
68
121
  messageId: params.id,
@@ -84,6 +137,16 @@ async function DELETE(req, { params }) {
84
137
  resourceKind: "messages.message",
85
138
  resourceId: params.id
86
139
  });
140
+ await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
141
+ tenantId: scope.tenantId,
142
+ organizationId: scope.organizationId,
143
+ userId: scope.userId,
144
+ resourceKind: "messages.message",
145
+ resourceId: params.id,
146
+ operation: "update",
147
+ requestMethod: req.method,
148
+ requestHeaders: req.headers
149
+ });
87
150
  return response;
88
151
  }
89
152
  const openApi = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/messages/api/%5Bid%5D/read/route.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { Message, MessageRecipient } from '../../../data/entities'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { hasOrganizationAccess, resolveMessageContext } from '../../../lib/routeHelpers'\nimport type { MessageScope } from '../../../lib/routeHelpers'\nimport { errorResponseSchema, okResponseSchema } from '../../openapi'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['messages.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\ntype ResolvedCtx = Awaited<ReturnType<typeof resolveMessageContext>>['ctx']\n\nasync function resolveRecipientContext(\n req: Request,\n id: string,\n): Promise<\n | { ctx: ResolvedCtx; scope: MessageScope; em: EntityManager; recipient: MessageRecipient }\n | { response: Response }\n> {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const message = await em.findOne(Message, {\n id,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n\n if (!message) {\n return { response: Response.json({ error: 'Message not found' }, { status: 404 }) }\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return { response: Response.json({ error: 'Access denied' }, { status: 403 }) }\n }\n\n const recipient = await em.findOne(MessageRecipient, {\n messageId: id,\n recipientUserId: scope.userId,\n deletedAt: null,\n })\n\n if (!recipient) {\n return { response: Response.json({ error: 'Access denied' }, { status: 403 }) }\n }\n\n return { ctx, scope, em, recipient }\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const context = await resolveRecipientContext(req, params.id)\n if ('response' in context) return context.response\n const { ctx, scope } = context\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute('messages.recipients.mark_read', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n return response\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const context = await resolveRecipientContext(req, params.id)\n if ('response' in context) return context.response\n const { ctx, scope } = context\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute('messages.recipients.mark_unread', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n PUT: {\n summary: 'Mark message as read',\n responses: [\n { status: 200, description: 'Message marked read', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Mark message as unread',\n responses: [\n { status: 200, description: 'Message marked unread', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { Message, MessageRecipient } from '../../../data/entities'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { hasOrganizationAccess, resolveMessageContext } from '../../../lib/routeHelpers'\nimport type { MessageScope } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport { errorResponseSchema, okResponseSchema } from '../../openapi'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['messages.view'] },\n DELETE: { requireAuth: true, requireFeatures: ['messages.view'] },\n}\n\ntype ResolvedCtx = Awaited<ReturnType<typeof resolveMessageContext>>['ctx']\n\nasync function resolveRecipientContext(\n req: Request,\n id: string,\n): Promise<\n | { ctx: ResolvedCtx; scope: MessageScope; em: EntityManager; recipient: MessageRecipient }\n | { response: Response }\n> {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const message = await em.findOne(Message, {\n id,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n\n if (!message) {\n return { response: Response.json({ error: 'Message not found' }, { status: 404 }) }\n }\n\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n return { response: Response.json({ error: 'Access denied' }, { status: 403 }) }\n }\n\n const recipient = await em.findOne(MessageRecipient, {\n messageId: id,\n recipientUserId: scope.userId,\n deletedAt: null,\n })\n\n if (!recipient) {\n return { response: Response.json({ error: 'Access denied' }, { status: 403 }) }\n }\n\n return { ctx, scope, em, recipient }\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const context = await resolveRecipientContext(req, params.id)\n if ('response' in context) return context.response\n const { ctx, scope } = context\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n const { logEntry } = await commandBus.execute('messages.recipients.mark_read', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const context = await resolveRecipientContext(req, params.id)\n if ('response' in context) return context.response\n const { ctx, scope } = context\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n const { logEntry } = await commandBus.execute('messages.recipients.mark_unread', {\n input: {\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n\n const response = Response.json({ ok: true })\n attachOperationMetadataHeader(response, logEntry, {\n resourceKind: 'messages.message',\n resourceId: params.id,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: params.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n PUT: {\n summary: 'Mark message as read',\n responses: [\n { status: 200, description: 'Message marked read', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Mark message as unread',\n responses: [\n { status: 200, description: 'Message marked unread', schema: okResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { replyMessageSchema } from "../../../data/validators.js";
2
2
  import { attachOperationMetadataHeader } from "../../../lib/operationMetadata.js";
3
3
  import { canUseMessageEmailFeature, resolveMessageContext } from "../../../lib/routeHelpers.js";
4
+ import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "../../guards.js";
4
5
  import {
5
6
  errorResponseSchema,
6
7
  forwardResponseSchema,
@@ -17,6 +18,27 @@ async function POST(req, { params }) {
17
18
  if (input.sendViaEmail && !await canUseMessageEmailFeature(ctx, scope)) {
18
19
  return Response.json({ error: "Missing feature: messages.email" }, { status: 403 });
19
20
  }
21
+ const guardResult = await runMessageMutationGuards(
22
+ ctx.container,
23
+ {
24
+ tenantId: scope.tenantId,
25
+ organizationId: scope.organizationId,
26
+ userId: scope.userId,
27
+ resourceKind: "messages.message",
28
+ resourceId: null,
29
+ operation: "create",
30
+ requestMethod: req.method,
31
+ requestHeaders: req.headers,
32
+ mutationPayload: input
33
+ },
34
+ resolveUserFeatures(ctx.auth)
35
+ );
36
+ if (!guardResult.ok) {
37
+ return Response.json(
38
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
39
+ { status: guardResult.errorStatus ?? 422 }
40
+ );
41
+ }
20
42
  let commandResult;
21
43
  try {
22
44
  commandResult = await commandBus.execute("messages.messages.reply", {
@@ -59,6 +81,16 @@ async function POST(req, { params }) {
59
81
  resourceKind: "messages.message",
60
82
  resourceId: messageId
61
83
  });
84
+ await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
85
+ tenantId: scope.tenantId,
86
+ organizationId: scope.organizationId,
87
+ userId: scope.userId,
88
+ resourceKind: "messages.message",
89
+ resourceId: messageId,
90
+ operation: "create",
91
+ requestMethod: req.method,
92
+ requestHeaders: req.headers
93
+ });
62
94
  return response;
63
95
  }
64
96
  const openApi = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/messages/api/%5Bid%5D/reply/route.ts"],
4
- "sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { replyMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, resolveMessageContext } from '../../../lib/routeHelpers'\nimport {\n errorResponseSchema,\n forwardResponseSchema,\n replyMessageSchema as replyOpenApiSchema,\n} from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n const input = replyMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n let commandResult\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.reply', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Reply is not allowed for this message type') {\n return Response.json({ error: 'Reply is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'No recipients available for reply') {\n return Response.json({ error: 'No recipients available for reply' }, { status: 409 })\n }\n }\n throw error\n }\n const messageId = commandResult.result.id\n\n const response = Response.json({ id: messageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry, {\n resourceKind: 'messages.message',\n resourceId: messageId,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Reply to message',\n requestBody: { schema: replyOpenApiSchema },\n responses: [\n { status: 201, description: 'Reply created', schema: forwardResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Reply not allowed for message type', schema: errorResponseSchema },\n { status: 409, description: 'No recipients available for reply', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAEA,SAAS,0BAA0B;AACnC,SAAS,qCAAqC;AAC9C,SAAS,2BAA2B,6BAA6B;AACjE;AAAA,EACE;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,OACjB;AAGA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,kBAAkB,EAAE;AACnE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA+B;AAC/E,QAAM,EAAE,KAAK,MAAM,IAAI,MAAM,sBAAsB,GAAG;AACtD,QAAM,aAAa,IAAI,UAAU,QAAQ,YAAY;AACrD,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,QAAQ,mBAAmB,MAAM,IAAI;AAC3C,MAAI,MAAM,gBAAgB,CAAE,MAAM,0BAA0B,KAAK,KAAK,GAAI;AACxE,WAAO,SAAS,KAAK,EAAE,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpF;AAEA,MAAI;AACJ,MAAI;AACF,oBAAgB,MAAM,WAAW,QAA8C,2BAA2B;AAAA,MACxG,OAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW,OAAO;AAAA,QAClB,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,QACtB,QAAQ,MAAM;AAAA,MAChB;AAAA,MACA,KAAK;AAAA,QACH,WAAW,IAAI;AAAA,QACf,MAAM,IAAI,QAAQ;AAAA,QAClB,mBAAmB;AAAA,QACnB,wBAAwB,MAAM;AAAA,QAC9B,iBAAiB,MAAM,iBAAiB,CAAC,MAAM,cAAc,IAAI;AAAA,QACjE,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,UAAI,MAAM,YAAY,qBAAqB;AACzC,eAAO,SAAS,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACtE;AACA,UAAI,MAAM,YAAY,iBAAiB;AACrC,eAAO,SAAS,KAAK,EAAE,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAClE;AACA,UAAI,MAAM,YAAY,8CAA8C;AAClE,eAAO,SAAS,KAAK,EAAE,OAAO,6CAA6C,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC/F;AACA,UAAI,MAAM,YAAY,qCAAqC;AACzD,eAAO,SAAS,KAAK,EAAE,OAAO,oCAAoC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACtF;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACA,QAAM,YAAY,cAAc,OAAO;AAEvC,QAAM,WAAW,SAAS,KAAK,EAAE,IAAI,UAAU,GAAG,EAAE,QAAQ,IAAI,CAAC;AACjE,gCAA8B,UAAU,cAAc,UAAU;AAAA,IAC9D,cAAc;AAAA,IACd,YAAY;AAAA,EACd,CAAC;AACD,SAAO;AACT;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa,EAAE,QAAQ,mBAAmB;AAAA,MAC1C,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,sBAAsB;AAAA,MAC7E;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,oBAAoB;AAAA,QACzE,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,oBAAoB;AAAA,QAC7E,EAAE,QAAQ,KAAK,aAAa,sCAAsC,QAAQ,oBAAoB;AAAA,QAC9F,EAAE,QAAQ,KAAK,aAAa,qCAAqC,QAAQ,oBAAoB;AAAA,MAC/F;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { replyMessageSchema } from '../../../data/validators'\nimport { attachOperationMetadataHeader } from '../../../lib/operationMetadata'\nimport { canUseMessageEmailFeature, resolveMessageContext } from '../../../lib/routeHelpers'\nimport { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from '../../guards'\nimport {\n errorResponseSchema,\n forwardResponseSchema,\n replyMessageSchema as replyOpenApiSchema,\n} from '../../openapi'\nimport { MessageCommandExecuteResult } from '../../../commands/shared'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['messages.compose'] },\n}\n\nexport async function POST(req: Request, { params }: { params: { id: string } }) {\n const { ctx, scope } = await resolveMessageContext(req)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const body = await req.json().catch(() => ({}))\n const input = replyMessageSchema.parse(body)\n if (input.sendViaEmail && !(await canUseMessageEmailFeature(ctx, scope))) {\n return Response.json({ error: 'Missing feature: messages.email' }, { status: 403 })\n }\n\n const guardResult = await runMessageMutationGuards(\n ctx.container,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input as Record<string, unknown>,\n },\n resolveUserFeatures(ctx.auth),\n )\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n let commandResult\n try {\n commandResult = await commandBus.execute<unknown, MessageCommandExecuteResult>('messages.messages.reply', {\n input: {\n ...input,\n messageId: params.id,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n ctx: {\n container: ctx.container,\n auth: ctx.auth ?? null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n request: req,\n },\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.message === 'Message not found') {\n return Response.json({ error: 'Message not found' }, { status: 404 })\n }\n if (error.message === 'Access denied') {\n return Response.json({ error: 'Access denied' }, { status: 403 })\n }\n if (error.message === 'Reply is not allowed for this message type') {\n return Response.json({ error: 'Reply is not allowed for this message type' }, { status: 409 })\n }\n if (error.message === 'No recipients available for reply') {\n return Response.json({ error: 'No recipients available for reply' }, { status: 409 })\n }\n }\n throw error\n }\n const messageId = commandResult.result.id\n\n const response = Response.json({ id: messageId }, { status: 201 })\n attachOperationMetadataHeader(response, commandResult.logEntry, {\n resourceKind: 'messages.message',\n resourceId: messageId,\n })\n await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: 'messages.message',\n resourceId: messageId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n return response\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n POST: {\n summary: 'Reply to message',\n requestBody: { schema: replyOpenApiSchema },\n responses: [\n { status: 201, description: 'Reply created', schema: forwardResponseSchema },\n ],\n errors: [\n { status: 403, description: 'Access denied', schema: errorResponseSchema },\n { status: 404, description: 'Message not found', schema: errorResponseSchema },\n { status: 409, description: 'Reply not allowed for message type', schema: errorResponseSchema },\n { status: 409, description: 'No recipients available for reply', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import { getMessageObjectType } from "../../lib/message-objects-registry.js";
7
7
  import { getMessageTypeOrDefault } from "../../lib/message-types-registry.js";
8
8
  import { attachOperationMetadataHeader } from "../../lib/operationMetadata.js";
9
9
  import { hasOrganizationAccess, resolveMessageContext } from "../../lib/routeHelpers.js";
10
+ import { resolveUserFeatures, runMessageMutationGuardAfterSuccess, runMessageMutationGuards } from "../guards.js";
10
11
  import {
11
12
  errorResponseSchema,
12
13
  messageDetailResponseSchema,
@@ -225,6 +226,27 @@ async function PATCH(req, { params }) {
225
226
  if (!message.isDraft) {
226
227
  return Response.json({ error: "Only draft messages can be edited" }, { status: 409 });
227
228
  }
229
+ const guardResult = await runMessageMutationGuards(
230
+ ctx.container,
231
+ {
232
+ tenantId: scope.tenantId,
233
+ organizationId: scope.organizationId,
234
+ userId: scope.userId,
235
+ resourceKind: "messages.message",
236
+ resourceId: message.id,
237
+ operation: "update",
238
+ requestMethod: req.method,
239
+ requestHeaders: req.headers,
240
+ mutationPayload: input
241
+ },
242
+ resolveUserFeatures(ctx.auth)
243
+ );
244
+ if (!guardResult.ok) {
245
+ return Response.json(
246
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
247
+ { status: guardResult.errorStatus ?? 422 }
248
+ );
249
+ }
228
250
  try {
229
251
  const { logEntry } = await commandBus.execute("messages.messages.update_draft", {
230
252
  input: {
@@ -248,6 +270,16 @@ async function PATCH(req, { params }) {
248
270
  resourceKind: "messages.message",
249
271
  resourceId: message.id
250
272
  });
273
+ await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
274
+ tenantId: scope.tenantId,
275
+ organizationId: scope.organizationId,
276
+ userId: scope.userId,
277
+ resourceKind: "messages.message",
278
+ resourceId: message.id,
279
+ operation: "update",
280
+ requestMethod: req.method,
281
+ requestHeaders: req.headers
282
+ });
251
283
  return response;
252
284
  } catch (error) {
253
285
  if (error instanceof Error) {
@@ -279,6 +311,27 @@ async function DELETE(req, { params }) {
279
311
  if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {
280
312
  return Response.json({ error: "Access denied" }, { status: 403 });
281
313
  }
314
+ const guardResult = await runMessageMutationGuards(
315
+ ctx.container,
316
+ {
317
+ tenantId: scope.tenantId,
318
+ organizationId: scope.organizationId,
319
+ userId: scope.userId,
320
+ resourceKind: "messages.message",
321
+ resourceId: params.id,
322
+ operation: "delete",
323
+ requestMethod: req.method,
324
+ requestHeaders: req.headers,
325
+ mutationPayload: null
326
+ },
327
+ resolveUserFeatures(ctx.auth)
328
+ );
329
+ if (!guardResult.ok) {
330
+ return Response.json(
331
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
332
+ { status: guardResult.errorStatus ?? 422 }
333
+ );
334
+ }
282
335
  try {
283
336
  const { logEntry } = await commandBus.execute("messages.messages.delete_for_actor", {
284
337
  input: {
@@ -301,6 +354,16 @@ async function DELETE(req, { params }) {
301
354
  resourceKind: "messages.message",
302
355
  resourceId: params.id
303
356
  });
357
+ await runMessageMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
358
+ tenantId: scope.tenantId,
359
+ organizationId: scope.organizationId,
360
+ userId: scope.userId,
361
+ resourceKind: "messages.message",
362
+ resourceId: params.id,
363
+ operation: "delete",
364
+ requestMethod: req.method,
365
+ requestHeaders: req.headers
366
+ });
304
367
  return response;
305
368
  } catch (error) {
306
369
  if (error instanceof Error && error.message === "Access denied") {