@open-mercato/core 0.4.5-develop-5191db4ef3 → 0.4.5-develop-9f9549ebc8

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 (385) hide show
  1. package/dist/generated/entities/message/index.js +65 -0
  2. package/dist/generated/entities/message/index.js.map +7 -0
  3. package/dist/generated/entities/message_access_token/index.js +19 -0
  4. package/dist/generated/entities/message_access_token/index.js.map +7 -0
  5. package/dist/generated/entities/message_confirmation/index.js +21 -0
  6. package/dist/generated/entities/message_confirmation/index.js.map +7 -0
  7. package/dist/generated/entities/message_object/index.js +23 -0
  8. package/dist/generated/entities/message_object/index.js.map +7 -0
  9. package/dist/generated/entities/message_recipient/index.js +31 -0
  10. package/dist/generated/entities/message_recipient/index.js.map +7 -0
  11. package/dist/generated/entities.ids.generated.js +8 -0
  12. package/dist/generated/entities.ids.generated.js.map +2 -2
  13. package/dist/generated/entity-fields-registry.js +10 -0
  14. package/dist/generated/entity-fields-registry.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/deals/[id]/page.js +27 -8
  16. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  17. package/dist/modules/customers/lib/messageObjectPreviews.js +131 -0
  18. package/dist/modules/customers/lib/messageObjectPreviews.js.map +7 -0
  19. package/dist/modules/customers/message-objects.js +71 -0
  20. package/dist/modules/customers/message-objects.js.map +7 -0
  21. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js +51 -0
  22. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js.map +7 -0
  23. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js +35 -0
  24. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js.map +7 -0
  25. package/dist/modules/customers/widgets/messages/index.js +7 -0
  26. package/dist/modules/customers/widgets/messages/index.js.map +7 -0
  27. package/dist/modules/messages/acl.js +15 -0
  28. package/dist/modules/messages/acl.js.map +7 -0
  29. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +92 -0
  30. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +7 -0
  31. package/dist/modules/messages/api/[id]/archive/route.js +120 -0
  32. package/dist/modules/messages/api/[id]/archive/route.js.map +7 -0
  33. package/dist/modules/messages/api/[id]/attachments/route.js +195 -0
  34. package/dist/modules/messages/api/[id]/attachments/route.js.map +7 -0
  35. package/dist/modules/messages/api/[id]/confirmation/route.js +67 -0
  36. package/dist/modules/messages/api/[id]/confirmation/route.js.map +7 -0
  37. package/dist/modules/messages/api/[id]/conversation/archive/route.js +68 -0
  38. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +7 -0
  39. package/dist/modules/messages/api/[id]/conversation/read/route.js +68 -0
  40. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +7 -0
  41. package/dist/modules/messages/api/[id]/conversation/route.js +68 -0
  42. package/dist/modules/messages/api/[id]/conversation/route.js.map +7 -0
  43. package/dist/modules/messages/api/[id]/forward/route.js +85 -0
  44. package/dist/modules/messages/api/[id]/forward/route.js.map +7 -0
  45. package/dist/modules/messages/api/[id]/forward-preview/route.js +70 -0
  46. package/dist/modules/messages/api/[id]/forward-preview/route.js.map +7 -0
  47. package/dist/modules/messages/api/[id]/read/route.js +120 -0
  48. package/dist/modules/messages/api/[id]/read/route.js.map +7 -0
  49. package/dist/modules/messages/api/[id]/reply/route.js +87 -0
  50. package/dist/modules/messages/api/[id]/reply/route.js.map +7 -0
  51. package/dist/modules/messages/api/[id]/route.js +350 -0
  52. package/dist/modules/messages/api/[id]/route.js.map +7 -0
  53. package/dist/modules/messages/api/object-types/route.js +54 -0
  54. package/dist/modules/messages/api/object-types/route.js.map +7 -0
  55. package/dist/modules/messages/api/openapi.js +261 -0
  56. package/dist/modules/messages/api/openapi.js.map +7 -0
  57. package/dist/modules/messages/api/route.js +262 -0
  58. package/dist/modules/messages/api/route.js.map +7 -0
  59. package/dist/modules/messages/api/token/[token]/route.js +99 -0
  60. package/dist/modules/messages/api/token/[token]/route.js.map +7 -0
  61. package/dist/modules/messages/api/types/route.js +40 -0
  62. package/dist/modules/messages/api/types/route.js.map +7 -0
  63. package/dist/modules/messages/api/unread-count/route.js +43 -0
  64. package/dist/modules/messages/api/unread-count/route.js.map +7 -0
  65. package/dist/modules/messages/backend/messages/[id]/page.js +10 -0
  66. package/dist/modules/messages/backend/messages/[id]/page.js.map +7 -0
  67. package/dist/modules/messages/backend/messages/[id]/page.meta.js +16 -0
  68. package/dist/modules/messages/backend/messages/[id]/page.meta.js.map +7 -0
  69. package/dist/modules/messages/backend/messages/compose/page.js +10 -0
  70. package/dist/modules/messages/backend/messages/compose/page.js.map +7 -0
  71. package/dist/modules/messages/backend/messages/compose/page.meta.js +17 -0
  72. package/dist/modules/messages/backend/messages/compose/page.meta.js.map +7 -0
  73. package/dist/modules/messages/backend/page.js +10 -0
  74. package/dist/modules/messages/backend/page.js.map +7 -0
  75. package/dist/modules/messages/backend/page.meta.js +33 -0
  76. package/dist/modules/messages/backend/page.meta.js.map +7 -0
  77. package/dist/modules/messages/commands/actions.js +265 -0
  78. package/dist/modules/messages/commands/actions.js.map +7 -0
  79. package/dist/modules/messages/commands/attachments.js +217 -0
  80. package/dist/modules/messages/commands/attachments.js.map +7 -0
  81. package/dist/modules/messages/commands/confirmations.js +151 -0
  82. package/dist/modules/messages/commands/confirmations.js.map +7 -0
  83. package/dist/modules/messages/commands/conversation.js +240 -0
  84. package/dist/modules/messages/commands/conversation.js.map +7 -0
  85. package/dist/modules/messages/commands/messages.js +748 -0
  86. package/dist/modules/messages/commands/messages.js.map +7 -0
  87. package/dist/modules/messages/commands/recipients.js +259 -0
  88. package/dist/modules/messages/commands/recipients.js.map +7 -0
  89. package/dist/modules/messages/commands/shared.js +258 -0
  90. package/dist/modules/messages/commands/shared.js.map +7 -0
  91. package/dist/modules/messages/commands/tokens.js +69 -0
  92. package/dist/modules/messages/commands/tokens.js.map +7 -0
  93. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -0
  94. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +7 -0
  95. package/dist/modules/messages/components/MessageDetailPageClient.js +261 -0
  96. package/dist/modules/messages/components/MessageDetailPageClient.js.map +7 -0
  97. package/dist/modules/messages/components/MessagesInboxPageClient.js +390 -0
  98. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +7 -0
  99. package/dist/modules/messages/components/confirmation/MessageConfirmationActions.js +31 -0
  100. package/dist/modules/messages/components/confirmation/MessageConfirmationActions.js.map +7 -0
  101. package/dist/modules/messages/components/confirmation/MessageConfirmationContent.js +69 -0
  102. package/dist/modules/messages/components/confirmation/MessageConfirmationContent.js.map +7 -0
  103. package/dist/modules/messages/components/defaults/DefaultMessageActions.js +31 -0
  104. package/dist/modules/messages/components/defaults/DefaultMessageActions.js.map +7 -0
  105. package/dist/modules/messages/components/defaults/DefaultMessageContent.js +19 -0
  106. package/dist/modules/messages/components/defaults/DefaultMessageContent.js.map +7 -0
  107. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +90 -0
  108. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +7 -0
  109. package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js +86 -0
  110. package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js.map +7 -0
  111. package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js +61 -0
  112. package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js.map +7 -0
  113. package/dist/modules/messages/components/message-detail/detail-panels.js +27 -0
  114. package/dist/modules/messages/components/message-detail/detail-panels.js.map +7 -0
  115. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +52 -0
  116. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +7 -0
  117. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +289 -0
  118. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +7 -0
  119. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +103 -0
  120. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +7 -0
  121. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +78 -0
  122. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +7 -0
  123. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js +94 -0
  124. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js.map +7 -0
  125. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +110 -0
  126. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +7 -0
  127. package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js +58 -0
  128. package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js.map +7 -0
  129. package/dist/modules/messages/components/message-detail/panels/actions-panel.js +51 -0
  130. package/dist/modules/messages/components/message-detail/panels/actions-panel.js.map +7 -0
  131. package/dist/modules/messages/components/message-detail/panels/attachments-panel.js +66 -0
  132. package/dist/modules/messages/components/message-detail/panels/attachments-panel.js.map +7 -0
  133. package/dist/modules/messages/components/message-detail/panels/body-panel.js +20 -0
  134. package/dist/modules/messages/components/message-detail/panels/body-panel.js.map +7 -0
  135. package/dist/modules/messages/components/message-detail/panels/composer-dialogs.js +36 -0
  136. package/dist/modules/messages/components/message-detail/panels/composer-dialogs.js.map +7 -0
  137. package/dist/modules/messages/components/message-detail/panels/dialogs.js +96 -0
  138. package/dist/modules/messages/components/message-detail/panels/dialogs.js.map +7 -0
  139. package/dist/modules/messages/components/message-detail/panels/index.js +25 -0
  140. package/dist/modules/messages/components/message-detail/panels/index.js.map +7 -0
  141. package/dist/modules/messages/components/message-detail/panels/meta-panel.js +14 -0
  142. package/dist/modules/messages/components/message-detail/panels/meta-panel.js.map +7 -0
  143. package/dist/modules/messages/components/message-detail/panels/objects-panel.js +51 -0
  144. package/dist/modules/messages/components/message-detail/panels/objects-panel.js.map +7 -0
  145. package/dist/modules/messages/components/message-detail/panels/thread-panel.js +54 -0
  146. package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +7 -0
  147. package/dist/modules/messages/components/message-detail/types.js +1 -0
  148. package/dist/modules/messages/components/message-detail/types.js.map +7 -0
  149. package/dist/modules/messages/components/message-detail/utils.js +54 -0
  150. package/dist/modules/messages/components/message-detail/utils.js.map +7 -0
  151. package/dist/modules/messages/components/utils/PriorityBadge.js +52 -0
  152. package/dist/modules/messages/components/utils/PriorityBadge.js.map +7 -0
  153. package/dist/modules/messages/components/utils/typeUiRegistry.js +77 -0
  154. package/dist/modules/messages/components/utils/typeUiRegistry.js.map +7 -0
  155. package/dist/modules/messages/data/entities.js +309 -0
  156. package/dist/modules/messages/data/entities.js.map +7 -0
  157. package/dist/modules/messages/data/validators.js +272 -0
  158. package/dist/modules/messages/data/validators.js.map +7 -0
  159. package/dist/modules/messages/emails/MessageEmail.js +108 -0
  160. package/dist/modules/messages/emails/MessageEmail.js.map +7 -0
  161. package/dist/modules/messages/events.js +24 -0
  162. package/dist/modules/messages/events.js.map +7 -0
  163. package/dist/modules/messages/frontend/messages/view/[token]/page.js +247 -0
  164. package/dist/modules/messages/frontend/messages/view/[token]/page.js.map +7 -0
  165. package/dist/modules/messages/frontend/messages/view/[token]/page.meta.js +9 -0
  166. package/dist/modules/messages/frontend/messages/view/[token]/page.meta.js.map +7 -0
  167. package/dist/modules/messages/index.js +21 -0
  168. package/dist/modules/messages/index.js.map +7 -0
  169. package/dist/modules/messages/lib/actions.js +141 -0
  170. package/dist/modules/messages/lib/actions.js.map +7 -0
  171. package/dist/modules/messages/lib/attachments.js +131 -0
  172. package/dist/modules/messages/lib/attachments.js.map +7 -0
  173. package/dist/modules/messages/lib/constants.js +7 -0
  174. package/dist/modules/messages/lib/constants.js.map +7 -0
  175. package/dist/modules/messages/lib/email-sender.js +201 -0
  176. package/dist/modules/messages/lib/email-sender.js.map +7 -0
  177. package/dist/modules/messages/lib/forwarding.js +179 -0
  178. package/dist/modules/messages/lib/forwarding.js.map +7 -0
  179. package/dist/modules/messages/lib/message-objects-registry.js +49 -0
  180. package/dist/modules/messages/lib/message-objects-registry.js.map +7 -0
  181. package/dist/modules/messages/lib/message-types-registry.js +41 -0
  182. package/dist/modules/messages/lib/message-types-registry.js.map +7 -0
  183. package/dist/modules/messages/lib/object-validation.js +20 -0
  184. package/dist/modules/messages/lib/object-validation.js.map +7 -0
  185. package/dist/modules/messages/lib/operationMetadata.js +21 -0
  186. package/dist/modules/messages/lib/operationMetadata.js.map +7 -0
  187. package/dist/modules/messages/lib/priorityUtils.js +61 -0
  188. package/dist/modules/messages/lib/priorityUtils.js.map +7 -0
  189. package/dist/modules/messages/lib/routeHelpers.js +44 -0
  190. package/dist/modules/messages/lib/routeHelpers.js.map +7 -0
  191. package/dist/modules/messages/message-objects.js +7 -0
  192. package/dist/modules/messages/message-objects.js.map +7 -0
  193. package/dist/modules/messages/message-types.js +67 -0
  194. package/dist/modules/messages/message-types.js.map +7 -0
  195. package/dist/modules/messages/migrations/Migration20260213181243.js +31 -0
  196. package/dist/modules/messages/migrations/Migration20260213181243.js.map +7 -0
  197. package/dist/modules/messages/migrations/Migration20260215165126.js +16 -0
  198. package/dist/modules/messages/migrations/Migration20260215165126.js.map +7 -0
  199. package/dist/modules/messages/notifications.js +27 -0
  200. package/dist/modules/messages/notifications.js.map +7 -0
  201. package/dist/modules/messages/setup.js +21 -0
  202. package/dist/modules/messages/setup.js.map +7 -0
  203. package/dist/modules/messages/subscribers/message-notification.js +108 -0
  204. package/dist/modules/messages/subscribers/message-notification.js.map +7 -0
  205. package/dist/modules/messages/workers/send-email.worker.js +253 -0
  206. package/dist/modules/messages/workers/send-email.worker.js.map +7 -0
  207. package/dist/modules/sales/backend/sales/documents/[id]/page.js +30 -11
  208. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  209. package/dist/modules/sales/commands/payments.js +12 -6
  210. package/dist/modules/sales/commands/payments.js.map +2 -2
  211. package/dist/modules/sales/lib/messageObjectPreviews.js +114 -0
  212. package/dist/modules/sales/lib/messageObjectPreviews.js.map +7 -0
  213. package/dist/modules/sales/message-objects.js +57 -0
  214. package/dist/modules/sales/message-objects.js.map +7 -0
  215. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js +51 -0
  216. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js.map +7 -0
  217. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js +36 -0
  218. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js.map +7 -0
  219. package/dist/modules/sales/widgets/messages/index.js +7 -0
  220. package/dist/modules/sales/widgets/messages/index.js.map +7 -0
  221. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +55 -1
  222. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  223. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +60 -1
  224. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  225. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +2 -19
  226. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  227. package/dist/modules/staff/components/LeaveRequestDetail.js +112 -0
  228. package/dist/modules/staff/components/LeaveRequestDetail.js.map +7 -0
  229. package/dist/modules/staff/components/LeaveRequestForm.js +3 -1
  230. package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
  231. package/dist/modules/staff/components/LeaveRequestPreview.js +43 -0
  232. package/dist/modules/staff/components/LeaveRequestPreview.js.map +7 -0
  233. package/dist/modules/staff/lib/messageObjectPreviews.js +148 -0
  234. package/dist/modules/staff/lib/messageObjectPreviews.js.map +7 -0
  235. package/dist/modules/staff/message-objects.js +104 -0
  236. package/dist/modules/staff/message-objects.js.map +7 -0
  237. package/dist/modules/staff/message-types.js +23 -0
  238. package/dist/modules/staff/message-types.js.map +7 -0
  239. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js +51 -0
  240. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js.map +7 -0
  241. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js +34 -0
  242. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js.map +7 -0
  243. package/dist/modules/staff/widgets/messages/index.js +7 -0
  244. package/dist/modules/staff/widgets/messages/index.js.map +7 -0
  245. package/generated/entities/message/index.ts +31 -0
  246. package/generated/entities/message_access_token/index.ts +8 -0
  247. package/generated/entities/message_confirmation/index.ts +9 -0
  248. package/generated/entities/message_object/index.ts +10 -0
  249. package/generated/entities/message_recipient/index.ts +14 -0
  250. package/generated/entities.ids.generated.ts +8 -0
  251. package/generated/entity-fields-registry.ts +10 -0
  252. package/jest.setup.ts +5 -0
  253. package/package.json +2 -2
  254. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +20 -4
  255. package/src/modules/customers/i18n/de.json +4 -0
  256. package/src/modules/customers/i18n/en.json +4 -0
  257. package/src/modules/customers/i18n/es.json +4 -0
  258. package/src/modules/customers/i18n/pl.json +4 -0
  259. package/src/modules/customers/lib/messageObjectPreviews.ts +154 -0
  260. package/src/modules/customers/message-objects.ts +70 -0
  261. package/src/modules/customers/widgets/messages/CustomerMessageObjectDetail.tsx +57 -0
  262. package/src/modules/customers/widgets/messages/CustomerMessageObjectPreview.tsx +49 -0
  263. package/src/modules/customers/widgets/messages/index.ts +2 -0
  264. package/src/modules/messages/acl.ts +11 -0
  265. package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +103 -0
  266. package/src/modules/messages/api/[id]/archive/route.ts +138 -0
  267. package/src/modules/messages/api/[id]/attachments/route.ts +217 -0
  268. package/src/modules/messages/api/[id]/confirmation/route.ts +73 -0
  269. package/src/modules/messages/api/[id]/conversation/archive/route.ts +69 -0
  270. package/src/modules/messages/api/[id]/conversation/read/route.ts +69 -0
  271. package/src/modules/messages/api/[id]/conversation/route.ts +69 -0
  272. package/src/modules/messages/api/[id]/forward/route.ts +87 -0
  273. package/src/modules/messages/api/[id]/forward-preview/route.ts +75 -0
  274. package/src/modules/messages/api/[id]/read/route.ts +138 -0
  275. package/src/modules/messages/api/[id]/reply/route.ts +89 -0
  276. package/src/modules/messages/api/[id]/route.ts +401 -0
  277. package/src/modules/messages/api/object-types/route.ts +54 -0
  278. package/src/modules/messages/api/openapi.ts +261 -0
  279. package/src/modules/messages/api/route.ts +374 -0
  280. package/src/modules/messages/api/token/[token]/route.ts +103 -0
  281. package/src/modules/messages/api/types/route.ts +39 -0
  282. package/src/modules/messages/api/unread-count/route.ts +55 -0
  283. package/src/modules/messages/backend/messages/[id]/page.meta.ts +12 -0
  284. package/src/modules/messages/backend/messages/[id]/page.tsx +12 -0
  285. package/src/modules/messages/backend/messages/compose/page.meta.ts +13 -0
  286. package/src/modules/messages/backend/messages/compose/page.tsx +12 -0
  287. package/src/modules/messages/backend/page.meta.ts +31 -0
  288. package/src/modules/messages/backend/page.tsx +12 -0
  289. package/src/modules/messages/commands/actions.ts +307 -0
  290. package/src/modules/messages/commands/attachments.ts +227 -0
  291. package/src/modules/messages/commands/confirmations.ts +183 -0
  292. package/src/modules/messages/commands/conversation.ts +292 -0
  293. package/src/modules/messages/commands/messages.ts +845 -0
  294. package/src/modules/messages/commands/recipients.ts +281 -0
  295. package/src/modules/messages/commands/shared.ts +350 -0
  296. package/src/modules/messages/commands/tokens.ts +80 -0
  297. package/src/modules/messages/components/ComposeMessagePageClient.tsx +23 -0
  298. package/src/modules/messages/components/MessageDetailPageClient.tsx +287 -0
  299. package/src/modules/messages/components/MessagesInboxPageClient.tsx +469 -0
  300. package/src/modules/messages/components/confirmation/MessageConfirmationActions.tsx +35 -0
  301. package/src/modules/messages/components/confirmation/MessageConfirmationContent.tsx +88 -0
  302. package/src/modules/messages/components/defaults/DefaultMessageActions.tsx +37 -0
  303. package/src/modules/messages/components/defaults/DefaultMessageContent.tsx +21 -0
  304. package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +102 -0
  305. package/src/modules/messages/components/defaults/MessageRecordObjectDetail.tsx +114 -0
  306. package/src/modules/messages/components/defaults/MessageRecordObjectPreview.tsx +74 -0
  307. package/src/modules/messages/components/message-detail/detail-panels.ts +13 -0
  308. package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +56 -0
  309. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +367 -0
  310. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +134 -0
  311. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +102 -0
  312. package/src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx +108 -0
  313. package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +144 -0
  314. package/src/modules/messages/components/message-detail/panels/MessageListComponent.tsx +63 -0
  315. package/src/modules/messages/components/message-detail/panels/actions-panel.tsx +66 -0
  316. package/src/modules/messages/components/message-detail/panels/attachments-panel.tsx +86 -0
  317. package/src/modules/messages/components/message-detail/panels/body-panel.tsx +32 -0
  318. package/src/modules/messages/components/message-detail/panels/composer-dialogs.tsx +42 -0
  319. package/src/modules/messages/components/message-detail/panels/dialogs.tsx +107 -0
  320. package/src/modules/messages/components/message-detail/panels/index.ts +11 -0
  321. package/src/modules/messages/components/message-detail/panels/meta-panel.tsx +19 -0
  322. package/src/modules/messages/components/message-detail/panels/objects-panel.tsx +65 -0
  323. package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +65 -0
  324. package/src/modules/messages/components/message-detail/types.ts +114 -0
  325. package/src/modules/messages/components/message-detail/utils.ts +62 -0
  326. package/src/modules/messages/components/utils/PriorityBadge.tsx +63 -0
  327. package/src/modules/messages/components/utils/typeUiRegistry.ts +106 -0
  328. package/src/modules/messages/data/entities.ts +284 -0
  329. package/src/modules/messages/data/validators.ts +297 -0
  330. package/src/modules/messages/emails/MessageEmail.tsx +143 -0
  331. package/src/modules/messages/events.ts +24 -0
  332. package/src/modules/messages/frontend/messages/view/[token]/page.meta.ts +5 -0
  333. package/src/modules/messages/frontend/messages/view/[token]/page.tsx +389 -0
  334. package/src/modules/messages/i18n/de.json +240 -0
  335. package/src/modules/messages/i18n/en.json +240 -0
  336. package/src/modules/messages/i18n/es.json +240 -0
  337. package/src/modules/messages/i18n/pl.json +240 -0
  338. package/src/modules/messages/index.ts +19 -0
  339. package/src/modules/messages/lib/actions.ts +204 -0
  340. package/src/modules/messages/lib/attachments.ts +197 -0
  341. package/src/modules/messages/lib/constants.ts +2 -0
  342. package/src/modules/messages/lib/email-sender.ts +255 -0
  343. package/src/modules/messages/lib/forwarding.ts +240 -0
  344. package/src/modules/messages/lib/message-objects-registry.ts +60 -0
  345. package/src/modules/messages/lib/message-types-registry.ts +48 -0
  346. package/src/modules/messages/lib/object-validation.ts +26 -0
  347. package/src/modules/messages/lib/operationMetadata.ts +43 -0
  348. package/src/modules/messages/lib/priorityUtils.ts +76 -0
  349. package/src/modules/messages/lib/routeHelpers.ts +65 -0
  350. package/src/modules/messages/message-objects.ts +5 -0
  351. package/src/modules/messages/message-types.ts +65 -0
  352. package/src/modules/messages/migrations/.snapshot-open-mercato.json +957 -0
  353. package/src/modules/messages/migrations/Migration20260213181243.ts +34 -0
  354. package/src/modules/messages/migrations/Migration20260215165126.ts +16 -0
  355. package/src/modules/messages/notifications.ts +25 -0
  356. package/src/modules/messages/setup.ts +19 -0
  357. package/src/modules/messages/subscribers/message-notification.ts +138 -0
  358. package/src/modules/messages/workers/send-email.worker.ts +321 -0
  359. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +23 -7
  360. package/src/modules/sales/commands/payments.ts +12 -6
  361. package/src/modules/sales/i18n/de.json +3 -0
  362. package/src/modules/sales/i18n/en.json +3 -0
  363. package/src/modules/sales/i18n/es.json +3 -0
  364. package/src/modules/sales/i18n/pl.json +3 -0
  365. package/src/modules/sales/lib/messageObjectPreviews.ts +150 -0
  366. package/src/modules/sales/message-objects.ts +56 -0
  367. package/src/modules/sales/widgets/messages/SalesDocumentMessageDetail.tsx +57 -0
  368. package/src/modules/sales/widgets/messages/SalesDocumentMessagePreview.tsx +46 -0
  369. package/src/modules/sales/widgets/messages/index.ts +2 -0
  370. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +54 -0
  371. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +58 -0
  372. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +2 -32
  373. package/src/modules/staff/components/LeaveRequestDetail.tsx +135 -0
  374. package/src/modules/staff/components/LeaveRequestForm.tsx +3 -0
  375. package/src/modules/staff/components/LeaveRequestPreview.tsx +74 -0
  376. package/src/modules/staff/i18n/de.json +8 -0
  377. package/src/modules/staff/i18n/en.json +8 -0
  378. package/src/modules/staff/i18n/es.json +8 -0
  379. package/src/modules/staff/i18n/pl.json +8 -0
  380. package/src/modules/staff/lib/messageObjectPreviews.ts +182 -0
  381. package/src/modules/staff/message-objects.ts +102 -0
  382. package/src/modules/staff/message-types.ts +21 -0
  383. package/src/modules/staff/widgets/messages/StaffMessageObjectDetail.tsx +57 -0
  384. package/src/modules/staff/widgets/messages/StaffMessageObjectPreview.tsx +44 -0
  385. package/src/modules/staff/widgets/messages/index.ts +2 -0
