@open-mercato/core 0.4.5-develop-754ef4d2f0 → 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 (434) 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/auth/components/AclEditor.js +4 -2
  16. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  17. package/dist/modules/auth/frontend/reset.js +3 -3
  18. package/dist/modules/auth/frontend/reset.js.map +2 -2
  19. package/dist/modules/customers/backend/customers/deals/[id]/page.js +27 -8
  20. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  21. package/dist/modules/customers/lib/messageObjectPreviews.js +131 -0
  22. package/dist/modules/customers/lib/messageObjectPreviews.js.map +7 -0
  23. package/dist/modules/customers/message-objects.js +71 -0
  24. package/dist/modules/customers/message-objects.js.map +7 -0
  25. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js +51 -0
  26. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js.map +7 -0
  27. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js +35 -0
  28. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js.map +7 -0
  29. package/dist/modules/customers/widgets/messages/index.js +7 -0
  30. package/dist/modules/customers/widgets/messages/index.js.map +7 -0
  31. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +35 -6
  32. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  33. package/dist/modules/messages/acl.js +15 -0
  34. package/dist/modules/messages/acl.js.map +7 -0
  35. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +92 -0
  36. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +7 -0
  37. package/dist/modules/messages/api/[id]/archive/route.js +120 -0
  38. package/dist/modules/messages/api/[id]/archive/route.js.map +7 -0
  39. package/dist/modules/messages/api/[id]/attachments/route.js +195 -0
  40. package/dist/modules/messages/api/[id]/attachments/route.js.map +7 -0
  41. package/dist/modules/messages/api/[id]/confirmation/route.js +67 -0
  42. package/dist/modules/messages/api/[id]/confirmation/route.js.map +7 -0
  43. package/dist/modules/messages/api/[id]/conversation/archive/route.js +68 -0
  44. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +7 -0
  45. package/dist/modules/messages/api/[id]/conversation/read/route.js +68 -0
  46. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +7 -0
  47. package/dist/modules/messages/api/[id]/conversation/route.js +68 -0
  48. package/dist/modules/messages/api/[id]/conversation/route.js.map +7 -0
  49. package/dist/modules/messages/api/[id]/forward/route.js +85 -0
  50. package/dist/modules/messages/api/[id]/forward/route.js.map +7 -0
  51. package/dist/modules/messages/api/[id]/forward-preview/route.js +70 -0
  52. package/dist/modules/messages/api/[id]/forward-preview/route.js.map +7 -0
  53. package/dist/modules/messages/api/[id]/read/route.js +120 -0
  54. package/dist/modules/messages/api/[id]/read/route.js.map +7 -0
  55. package/dist/modules/messages/api/[id]/reply/route.js +87 -0
  56. package/dist/modules/messages/api/[id]/reply/route.js.map +7 -0
  57. package/dist/modules/messages/api/[id]/route.js +350 -0
  58. package/dist/modules/messages/api/[id]/route.js.map +7 -0
  59. package/dist/modules/messages/api/object-types/route.js +54 -0
  60. package/dist/modules/messages/api/object-types/route.js.map +7 -0
  61. package/dist/modules/messages/api/openapi.js +261 -0
  62. package/dist/modules/messages/api/openapi.js.map +7 -0
  63. package/dist/modules/messages/api/route.js +262 -0
  64. package/dist/modules/messages/api/route.js.map +7 -0
  65. package/dist/modules/messages/api/token/[token]/route.js +99 -0
  66. package/dist/modules/messages/api/token/[token]/route.js.map +7 -0
  67. package/dist/modules/messages/api/types/route.js +40 -0
  68. package/dist/modules/messages/api/types/route.js.map +7 -0
  69. package/dist/modules/messages/api/unread-count/route.js +43 -0
  70. package/dist/modules/messages/api/unread-count/route.js.map +7 -0
  71. package/dist/modules/messages/backend/messages/[id]/page.js +10 -0
  72. package/dist/modules/messages/backend/messages/[id]/page.js.map +7 -0
  73. package/dist/modules/messages/backend/messages/[id]/page.meta.js +16 -0
  74. package/dist/modules/messages/backend/messages/[id]/page.meta.js.map +7 -0
  75. package/dist/modules/messages/backend/messages/compose/page.js +10 -0
  76. package/dist/modules/messages/backend/messages/compose/page.js.map +7 -0
  77. package/dist/modules/messages/backend/messages/compose/page.meta.js +17 -0
  78. package/dist/modules/messages/backend/messages/compose/page.meta.js.map +7 -0
  79. package/dist/modules/messages/backend/page.js +10 -0
  80. package/dist/modules/messages/backend/page.js.map +7 -0
  81. package/dist/modules/messages/backend/page.meta.js +33 -0
  82. package/dist/modules/messages/backend/page.meta.js.map +7 -0
  83. package/dist/modules/messages/commands/actions.js +265 -0
  84. package/dist/modules/messages/commands/actions.js.map +7 -0
  85. package/dist/modules/messages/commands/attachments.js +217 -0
  86. package/dist/modules/messages/commands/attachments.js.map +7 -0
  87. package/dist/modules/messages/commands/confirmations.js +151 -0
  88. package/dist/modules/messages/commands/confirmations.js.map +7 -0
  89. package/dist/modules/messages/commands/conversation.js +240 -0
  90. package/dist/modules/messages/commands/conversation.js.map +7 -0
  91. package/dist/modules/messages/commands/messages.js +748 -0
  92. package/dist/modules/messages/commands/messages.js.map +7 -0
  93. package/dist/modules/messages/commands/recipients.js +259 -0
  94. package/dist/modules/messages/commands/recipients.js.map +7 -0
  95. package/dist/modules/messages/commands/shared.js +258 -0
  96. package/dist/modules/messages/commands/shared.js.map +7 -0
  97. package/dist/modules/messages/commands/tokens.js +69 -0
  98. package/dist/modules/messages/commands/tokens.js.map +7 -0
  99. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -0
  100. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +7 -0
  101. package/dist/modules/messages/components/MessageDetailPageClient.js +261 -0
  102. package/dist/modules/messages/components/MessageDetailPageClient.js.map +7 -0
  103. package/dist/modules/messages/components/MessagesInboxPageClient.js +390 -0
  104. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +7 -0
  105. package/dist/modules/messages/components/confirmation/MessageConfirmationActions.js +31 -0
  106. package/dist/modules/messages/components/confirmation/MessageConfirmationActions.js.map +7 -0
  107. package/dist/modules/messages/components/confirmation/MessageConfirmationContent.js +69 -0
  108. package/dist/modules/messages/components/confirmation/MessageConfirmationContent.js.map +7 -0
  109. package/dist/modules/messages/components/defaults/DefaultMessageActions.js +31 -0
  110. package/dist/modules/messages/components/defaults/DefaultMessageActions.js.map +7 -0
  111. package/dist/modules/messages/components/defaults/DefaultMessageContent.js +19 -0
  112. package/dist/modules/messages/components/defaults/DefaultMessageContent.js.map +7 -0
  113. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +90 -0
  114. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +7 -0
  115. package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js +86 -0
  116. package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js.map +7 -0
  117. package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js +61 -0
  118. package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js.map +7 -0
  119. package/dist/modules/messages/components/message-detail/detail-panels.js +27 -0
  120. package/dist/modules/messages/components/message-detail/detail-panels.js.map +7 -0
  121. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +52 -0
  122. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +7 -0
  123. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +289 -0
  124. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +7 -0
  125. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +103 -0
  126. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +7 -0
  127. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +78 -0
  128. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +7 -0
  129. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js +94 -0
  130. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js.map +7 -0
  131. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +110 -0
  132. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +7 -0
  133. package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js +58 -0
  134. package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js.map +7 -0
  135. package/dist/modules/messages/components/message-detail/panels/actions-panel.js +51 -0
  136. package/dist/modules/messages/components/message-detail/panels/actions-panel.js.map +7 -0
  137. package/dist/modules/messages/components/message-detail/panels/attachments-panel.js +66 -0
  138. package/dist/modules/messages/components/message-detail/panels/attachments-panel.js.map +7 -0
  139. package/dist/modules/messages/components/message-detail/panels/body-panel.js +20 -0
  140. package/dist/modules/messages/components/message-detail/panels/body-panel.js.map +7 -0
  141. package/dist/modules/messages/components/message-detail/panels/composer-dialogs.js +36 -0
  142. package/dist/modules/messages/components/message-detail/panels/composer-dialogs.js.map +7 -0
  143. package/dist/modules/messages/components/message-detail/panels/dialogs.js +96 -0
  144. package/dist/modules/messages/components/message-detail/panels/dialogs.js.map +7 -0
  145. package/dist/modules/messages/components/message-detail/panels/index.js +25 -0
  146. package/dist/modules/messages/components/message-detail/panels/index.js.map +7 -0
  147. package/dist/modules/messages/components/message-detail/panels/meta-panel.js +14 -0
  148. package/dist/modules/messages/components/message-detail/panels/meta-panel.js.map +7 -0
  149. package/dist/modules/messages/components/message-detail/panels/objects-panel.js +51 -0
  150. package/dist/modules/messages/components/message-detail/panels/objects-panel.js.map +7 -0
  151. package/dist/modules/messages/components/message-detail/panels/thread-panel.js +54 -0
  152. package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +7 -0
  153. package/dist/modules/messages/components/message-detail/types.js +1 -0
  154. package/dist/modules/messages/components/message-detail/types.js.map +7 -0
  155. package/dist/modules/messages/components/message-detail/utils.js +54 -0
  156. package/dist/modules/messages/components/message-detail/utils.js.map +7 -0
  157. package/dist/modules/messages/components/utils/PriorityBadge.js +52 -0
  158. package/dist/modules/messages/components/utils/PriorityBadge.js.map +7 -0
  159. package/dist/modules/messages/components/utils/typeUiRegistry.js +77 -0
  160. package/dist/modules/messages/components/utils/typeUiRegistry.js.map +7 -0
  161. package/dist/modules/messages/data/entities.js +309 -0
  162. package/dist/modules/messages/data/entities.js.map +7 -0
  163. package/dist/modules/messages/data/validators.js +272 -0
  164. package/dist/modules/messages/data/validators.js.map +7 -0
  165. package/dist/modules/messages/emails/MessageEmail.js +108 -0
  166. package/dist/modules/messages/emails/MessageEmail.js.map +7 -0
  167. package/dist/modules/messages/events.js +24 -0
  168. package/dist/modules/messages/events.js.map +7 -0
  169. package/dist/modules/messages/frontend/messages/view/[token]/page.js +247 -0
  170. package/dist/modules/messages/frontend/messages/view/[token]/page.js.map +7 -0
  171. package/dist/modules/messages/frontend/messages/view/[token]/page.meta.js +9 -0
  172. package/dist/modules/messages/frontend/messages/view/[token]/page.meta.js.map +7 -0
  173. package/dist/modules/messages/index.js +21 -0
  174. package/dist/modules/messages/index.js.map +7 -0
  175. package/dist/modules/messages/lib/actions.js +141 -0
  176. package/dist/modules/messages/lib/actions.js.map +7 -0
  177. package/dist/modules/messages/lib/attachments.js +131 -0
  178. package/dist/modules/messages/lib/attachments.js.map +7 -0
  179. package/dist/modules/messages/lib/constants.js +7 -0
  180. package/dist/modules/messages/lib/constants.js.map +7 -0
  181. package/dist/modules/messages/lib/email-sender.js +201 -0
  182. package/dist/modules/messages/lib/email-sender.js.map +7 -0
  183. package/dist/modules/messages/lib/forwarding.js +179 -0
  184. package/dist/modules/messages/lib/forwarding.js.map +7 -0
  185. package/dist/modules/messages/lib/message-objects-registry.js +49 -0
  186. package/dist/modules/messages/lib/message-objects-registry.js.map +7 -0
  187. package/dist/modules/messages/lib/message-types-registry.js +41 -0
  188. package/dist/modules/messages/lib/message-types-registry.js.map +7 -0
  189. package/dist/modules/messages/lib/object-validation.js +20 -0
  190. package/dist/modules/messages/lib/object-validation.js.map +7 -0
  191. package/dist/modules/messages/lib/operationMetadata.js +21 -0
  192. package/dist/modules/messages/lib/operationMetadata.js.map +7 -0
  193. package/dist/modules/messages/lib/priorityUtils.js +61 -0
  194. package/dist/modules/messages/lib/priorityUtils.js.map +7 -0
  195. package/dist/modules/messages/lib/routeHelpers.js +44 -0
  196. package/dist/modules/messages/lib/routeHelpers.js.map +7 -0
  197. package/dist/modules/messages/message-objects.js +7 -0
  198. package/dist/modules/messages/message-objects.js.map +7 -0
  199. package/dist/modules/messages/message-types.js +67 -0
  200. package/dist/modules/messages/message-types.js.map +7 -0
  201. package/dist/modules/messages/migrations/Migration20260213181243.js +31 -0
  202. package/dist/modules/messages/migrations/Migration20260213181243.js.map +7 -0
  203. package/dist/modules/messages/migrations/Migration20260215165126.js +16 -0
  204. package/dist/modules/messages/migrations/Migration20260215165126.js.map +7 -0
  205. package/dist/modules/messages/notifications.js +27 -0
  206. package/dist/modules/messages/notifications.js.map +7 -0
  207. package/dist/modules/messages/setup.js +21 -0
  208. package/dist/modules/messages/setup.js.map +7 -0
  209. package/dist/modules/messages/subscribers/message-notification.js +108 -0
  210. package/dist/modules/messages/subscribers/message-notification.js.map +7 -0
  211. package/dist/modules/messages/workers/send-email.worker.js +253 -0
  212. package/dist/modules/messages/workers/send-email.worker.js.map +7 -0
  213. package/dist/modules/sales/backend/sales/documents/[id]/page.js +30 -11
  214. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  215. package/dist/modules/sales/commands/payments.js +12 -6
  216. package/dist/modules/sales/commands/payments.js.map +2 -2
  217. package/dist/modules/sales/lib/messageObjectPreviews.js +114 -0
  218. package/dist/modules/sales/lib/messageObjectPreviews.js.map +7 -0
  219. package/dist/modules/sales/message-objects.js +57 -0
  220. package/dist/modules/sales/message-objects.js.map +7 -0
  221. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js +51 -0
  222. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js.map +7 -0
  223. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js +36 -0
  224. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js.map +7 -0
  225. package/dist/modules/sales/widgets/messages/index.js +7 -0
  226. package/dist/modules/sales/widgets/messages/index.js.map +7 -0
  227. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +55 -1
  228. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  229. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +60 -1
  230. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  231. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +2 -19
  232. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  233. package/dist/modules/staff/components/LeaveRequestDetail.js +112 -0
  234. package/dist/modules/staff/components/LeaveRequestDetail.js.map +7 -0
  235. package/dist/modules/staff/components/LeaveRequestForm.js +3 -1
  236. package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
  237. package/dist/modules/staff/components/LeaveRequestPreview.js +43 -0
  238. package/dist/modules/staff/components/LeaveRequestPreview.js.map +7 -0
  239. package/dist/modules/staff/lib/messageObjectPreviews.js +148 -0
  240. package/dist/modules/staff/lib/messageObjectPreviews.js.map +7 -0
  241. package/dist/modules/staff/message-objects.js +104 -0
  242. package/dist/modules/staff/message-objects.js.map +7 -0
  243. package/dist/modules/staff/message-types.js +23 -0
  244. package/dist/modules/staff/message-types.js.map +7 -0
  245. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js +51 -0
  246. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js.map +7 -0
  247. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js +34 -0
  248. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js.map +7 -0
  249. package/dist/modules/staff/widgets/messages/index.js +7 -0
  250. package/dist/modules/staff/widgets/messages/index.js.map +7 -0
  251. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -6
  252. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  253. package/generated/entities/message/index.ts +31 -0
  254. package/generated/entities/message_access_token/index.ts +8 -0
  255. package/generated/entities/message_confirmation/index.ts +9 -0
  256. package/generated/entities/message_object/index.ts +10 -0
  257. package/generated/entities/message_recipient/index.ts +14 -0
  258. package/generated/entities.ids.generated.ts +8 -0
  259. package/generated/entity-fields-registry.ts +10 -0
  260. package/jest.setup.ts +5 -0
  261. package/package.json +2 -2
  262. package/src/modules/attachments/i18n/de.json +4 -0
  263. package/src/modules/attachments/i18n/en.json +4 -0
  264. package/src/modules/attachments/i18n/es.json +4 -0
  265. package/src/modules/attachments/i18n/pl.json +4 -0
  266. package/src/modules/auth/components/AclEditor.tsx +4 -2
  267. package/src/modules/auth/frontend/reset.tsx +3 -3
  268. package/src/modules/auth/i18n/de.json +5 -0
  269. package/src/modules/auth/i18n/en.json +5 -0
  270. package/src/modules/auth/i18n/es.json +5 -0
  271. package/src/modules/auth/i18n/pl.json +5 -0
  272. package/src/modules/catalog/i18n/de.json +21 -0
  273. package/src/modules/catalog/i18n/en.json +21 -0
  274. package/src/modules/catalog/i18n/es.json +21 -0
  275. package/src/modules/catalog/i18n/pl.json +21 -0
  276. package/src/modules/currencies/i18n/de.json +6 -0
  277. package/src/modules/currencies/i18n/en.json +6 -0
  278. package/src/modules/currencies/i18n/es.json +6 -0
  279. package/src/modules/currencies/i18n/pl.json +6 -0
  280. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +20 -4
  281. package/src/modules/customers/i18n/de.json +26 -1
  282. package/src/modules/customers/i18n/en.json +25 -0
  283. package/src/modules/customers/i18n/es.json +25 -0
  284. package/src/modules/customers/i18n/pl.json +25 -0
  285. package/src/modules/customers/lib/messageObjectPreviews.ts +154 -0
  286. package/src/modules/customers/message-objects.ts +70 -0
  287. package/src/modules/customers/widgets/messages/CustomerMessageObjectDetail.tsx +57 -0
  288. package/src/modules/customers/widgets/messages/CustomerMessageObjectPreview.tsx +49 -0
  289. package/src/modules/customers/widgets/messages/index.ts +2 -0
  290. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +41 -5
  291. package/src/modules/dashboards/i18n/de.json +5 -1
  292. package/src/modules/dashboards/i18n/en.json +5 -1
  293. package/src/modules/dashboards/i18n/es.json +5 -1
  294. package/src/modules/dashboards/i18n/pl.json +5 -1
  295. package/src/modules/dictionaries/i18n/de.json +14 -1
  296. package/src/modules/dictionaries/i18n/en.json +14 -1
  297. package/src/modules/dictionaries/i18n/es.json +14 -1
  298. package/src/modules/dictionaries/i18n/pl.json +14 -1
  299. package/src/modules/feature_toggles/i18n/de.json +3 -0
  300. package/src/modules/feature_toggles/i18n/en.json +3 -0
  301. package/src/modules/feature_toggles/i18n/es.json +3 -0
  302. package/src/modules/feature_toggles/i18n/pl.json +3 -0
  303. package/src/modules/messages/acl.ts +11 -0
  304. package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +103 -0
  305. package/src/modules/messages/api/[id]/archive/route.ts +138 -0
  306. package/src/modules/messages/api/[id]/attachments/route.ts +217 -0
  307. package/src/modules/messages/api/[id]/confirmation/route.ts +73 -0
  308. package/src/modules/messages/api/[id]/conversation/archive/route.ts +69 -0
  309. package/src/modules/messages/api/[id]/conversation/read/route.ts +69 -0
  310. package/src/modules/messages/api/[id]/conversation/route.ts +69 -0
  311. package/src/modules/messages/api/[id]/forward/route.ts +87 -0
  312. package/src/modules/messages/api/[id]/forward-preview/route.ts +75 -0
  313. package/src/modules/messages/api/[id]/read/route.ts +138 -0
  314. package/src/modules/messages/api/[id]/reply/route.ts +89 -0
  315. package/src/modules/messages/api/[id]/route.ts +401 -0
  316. package/src/modules/messages/api/object-types/route.ts +54 -0
  317. package/src/modules/messages/api/openapi.ts +261 -0
  318. package/src/modules/messages/api/route.ts +374 -0
  319. package/src/modules/messages/api/token/[token]/route.ts +103 -0
  320. package/src/modules/messages/api/types/route.ts +39 -0
  321. package/src/modules/messages/api/unread-count/route.ts +55 -0
  322. package/src/modules/messages/backend/messages/[id]/page.meta.ts +12 -0
  323. package/src/modules/messages/backend/messages/[id]/page.tsx +12 -0
  324. package/src/modules/messages/backend/messages/compose/page.meta.ts +13 -0
  325. package/src/modules/messages/backend/messages/compose/page.tsx +12 -0
  326. package/src/modules/messages/backend/page.meta.ts +31 -0
  327. package/src/modules/messages/backend/page.tsx +12 -0
  328. package/src/modules/messages/commands/actions.ts +307 -0
  329. package/src/modules/messages/commands/attachments.ts +227 -0
  330. package/src/modules/messages/commands/confirmations.ts +183 -0
  331. package/src/modules/messages/commands/conversation.ts +292 -0
  332. package/src/modules/messages/commands/messages.ts +845 -0
  333. package/src/modules/messages/commands/recipients.ts +281 -0
  334. package/src/modules/messages/commands/shared.ts +350 -0
  335. package/src/modules/messages/commands/tokens.ts +80 -0
  336. package/src/modules/messages/components/ComposeMessagePageClient.tsx +23 -0
  337. package/src/modules/messages/components/MessageDetailPageClient.tsx +287 -0
  338. package/src/modules/messages/components/MessagesInboxPageClient.tsx +469 -0
  339. package/src/modules/messages/components/confirmation/MessageConfirmationActions.tsx +35 -0
  340. package/src/modules/messages/components/confirmation/MessageConfirmationContent.tsx +88 -0
  341. package/src/modules/messages/components/defaults/DefaultMessageActions.tsx +37 -0
  342. package/src/modules/messages/components/defaults/DefaultMessageContent.tsx +21 -0
  343. package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +102 -0
  344. package/src/modules/messages/components/defaults/MessageRecordObjectDetail.tsx +114 -0
  345. package/src/modules/messages/components/defaults/MessageRecordObjectPreview.tsx +74 -0
  346. package/src/modules/messages/components/message-detail/detail-panels.ts +13 -0
  347. package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +56 -0
  348. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +367 -0
  349. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +134 -0
  350. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +102 -0
  351. package/src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx +108 -0
  352. package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +144 -0
  353. package/src/modules/messages/components/message-detail/panels/MessageListComponent.tsx +63 -0
  354. package/src/modules/messages/components/message-detail/panels/actions-panel.tsx +66 -0
  355. package/src/modules/messages/components/message-detail/panels/attachments-panel.tsx +86 -0
  356. package/src/modules/messages/components/message-detail/panels/body-panel.tsx +32 -0
  357. package/src/modules/messages/components/message-detail/panels/composer-dialogs.tsx +42 -0
  358. package/src/modules/messages/components/message-detail/panels/dialogs.tsx +107 -0
  359. package/src/modules/messages/components/message-detail/panels/index.ts +11 -0
  360. package/src/modules/messages/components/message-detail/panels/meta-panel.tsx +19 -0
  361. package/src/modules/messages/components/message-detail/panels/objects-panel.tsx +65 -0
  362. package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +65 -0
  363. package/src/modules/messages/components/message-detail/types.ts +114 -0
  364. package/src/modules/messages/components/message-detail/utils.ts +62 -0
  365. package/src/modules/messages/components/utils/PriorityBadge.tsx +63 -0
  366. package/src/modules/messages/components/utils/typeUiRegistry.ts +106 -0
  367. package/src/modules/messages/data/entities.ts +284 -0
  368. package/src/modules/messages/data/validators.ts +297 -0
  369. package/src/modules/messages/emails/MessageEmail.tsx +143 -0
  370. package/src/modules/messages/events.ts +24 -0
  371. package/src/modules/messages/frontend/messages/view/[token]/page.meta.ts +5 -0
  372. package/src/modules/messages/frontend/messages/view/[token]/page.tsx +389 -0
  373. package/src/modules/messages/i18n/de.json +240 -0
  374. package/src/modules/messages/i18n/en.json +240 -0
  375. package/src/modules/messages/i18n/es.json +240 -0
  376. package/src/modules/messages/i18n/pl.json +240 -0
  377. package/src/modules/messages/index.ts +19 -0
  378. package/src/modules/messages/lib/actions.ts +204 -0
  379. package/src/modules/messages/lib/attachments.ts +197 -0
  380. package/src/modules/messages/lib/constants.ts +2 -0
  381. package/src/modules/messages/lib/email-sender.ts +255 -0
  382. package/src/modules/messages/lib/forwarding.ts +240 -0
  383. package/src/modules/messages/lib/message-objects-registry.ts +60 -0
  384. package/src/modules/messages/lib/message-types-registry.ts +48 -0
  385. package/src/modules/messages/lib/object-validation.ts +26 -0
  386. package/src/modules/messages/lib/operationMetadata.ts +43 -0
  387. package/src/modules/messages/lib/priorityUtils.ts +76 -0
  388. package/src/modules/messages/lib/routeHelpers.ts +65 -0
  389. package/src/modules/messages/message-objects.ts +5 -0
  390. package/src/modules/messages/message-types.ts +65 -0
  391. package/src/modules/messages/migrations/.snapshot-open-mercato.json +957 -0
  392. package/src/modules/messages/migrations/Migration20260213181243.ts +34 -0
  393. package/src/modules/messages/migrations/Migration20260215165126.ts +16 -0
  394. package/src/modules/messages/notifications.ts +25 -0
  395. package/src/modules/messages/setup.ts +19 -0
  396. package/src/modules/messages/subscribers/message-notification.ts +138 -0
  397. package/src/modules/messages/workers/send-email.worker.ts +321 -0
  398. package/src/modules/query_index/i18n/es.json +2 -2
  399. package/src/modules/resources/i18n/de.json +57 -0
  400. package/src/modules/resources/i18n/en.json +57 -0
  401. package/src/modules/resources/i18n/es.json +57 -0
  402. package/src/modules/resources/i18n/pl.json +57 -0
  403. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +23 -7
  404. package/src/modules/sales/commands/payments.ts +12 -6
  405. package/src/modules/sales/i18n/de.json +26 -0
  406. package/src/modules/sales/i18n/en.json +26 -0
  407. package/src/modules/sales/i18n/es.json +26 -0
  408. package/src/modules/sales/i18n/pl.json +26 -0
  409. package/src/modules/sales/lib/messageObjectPreviews.ts +150 -0
  410. package/src/modules/sales/message-objects.ts +56 -0
  411. package/src/modules/sales/widgets/messages/SalesDocumentMessageDetail.tsx +57 -0
  412. package/src/modules/sales/widgets/messages/SalesDocumentMessagePreview.tsx +46 -0
  413. package/src/modules/sales/widgets/messages/index.ts +2 -0
  414. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +54 -0
  415. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +58 -0
  416. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +2 -32
  417. package/src/modules/staff/components/LeaveRequestDetail.tsx +135 -0
  418. package/src/modules/staff/components/LeaveRequestForm.tsx +3 -0
  419. package/src/modules/staff/components/LeaveRequestPreview.tsx +74 -0
  420. package/src/modules/staff/i18n/de.json +22 -0
  421. package/src/modules/staff/i18n/en.json +22 -0
  422. package/src/modules/staff/i18n/es.json +22 -0
  423. package/src/modules/staff/i18n/pl.json +22 -0
  424. package/src/modules/staff/lib/messageObjectPreviews.ts +182 -0
  425. package/src/modules/staff/message-objects.ts +102 -0
  426. package/src/modules/staff/message-types.ts +21 -0
  427. package/src/modules/staff/widgets/messages/StaffMessageObjectDetail.tsx +57 -0
  428. package/src/modules/staff/widgets/messages/StaffMessageObjectPreview.tsx +44 -0
  429. package/src/modules/staff/widgets/messages/index.ts +2 -0
  430. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +6 -6
  431. package/src/modules/workflows/i18n/de.json +41 -0
  432. package/src/modules/workflows/i18n/en.json +41 -0
  433. package/src/modules/workflows/i18n/es.json +41 -0
  434. package/src/modules/workflows/i18n/pl.json +41 -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
+ }