@@ -0,0 +1,204 @@
1
+ import type { Message, MessageAction, MessageActionData, MessageObject } from '../data/entities'
2
+ import { getMessageObjectType } from './message-objects-registry'
3
+ import { getMessageType } from './message-types-registry'
4
+
5
+ type MessageActionSource = 'message' | 'type_default' | 'object'
6
+
7
+ export type MessageActionObjectRef = {
8
+ objectId: string
9
+ entityModule: string
10
+ entityType: string
11
+ entityId: string
12
+ }
13
+
14
+ export type ResolvedMessageAction = MessageAction & {
15
+ source: MessageActionSource
16
+ objectRef?: MessageActionObjectRef
17
+ }
18
+
19
+ export type MessageActionResolutionContext = {
20
+ tenantId: string
21
+ organizationId?: string | null
22
+ userId: string
23
+ }
24
+
25
+ function normalizeActionLabel(
26
+ action: Pick<MessageAction, 'label' | 'id'>,
27
+ fallback?: string | null,
28
+ ): string {
29
+ if (typeof action.label === 'string' && action.label.trim().length > 0) {
30
+ return action.label
31
+ }
32
+ if (typeof fallback === 'string' && fallback.trim().length > 0) {
33
+ return fallback.trim()
34
+ }
35
+ return action.id
36
+ }
37
+
38
+ export function isTerminalMessageAction(
39
+ action: Pick<MessageAction, 'isTerminal' | 'commandId' | 'href'>,
40
+ ): boolean {
41
+ if (typeof action.isTerminal === 'boolean') return action.isTerminal
42
+ if (typeof action.commandId === 'string' && action.commandId.trim().length > 0) return true
43
+ if (typeof action.href === 'string' && action.href.trim().length > 0) return false
44
+ return true
45
+ }
46
+
47
+ export function buildMessageObjectActionId(objectId: string, actionId: string): string {
48
+ return `object:${objectId}:${actionId}`
49
+ }
50
+
51
+ function readActionDataExpiry(message: Message): string | undefined {
52
+ if (message.actionData?.expiresAt) return message.actionData.expiresAt
53
+ const messageType = getMessageType(message.type)
54
+ if (!messageType?.actionsExpireAfterHours || !message.sentAt) return undefined
55
+ return new Date(
56
+ message.sentAt.getTime() + messageType.actionsExpireAfterHours * 60 * 60 * 1000,
57
+ ).toISOString()
58
+ }
59
+
60
+ export function buildResolvedMessageActions(
61
+ message: Message,
62
+ objects: MessageObject[],
63
+ ): MessageActionData | null {
64
+ const resolved: ResolvedMessageAction[] = []
65
+ const usedIds = new Set<string>()
66
+
67
+ const pushAction = (
68
+ action: MessageAction,
69
+ source: MessageActionSource,
70
+ objectRef?: MessageActionObjectRef,
71
+ labelFallback?: string | null,
72
+ ) => {
73
+ const id = typeof action.id === 'string' ? action.id.trim() : ''
74
+ if (!id || usedIds.has(id)) return
75
+ usedIds.add(id)
76
+
77
+ resolved.push({
78
+ ...action,
79
+ id,
80
+ label: normalizeActionLabel(action, labelFallback),
81
+ source,
82
+ objectRef,
83
+ })
84
+ }
85
+
86
+ for (const action of message.actionData?.actions ?? []) {
87
+ pushAction(action, 'message')
88
+ }
89
+
90
+ const messageType = getMessageType(message.type)
91
+ for (const action of messageType?.defaultActions ?? []) {
92
+ pushAction(action, 'type_default')
93
+ }
94
+
95
+ for (const object of objects) {
96
+ if (!object.actionRequired || !object.actionType) continue
97
+ const objectType = getMessageObjectType(object.entityModule, object.entityType)
98
+ if (!objectType) continue
99
+ const actionDef = objectType.actions.find((entry) => entry.id === object.actionType)
100
+ if (!actionDef) continue
101
+
102
+ pushAction(
103
+ {
104
+ id: buildMessageObjectActionId(object.id, actionDef.id),
105
+ label: object.actionLabel ?? actionDef.id,
106
+ labelKey: actionDef.labelKey,
107
+ variant: actionDef.variant,
108
+ icon: actionDef.icon,
109
+ commandId: actionDef.commandId,
110
+ href: actionDef.href,
111
+ isTerminal: actionDef.isTerminal,
112
+ confirmRequired: actionDef.confirmRequired,
113
+ confirmMessage: actionDef.confirmMessage,
114
+ },
115
+ 'object',
116
+ {
117
+ objectId: object.id,
118
+ entityModule: object.entityModule,
119
+ entityType: object.entityType,
120
+ entityId: object.entityId,
121
+ },
122
+ object.actionLabel ?? actionDef.id,
123
+ )
124
+ }
125
+
126
+ const expiresAt = readActionDataExpiry(message)
127
+ if (resolved.length === 0 && !expiresAt) {
128
+ return null
129
+ }
130
+
131
+ const configuredPrimaryActionId = message.actionData?.primaryActionId
132
+ const primaryActionId = configuredPrimaryActionId && resolved.some((entry) => entry.id === configuredPrimaryActionId)
133
+ ? configuredPrimaryActionId
134
+ : resolved[0]?.id
135
+
136
+ return {
137
+ actions: resolved,
138
+ primaryActionId,
139
+ expiresAt,
140
+ }
141
+ }
142
+
143
+ export function findResolvedMessageActionById(
144
+ message: Message,
145
+ objects: MessageObject[],
146
+ actionId: string,
147
+ ): ResolvedMessageAction | null {
148
+ const actionData = buildResolvedMessageActions(message, objects)
149
+ const match = actionData?.actions.find((entry) => entry.id === actionId)
150
+ return (match as ResolvedMessageAction | undefined) ?? null
151
+ }
152
+
153
+ function buildTemplateContext(
154
+ message: Message,
155
+ resolutionContext: MessageActionResolutionContext,
156
+ objectRef?: MessageActionObjectRef,
157
+ ): Record<string, unknown> {
158
+ return {
159
+ messageId: message.id,
160
+ sourceEntityId: message.sourceEntityId ?? message.id,
161
+ sourceEntityType: message.sourceEntityType ?? null,
162
+ threadId: message.threadId ?? null,
163
+ parentMessageId: message.parentMessageId ?? null,
164
+ messageType: message.type,
165
+ tenantId: resolutionContext.tenantId,
166
+ organizationId: resolutionContext.organizationId ?? null,
167
+ userId: resolutionContext.userId,
168
+ objectId: objectRef?.objectId ?? null,
169
+ entityId: objectRef?.entityId ?? null,
170
+ entityType: objectRef?.entityType ?? null,
171
+ entityModule: objectRef?.entityModule ?? null,
172
+ }
173
+ }
174
+
175
+ function resolveTemplateString(template: string, context: Record<string, unknown>): string {
176
+ return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (fullMatch, key: string) => {
177
+ const value = context[key]
178
+ if (value == null) return fullMatch
179
+ return String(value)
180
+ })
181
+ }
182
+
183
+ export function resolveActionHref(
184
+ action: ResolvedMessageAction,
185
+ message: Message,
186
+ resolutionContext: MessageActionResolutionContext,
187
+ ): string | null {
188
+ if (!action.href) return null
189
+ const context = buildTemplateContext(message, resolutionContext, action.objectRef)
190
+ return resolveTemplateString(action.href, context)
191
+ }
192
+
193
+ export function resolveActionCommandInput(
194
+ action: ResolvedMessageAction,
195
+ message: Message,
196
+ _resolutionContext: MessageActionResolutionContext,
197
+ requestInput: Record<string, unknown>,
198
+ ): Record<string, unknown> {
199
+ return {
200
+ ...requestInput,
201
+ messageId: message.id,
202
+ actionId: action.id,
203
+ }
204
+ }
@@ -0,0 +1,197 @@
1
+ import type { EntityManager } from '@mikro-orm/core'
2
+ import { MESSAGE_ATTACHMENT_ENTITY_ID } from './constants'
3
+ const LIBRARY_ATTACHMENT_ENTITY_ID = 'attachments:library'
4
+
5
+ function buildOrganizationScopeFilter(organizationId: string | null) {
6
+ if (!organizationId) {
7
+ return { organizationId: null }
8
+ }
9
+ return {
10
+ $or: [
11
+ { organizationId },
12
+ { organizationId: null },
13
+ ],
14
+ }
15
+ }
16
+
17
+ export async function linkAttachmentsToMessage(
18
+ em: EntityManager,
19
+ messageId: string,
20
+ attachmentIds: string[],
21
+ organizationId: string | null,
22
+ tenantId: string,
23
+ ): Promise<void> {
24
+ if (attachmentIds.length === 0) return
25
+
26
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
27
+
28
+ const attachments = await em.find(Attachment, {
29
+ id: { $in: attachmentIds },
30
+ tenantId,
31
+ ...buildOrganizationScopeFilter(organizationId),
32
+ })
33
+
34
+ for (const attachment of attachments) {
35
+ attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID
36
+ attachment.recordId = messageId
37
+ }
38
+
39
+ await em.flush()
40
+ }
41
+
42
+ export async function getMessageAttachments(
43
+ em: EntityManager,
44
+ messageId: string,
45
+ organizationId: string | null,
46
+ tenantId: string,
47
+ ): Promise<Array<{
48
+ id: string
49
+ fileName: string
50
+ fileSize: number
51
+ mimeType: string
52
+ url: string
53
+ }>> {
54
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
55
+
56
+ const attachments = await em.find(Attachment, {
57
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
58
+ recordId: messageId,
59
+ tenantId,
60
+ ...buildOrganizationScopeFilter(organizationId),
61
+ })
62
+
63
+ return attachments.map((attachment) => ({
64
+ id: attachment.id,
65
+ fileName: attachment.fileName,
66
+ fileSize: attachment.fileSize,
67
+ mimeType: attachment.mimeType,
68
+ url: attachment.url,
69
+ }))
70
+ }
71
+
72
+ export type MessageEmailAttachment = {
73
+ fileName: string
74
+ fileSize: number
75
+ mimeType: string
76
+ partitionCode: string
77
+ storagePath: string
78
+ storageDriver: string
79
+ }
80
+
81
+ export async function getMessageEmailAttachments(
82
+ em: EntityManager,
83
+ messageId: string,
84
+ organizationId: string | null,
85
+ tenantId: string,
86
+ ): Promise<MessageEmailAttachment[]> {
87
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
88
+
89
+ const attachments = await em.find(Attachment, {
90
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
91
+ recordId: messageId,
92
+ tenantId,
93
+ ...buildOrganizationScopeFilter(organizationId),
94
+ })
95
+
96
+ return attachments.map((attachment) => ({
97
+ fileName: attachment.fileName,
98
+ fileSize: attachment.fileSize,
99
+ mimeType: attachment.mimeType,
100
+ partitionCode: attachment.partitionCode,
101
+ storagePath: attachment.storagePath,
102
+ storageDriver: attachment.storageDriver,
103
+ }))
104
+ }
105
+
106
+ export async function linkLibraryAttachmentsToMessage(
107
+ em: EntityManager,
108
+ messageId: string,
109
+ sourceRecordId: string,
110
+ organizationId: string | null,
111
+ tenantId: string,
112
+ ): Promise<void> {
113
+ if (!sourceRecordId.trim()) return
114
+
115
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
116
+
117
+ const attachments = await em.find(Attachment, {
118
+ entityId: LIBRARY_ATTACHMENT_ENTITY_ID,
119
+ recordId: sourceRecordId,
120
+ tenantId,
121
+ ...buildOrganizationScopeFilter(organizationId),
122
+ })
123
+
124
+ if (!attachments.length) return
125
+
126
+ for (const attachment of attachments) {
127
+ attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID
128
+ attachment.recordId = messageId
129
+ }
130
+
131
+ await em.flush()
132
+ }
133
+
134
+ export async function copyAttachmentsForForward(
135
+ em: EntityManager,
136
+ sourceMessageId: string,
137
+ targetMessageId: string,
138
+ organizationId: string | null,
139
+ tenantId: string,
140
+ ): Promise<number> {
141
+ return copyAttachmentsForForwardMessages(
142
+ em,
143
+ [sourceMessageId],
144
+ targetMessageId,
145
+ organizationId,
146
+ tenantId,
147
+ )
148
+ }
149
+
150
+ export async function copyAttachmentsForForwardMessages(
151
+ em: EntityManager,
152
+ sourceMessageIds: string[],
153
+ targetMessageId: string,
154
+ targetOrganizationId: string | null,
155
+ tenantId: string,
156
+ ): Promise<number> {
157
+ if (sourceMessageIds.length === 0) return 0
158
+
159
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
160
+ const sourceAttachments = await em.find(Attachment, {
161
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
162
+ recordId: { $in: sourceMessageIds },
163
+ tenantId,
164
+ })
165
+ if (sourceAttachments.length === 0) {
166
+ return 0
167
+ }
168
+
169
+ const dedupedById = new Map<string, typeof sourceAttachments[number]>()
170
+ for (const sourceAttachment of sourceAttachments) {
171
+ if (!dedupedById.has(sourceAttachment.id)) {
172
+ dedupedById.set(sourceAttachment.id, sourceAttachment)
173
+ }
174
+ }
175
+
176
+ for (const sourceAttachment of dedupedById.values()) {
177
+ const copy = em.create(Attachment, {
178
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
179
+ recordId: targetMessageId,
180
+ organizationId: targetOrganizationId,
181
+ tenantId,
182
+ fileName: sourceAttachment.fileName,
183
+ mimeType: sourceAttachment.mimeType,
184
+ fileSize: sourceAttachment.fileSize,
185
+ storageDriver: sourceAttachment.storageDriver,
186
+ storagePath: sourceAttachment.storagePath,
187
+ storageMetadata: sourceAttachment.storageMetadata,
188
+ url: sourceAttachment.url,
189
+ partitionCode: sourceAttachment.partitionCode,
190
+ })
191
+
192
+ em.persist(copy)
193
+ }
194
+
195
+ await em.flush()
196
+ return dedupedById.size
197
+ }
@@ -0,0 +1,2 @@
1
+ export const MESSAGE_ATTACHMENT_ENTITY_ID = 'messages:message'
2
+ export const MESSAGE_ATTACHMENT_PARTITION = 'messages'
@@ -0,0 +1,255 @@
1
+ import * as React from 'react'
2
+ import crypto from 'node:crypto'
3
+ import { promises as fs } from 'fs'
4
+ import type { EntityManager } from '@mikro-orm/postgresql'
5
+ import { sendEmail } from '@open-mercato/shared/lib/email/send'
6
+ import { loadDictionary } from '@open-mercato/shared/lib/i18n/server'
7
+ import { defaultLocale } from '@open-mercato/shared/lib/i18n/config'
8
+ import { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'
9
+ import type { Message, MessageObject } from '../data/entities'
10
+ import { MessageAccessToken } from '../data/entities'
11
+ import MessageEmail from '../emails/MessageEmail'
12
+ import { resolveAttachmentAbsolutePath } from '../../attachments/lib/storage'
13
+ import type { MessageEmailAttachment } from './attachments'
14
+
15
+ const ACCESS_TOKEN_EXPIRY_HOURS = 24 * 7
16
+ const DEBUG = process.env.MESSAGES_EMAIL_DEBUG === 'true'
17
+ const MAX_EMAIL_ATTACHMENTS = 10
18
+ const MAX_TOTAL_ATTACHMENT_BYTES = 20 * 1024 * 1024
19
+
20
+ export type SenderIdentity = {
21
+ name: string | null
22
+ email: string | null
23
+ }
24
+
25
+ function logDebug(message: string, details?: Record<string, unknown>) {
26
+ if (!DEBUG) return
27
+ if (details) {
28
+ console.log(`[messages:email-sender] ${message}`, details)
29
+ return
30
+ }
31
+ console.log(`[messages:email-sender] ${message}`)
32
+ }
33
+
34
+ function resolveAppUrl(): string | null {
35
+ const raw = process.env.APP_URL?.trim()
36
+ if (!raw) return null
37
+ return raw.replace(/\/$/, '')
38
+ }
39
+
40
+ function buildSenderLabel(sender: SenderIdentity): string {
41
+ const name = sender.name?.trim()
42
+ if (name) return name
43
+ const email = sender.email?.trim()
44
+ if (email) return email
45
+ return 'System'
46
+ }
47
+
48
+ function generateAccessToken(): string {
49
+ return crypto.randomBytes(32).toString('hex')
50
+ }
51
+
52
+ function resolveObjectLabels(objects: MessageObject[]): string[] {
53
+ return objects.map((item) => `${item.entityModule}.${item.entityType} (${item.entityId})`)
54
+ }
55
+
56
+ type ResendAttachment = {
57
+ filename: string
58
+ content: string
59
+ contentType?: string
60
+ }
61
+
62
+ async function mapAttachmentsForEmail(
63
+ messageId: string,
64
+ attachments: MessageEmailAttachment[],
65
+ ): Promise<ResendAttachment[]> {
66
+ const resendAttachments: ResendAttachment[] = []
67
+ let totalBytes = 0
68
+
69
+ for (const attachment of attachments.slice(0, MAX_EMAIL_ATTACHMENTS)) {
70
+ const absolutePath = resolveAttachmentAbsolutePath(
71
+ attachment.partitionCode,
72
+ attachment.storagePath,
73
+ attachment.storageDriver,
74
+ )
75
+
76
+ let buffer: Buffer
77
+ try {
78
+ buffer = await fs.readFile(absolutePath)
79
+ } catch (error) {
80
+ logDebug('Attachment skipped: file read failed', {
81
+ messageId,
82
+ fileName: attachment.fileName,
83
+ error: error instanceof Error ? error.message : String(error),
84
+ })
85
+ continue
86
+ }
87
+
88
+ if ((totalBytes + buffer.length) > MAX_TOTAL_ATTACHMENT_BYTES) {
89
+ logDebug('Attachment skipped: total size limit exceeded', {
90
+ messageId,
91
+ fileName: attachment.fileName,
92
+ nextTotalBytes: totalBytes + buffer.length,
93
+ })
94
+ continue
95
+ }
96
+
97
+ totalBytes += buffer.length
98
+ resendAttachments.push({
99
+ filename: attachment.fileName,
100
+ content: buffer.toString('base64'),
101
+ contentType: attachment.mimeType || undefined,
102
+ })
103
+ }
104
+
105
+ return resendAttachments
106
+ }
107
+
108
+ async function renderMarkdownEmailBody(body: string) {
109
+ const ReactMarkdownModule = await import('react-markdown')
110
+ const remarkGfmModule = await import('remark-gfm')
111
+ const ReactMarkdown =
112
+ (ReactMarkdownModule.default ?? ReactMarkdownModule) as React.ComponentType<{
113
+ remarkPlugins?: unknown
114
+ children?: React.ReactNode
115
+ }>
116
+ const remarkGfmPlugin = remarkGfmModule.default ?? remarkGfmModule
117
+ const { renderToStaticMarkup } = await import('react-dom/server')
118
+
119
+ return renderToStaticMarkup(
120
+ React.createElement(ReactMarkdown, { remarkPlugins: [remarkGfmPlugin] }, body),
121
+ )
122
+ }
123
+
124
+ async function buildEmailBodyHtml(message: Message): Promise<string | undefined> {
125
+ if (message.bodyFormat !== 'markdown') return undefined
126
+ if (!message.body) return undefined
127
+ return renderMarkdownEmailBody(message.body)
128
+ }
129
+
130
+ async function buildEmailCopy(sentAt: Date) {
131
+ const dict = await loadDictionary(defaultLocale)
132
+ const t = createFallbackTranslator(dict)
133
+ return {
134
+ preview: t('messages.email.preview', 'You received a message in Open Mercato'),
135
+ heading: t('messages.email.heading', 'New message'),
136
+ from: t('messages.email.from', 'From'),
137
+ sentAt: t('messages.email.sentAt', 'Sent'),
138
+ sentAtLabel: sentAt.toISOString(),
139
+ viewCta: t('messages.email.viewCta', 'View message'),
140
+ attachmentsLabel: t('messages.email.attachments', 'Attachments'),
141
+ objectsLabel: t('messages.email.objects', 'Related records'),
142
+ footer: t('messages.email.footer', 'Open Mercato messages'),
143
+ }
144
+ }
145
+
146
+ export async function createMessageAccessToken(
147
+ em: EntityManager,
148
+ messageId: string,
149
+ recipientUserId: string,
150
+ ): Promise<string> {
151
+ const token = generateAccessToken()
152
+ const expiresAt = new Date(Date.now() + ACCESS_TOKEN_EXPIRY_HOURS * 60 * 60 * 1000)
153
+ const record = em.create(MessageAccessToken, {
154
+ messageId,
155
+ recipientUserId,
156
+ token,
157
+ expiresAt,
158
+ useCount: 0,
159
+ })
160
+ await em.persistAndFlush(record)
161
+ logDebug('Created access token', {
162
+ messageId,
163
+ recipientUserId,
164
+ expiresAt: expiresAt.toISOString(),
165
+ })
166
+ return token
167
+ }
168
+
169
+ export async function sendMessageEmailToRecipient(params: {
170
+ em: EntityManager
171
+ message: Message
172
+ recipientUserId: string
173
+ recipientEmail: string
174
+ sender: SenderIdentity
175
+ objects: MessageObject[]
176
+ attachments: MessageEmailAttachment[]
177
+ }): Promise<void> {
178
+ const { em, message, recipientUserId, recipientEmail, sender, objects, attachments } = params
179
+ const token = await createMessageAccessToken(em, message.id, recipientUserId)
180
+ const appUrl = resolveAppUrl()
181
+ const viewUrl = appUrl ? `${appUrl}/messages/view/${token}` : null
182
+ if (!appUrl) {
183
+ logDebug('APP_URL missing - email link omitted', { messageId: message.id })
184
+ }
185
+ const copy = await buildEmailCopy(message.sentAt ?? new Date())
186
+ const bodyHtml = await buildEmailBodyHtml(message)
187
+ const resendAttachments = await mapAttachmentsForEmail(message.id, attachments)
188
+ logDebug('Sending recipient email via Resend', {
189
+ messageId: message.id,
190
+ recipientUserId,
191
+ recipientEmail,
192
+ hasViewUrl: Boolean(viewUrl),
193
+ attachmentsCount: resendAttachments.length,
194
+ hasApiKey: Boolean(process.env.RESEND_API_KEY),
195
+ from: process.env.EMAIL_FROM ?? null,
196
+ })
197
+
198
+ await sendEmail({
199
+ to: recipientEmail,
200
+ subject: message.subject,
201
+ react: MessageEmail({
202
+ subject: message.subject,
203
+ body: message.body,
204
+ bodyHtml,
205
+ senderName: buildSenderLabel(sender),
206
+ sentAtLabel: copy.sentAtLabel,
207
+ viewUrl,
208
+ copy,
209
+ attachmentNames: attachments.map((item) => item.fileName),
210
+ objectLabels: resolveObjectLabels(objects),
211
+ }),
212
+ attachments: resendAttachments,
213
+ })
214
+ }
215
+
216
+ export async function sendMessageEmailToExternal(params: {
217
+ message: Message
218
+ email: string
219
+ sender: SenderIdentity
220
+ objects: MessageObject[]
221
+ attachments: MessageEmailAttachment[]
222
+ }): Promise<void> {
223
+ const { message, email, sender, objects, attachments } = params
224
+ const copy = await buildEmailCopy(message.sentAt ?? new Date())
225
+ const bodyHtml = await buildEmailBodyHtml(message)
226
+ const resendAttachments = await mapAttachmentsForEmail(message.id, attachments)
227
+ logDebug('Sending external email via Resend', {
228
+ messageId: message.id,
229
+ email,
230
+ attachmentsCount: resendAttachments.length,
231
+ hasApiKey: Boolean(process.env.RESEND_API_KEY),
232
+ from: process.env.EMAIL_FROM ?? null,
233
+ })
234
+
235
+ await sendEmail({
236
+ to: email,
237
+ subject: message.subject,
238
+ react: MessageEmail({
239
+ subject: message.subject,
240
+ body: message.body,
241
+ bodyHtml,
242
+ senderName: buildSenderLabel(sender),
243
+ sentAtLabel: copy.sentAtLabel,
244
+ viewUrl: null,
245
+ copy,
246
+ attachmentNames: attachments.map((item) => item.fileName),
247
+ objectLabels: resolveObjectLabels(objects),
248
+ }),
249
+ attachments: resendAttachments,
250
+ })
251
+ logDebug('External email sent via Resend', {
252
+ messageId: message.id,
253
+ email,
254
+ })
255
+ }