@open-mercato/core 0.4.5-develop-5191db4ef3 → 0.4.5-develop-033a719bf2

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,227 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { z } from 'zod'
3
+ import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
4
+ import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
+ import { Message } from '../data/entities'
6
+ import { attachmentIdsPayloadSchema, unlinkAttachmentPayloadSchema } from '../data/validators'
7
+ import { linkAttachmentsToMessage } from '../lib/attachments'
8
+ import { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'
9
+ import { assertOrganizationAccess, getAttachmentIdsForMessage, type MessageScopeInput } from './shared'
10
+
11
+ const attachmentMutationScopeSchema = z.object({
12
+ messageId: z.string().uuid(),
13
+ tenantId: z.string().uuid(),
14
+ organizationId: z.string().uuid().nullable(),
15
+ userId: z.string().uuid(),
16
+ })
17
+
18
+ const linkDraftAttachmentsSchema = attachmentMutationScopeSchema.extend({
19
+ attachmentIds: attachmentIdsPayloadSchema.shape.attachmentIds,
20
+ })
21
+
22
+ const unlinkDraftAttachmentsSchema = attachmentMutationScopeSchema.extend({
23
+ attachmentIds: z.array(z.string().uuid()).min(1).max(100),
24
+ })
25
+
26
+ type AttachmentState = { attachmentIds: string[] }
27
+
28
+ type LinkDraftAttachmentsInput = z.infer<typeof linkDraftAttachmentsSchema>
29
+ type UnlinkDraftAttachmentsInput = z.infer<typeof unlinkDraftAttachmentsSchema>
30
+
31
+ async function requireEditableDraftMessage(em: EntityManager, scope: MessageScopeInput, messageId: string) {
32
+ const message = await em.findOne(Message, {
33
+ id: messageId,
34
+ tenantId: scope.tenantId,
35
+ deletedAt: null,
36
+ })
37
+ if (!message) throw new Error('Message not found')
38
+ assertOrganizationAccess(scope, message)
39
+ if (message.senderUserId !== scope.userId) throw new Error('Access denied')
40
+ if (!message.isDraft) throw new Error('Attachments can only be modified on drafts')
41
+ return message
42
+ }
43
+
44
+ const linkDraftAttachmentsCommand: CommandHandler<unknown, { ok: true }> = {
45
+ id: 'messages.attachments.link_to_draft',
46
+ async prepare(rawInput, ctx) {
47
+ const input = linkDraftAttachmentsSchema.parse(rawInput)
48
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
49
+ const message = await requireEditableDraftMessage(em, input, input.messageId)
50
+ return {
51
+ before: {
52
+ attachmentIds: await getAttachmentIdsForMessage(em, message.id, {
53
+ tenantId: input.tenantId,
54
+ organizationId: input.organizationId,
55
+ }),
56
+ } satisfies AttachmentState,
57
+ }
58
+ },
59
+ async execute(rawInput, ctx) {
60
+ const input = linkDraftAttachmentsSchema.parse(rawInput)
61
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
62
+ await requireEditableDraftMessage(em, input, input.messageId)
63
+ await linkAttachmentsToMessage(
64
+ em,
65
+ input.messageId,
66
+ input.attachmentIds,
67
+ input.organizationId,
68
+ input.tenantId,
69
+ )
70
+ return { ok: true }
71
+ },
72
+ async captureAfter(rawInput, _result, ctx) {
73
+ const input = linkDraftAttachmentsSchema.parse(rawInput)
74
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
75
+ return {
76
+ attachmentIds: await getAttachmentIdsForMessage(em, input.messageId, {
77
+ tenantId: input.tenantId,
78
+ organizationId: input.organizationId,
79
+ }),
80
+ } satisfies AttachmentState
81
+ },
82
+ buildLog: async ({ input, snapshots }) => {
83
+ const parsed = linkDraftAttachmentsSchema.parse(input)
84
+ return {
85
+ actionLabel: 'Link message draft attachments',
86
+ resourceKind: 'messages.message',
87
+ resourceId: parsed.messageId,
88
+ tenantId: parsed.tenantId,
89
+ organizationId: parsed.organizationId,
90
+ payload: {
91
+ undo: {
92
+ before: (snapshots.before as AttachmentState | undefined) ?? null,
93
+ after: (snapshots.after as AttachmentState | undefined) ?? null,
94
+ },
95
+ },
96
+ snapshotBefore: snapshots.before ?? null,
97
+ snapshotAfter: snapshots.after ?? null,
98
+ }
99
+ },
100
+ undo: async ({ logEntry, ctx }) => {
101
+ const undo = extractUndoPayload<{ before?: AttachmentState | null }>(logEntry)
102
+ const before = undo?.before
103
+ if (!before) return
104
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
105
+ const messageId = logEntry?.resourceId as string | null
106
+ const tenantId = logEntry?.tenantId as string | null
107
+ const organizationId = (logEntry?.organizationId as string | null) ?? null
108
+ if (!messageId || !tenantId) return
109
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
110
+ await em.nativeDelete(Attachment, {
111
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
112
+ recordId: messageId,
113
+ tenantId,
114
+ organizationId,
115
+ id: { $nin: before.attachmentIds.length ? before.attachmentIds : ['00000000-0000-0000-0000-000000000000'] },
116
+ })
117
+ if (before.attachmentIds.length > 0) {
118
+ const attachments = await em.find(Attachment, {
119
+ id: { $in: before.attachmentIds },
120
+ tenantId,
121
+ organizationId,
122
+ })
123
+ for (const attachment of attachments) {
124
+ attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID
125
+ attachment.recordId = messageId
126
+ }
127
+ }
128
+ await em.flush()
129
+ },
130
+ }
131
+
132
+ const unlinkDraftAttachmentsCommand: CommandHandler<unknown, { ok: true }> = {
133
+ id: 'messages.attachments.unlink_from_draft',
134
+ async prepare(rawInput, ctx) {
135
+ const input = unlinkDraftAttachmentsSchema.parse(rawInput)
136
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
137
+ const message = await requireEditableDraftMessage(em, input, input.messageId)
138
+ return {
139
+ before: {
140
+ attachmentIds: await getAttachmentIdsForMessage(em, message.id, {
141
+ tenantId: input.tenantId,
142
+ organizationId: input.organizationId,
143
+ }),
144
+ } satisfies AttachmentState,
145
+ }
146
+ },
147
+ async execute(rawInput, ctx) {
148
+ const input = unlinkDraftAttachmentsSchema.parse(rawInput)
149
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
150
+ await requireEditableDraftMessage(em, input, input.messageId)
151
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
152
+ await em.nativeDelete(Attachment, {
153
+ id: { $in: input.attachmentIds },
154
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
155
+ recordId: input.messageId,
156
+ tenantId: input.tenantId,
157
+ organizationId: input.organizationId,
158
+ })
159
+ return { ok: true }
160
+ },
161
+ async captureAfter(rawInput, _result, ctx) {
162
+ const input = unlinkDraftAttachmentsSchema.parse(rawInput)
163
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
164
+ return {
165
+ attachmentIds: await getAttachmentIdsForMessage(em, input.messageId, {
166
+ tenantId: input.tenantId,
167
+ organizationId: input.organizationId,
168
+ }),
169
+ } satisfies AttachmentState
170
+ },
171
+ buildLog: async ({ input, snapshots }) => {
172
+ const parsed = unlinkDraftAttachmentsSchema.parse(input)
173
+ return {
174
+ actionLabel: 'Unlink message draft attachments',
175
+ resourceKind: 'messages.message',
176
+ resourceId: parsed.messageId,
177
+ tenantId: parsed.tenantId,
178
+ organizationId: parsed.organizationId,
179
+ payload: {
180
+ undo: {
181
+ before: (snapshots.before as AttachmentState | undefined) ?? null,
182
+ after: (snapshots.after as AttachmentState | undefined) ?? null,
183
+ },
184
+ },
185
+ snapshotBefore: snapshots.before ?? null,
186
+ snapshotAfter: snapshots.after ?? null,
187
+ }
188
+ },
189
+ undo: async ({ logEntry, ctx }) => {
190
+ const undo = extractUndoPayload<{ before?: AttachmentState | null }>(logEntry)
191
+ const before = undo?.before
192
+ if (!before) return
193
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
194
+ const messageId = logEntry?.resourceId as string | null
195
+ const tenantId = logEntry?.tenantId as string | null
196
+ const organizationId = (logEntry?.organizationId as string | null) ?? null
197
+ if (!messageId || !tenantId) return
198
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
199
+ await em.nativeDelete(Attachment, {
200
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
201
+ recordId: messageId,
202
+ tenantId,
203
+ organizationId,
204
+ id: { $nin: before.attachmentIds.length ? before.attachmentIds : ['00000000-0000-0000-0000-000000000000'] },
205
+ })
206
+ if (before.attachmentIds.length > 0) {
207
+ const attachments = await em.find(Attachment, {
208
+ id: { $in: before.attachmentIds },
209
+ tenantId,
210
+ organizationId,
211
+ })
212
+ for (const attachment of attachments) {
213
+ attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID
214
+ attachment.recordId = messageId
215
+ }
216
+ }
217
+ await em.flush()
218
+ },
219
+ }
220
+
221
+ export function parseUnlinkAttachmentIds(rawInput: unknown): string[] {
222
+ const input = unlinkAttachmentPayloadSchema.parse(rawInput)
223
+ return input.attachmentIds ?? (input.attachmentId ? [input.attachmentId] : [])
224
+ }
225
+
226
+ registerCommand(linkDraftAttachmentsCommand)
227
+ registerCommand(unlinkDraftAttachmentsCommand)
@@ -0,0 +1,183 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
3
+ import type { CommandHandler } from '@open-mercato/shared/lib/commands'
4
+ import { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
+ import { Message, MessageConfirmation, MessageRecipient } from '../data/entities'
6
+ import { confirmMessageSchema } from '../data/validators'
7
+
8
+ type ConfirmMessageResult = {
9
+ messageId: string
10
+ confirmed: boolean
11
+ confirmedAt: string | null
12
+ confirmedByUserId: string | null
13
+ }
14
+
15
+ type ConfirmationSnapshot = {
16
+ id: string | null
17
+ messageId: string
18
+ confirmed: boolean
19
+ confirmedAt: string | null
20
+ confirmedByUserId: string | null
21
+ tenantId: string
22
+ organizationId: string | null
23
+ }
24
+
25
+ function toIso(value: Date | null | undefined): string | null {
26
+ return value ? value.toISOString() : null
27
+ }
28
+
29
+ function toDate(value: string | null | undefined): Date | null {
30
+ if (!value) return null
31
+ return new Date(value)
32
+ }
33
+
34
+ const confirmMessageCommand: CommandHandler<unknown, ConfirmMessageResult> = {
35
+ id: 'messages.confirmations.confirm',
36
+ async prepare(rawInput, ctx) {
37
+ const input = confirmMessageSchema.parse(rawInput)
38
+ const tenantId = input.tenantId ?? ctx.auth?.tenantId ?? null
39
+ if (!tenantId) throw new Error('Tenant scope is required')
40
+ const organizationId = input.organizationId ?? ctx.selectedOrganizationId ?? null
41
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
42
+ const existing = await em.findOne(MessageConfirmation, { messageId: input.messageId })
43
+ return {
44
+ before: {
45
+ id: existing?.id ?? null,
46
+ messageId: input.messageId,
47
+ confirmed: existing?.confirmed ?? false,
48
+ confirmedAt: toIso(existing?.confirmedAt),
49
+ confirmedByUserId: existing?.confirmedByUserId ?? null,
50
+ tenantId,
51
+ organizationId,
52
+ } satisfies ConfirmationSnapshot,
53
+ }
54
+ },
55
+ async execute(rawInput, ctx) {
56
+ const input = confirmMessageSchema.parse(rawInput)
57
+ const tenantId = input.tenantId ?? ctx.auth?.tenantId ?? null
58
+
59
+ if (!tenantId) {
60
+ throw new Error('Tenant scope is required')
61
+ }
62
+
63
+ const organizationId = input.organizationId ?? ctx.selectedOrganizationId ?? null
64
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
65
+
66
+ const message = await em.findOne(Message, {
67
+ id: input.messageId,
68
+ tenantId,
69
+ organizationId,
70
+ deletedAt: null,
71
+ })
72
+
73
+ if (!message) {
74
+ throw new Error('Message not found')
75
+ }
76
+
77
+ const actorUserId = ctx.auth?.sub ?? null
78
+ if (!actorUserId) {
79
+ throw new Error('Authentication required')
80
+ }
81
+
82
+ const recipient = await em.findOne(MessageRecipient, {
83
+ messageId: message.id,
84
+ recipientUserId: actorUserId,
85
+ deletedAt: null,
86
+ })
87
+ const isSender = message.senderUserId === actorUserId
88
+ if (!isSender && !recipient) {
89
+ throw new Error('Access denied')
90
+ }
91
+
92
+ let confirmation = await em.findOne(MessageConfirmation, { messageId: message.id })
93
+ if (!confirmation) {
94
+ confirmation = em.create(MessageConfirmation, {
95
+ messageId: message.id,
96
+ tenantId,
97
+ organizationId,
98
+ })
99
+ }
100
+
101
+ confirmation.confirmed = input.confirmed
102
+ confirmation.confirmedByUserId = actorUserId
103
+ confirmation.confirmedAt = input.confirmed ? new Date() : null
104
+
105
+ await em.persistAndFlush(confirmation)
106
+
107
+ return {
108
+ messageId: confirmation.messageId,
109
+ confirmed: confirmation.confirmed,
110
+ confirmedAt: confirmation.confirmedAt ? confirmation.confirmedAt.toISOString() : null,
111
+ confirmedByUserId: confirmation.confirmedByUserId ?? null,
112
+ }
113
+ },
114
+ async captureAfter(rawInput, _result, ctx) {
115
+ const input = confirmMessageSchema.parse(rawInput)
116
+ const tenantId = input.tenantId ?? ctx.auth?.tenantId ?? null
117
+ if (!tenantId) return null
118
+ const organizationId = input.organizationId ?? ctx.selectedOrganizationId ?? null
119
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
120
+ const confirmation = await em.findOne(MessageConfirmation, { messageId: input.messageId })
121
+ return {
122
+ id: confirmation?.id ?? null,
123
+ messageId: input.messageId,
124
+ confirmed: confirmation?.confirmed ?? false,
125
+ confirmedAt: toIso(confirmation?.confirmedAt),
126
+ confirmedByUserId: confirmation?.confirmedByUserId ?? null,
127
+ tenantId,
128
+ organizationId,
129
+ } satisfies ConfirmationSnapshot
130
+ },
131
+ async buildLog({ input, snapshots }) {
132
+ const parsed = confirmMessageSchema.parse(input)
133
+ return {
134
+ actionLabel: parsed.confirmed ? 'Confirm message' : 'Unconfirm message',
135
+ resourceKind: 'messages.message',
136
+ resourceId: parsed.messageId,
137
+ tenantId: parsed.tenantId ?? null,
138
+ organizationId: parsed.organizationId ?? null,
139
+ payload: {
140
+ undo: {
141
+ before: (snapshots.before as ConfirmationSnapshot | undefined) ?? null,
142
+ after: (snapshots.after as ConfirmationSnapshot | undefined) ?? null,
143
+ } satisfies UndoPayload<ConfirmationSnapshot>,
144
+ },
145
+ snapshotBefore: snapshots.before ?? null,
146
+ snapshotAfter: snapshots.after ?? null,
147
+ }
148
+ },
149
+ undo: async ({ logEntry, ctx }) => {
150
+ const undo = extractUndoPayload<UndoPayload<ConfirmationSnapshot>>(logEntry)
151
+ const before = undo?.before
152
+ if (!before) return
153
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
154
+ const existing = await em.findOne(MessageConfirmation, { messageId: before.messageId })
155
+ if (!before.id) {
156
+ if (existing) {
157
+ em.remove(existing)
158
+ await em.flush()
159
+ }
160
+ return
161
+ }
162
+ if (!existing) {
163
+ em.persist(em.create(MessageConfirmation, {
164
+ id: before.id,
165
+ messageId: before.messageId,
166
+ confirmed: before.confirmed,
167
+ confirmedAt: toDate(before.confirmedAt),
168
+ confirmedByUserId: before.confirmedByUserId,
169
+ tenantId: before.tenantId,
170
+ organizationId: before.organizationId,
171
+ }))
172
+ } else {
173
+ existing.confirmed = before.confirmed
174
+ existing.confirmedAt = toDate(before.confirmedAt)
175
+ existing.confirmedByUserId = before.confirmedByUserId
176
+ existing.tenantId = before.tenantId
177
+ existing.organizationId = before.organizationId
178
+ }
179
+ await em.flush()
180
+ },
181
+ }
182
+
183
+ registerCommand(confirmMessageCommand)
@@ -0,0 +1,292 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { z } from 'zod'
3
+ import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
4
+ import { Message, MessageRecipient } from '../data/entities'
5
+ import { emitMessagesEvent } from '../events'
6
+ import { assertOrganizationAccess, type MessageScopeInput } from './shared'
7
+
8
+ const conversationCommandSchema = z.object({
9
+ anchorMessageId: z.string().uuid(),
10
+ tenantId: z.string().uuid(),
11
+ organizationId: z.string().uuid().nullable(),
12
+ userId: z.string().uuid(),
13
+ })
14
+
15
+ type ConversationCommandInput = z.infer<typeof conversationCommandSchema>
16
+
17
+ type ConversationScope = {
18
+ anchorMessageId: string
19
+ messageIds: string[]
20
+ recipientMessageIds: Set<string>
21
+ senderMessageIds: Set<string>
22
+ }
23
+
24
+ async function resolveConversationScope(
25
+ em: EntityManager,
26
+ input: ConversationCommandInput,
27
+ ): Promise<ConversationScope> {
28
+ const anchorMessage = await em.findOne(Message, {
29
+ id: input.anchorMessageId,
30
+ tenantId: input.tenantId,
31
+ deletedAt: null,
32
+ })
33
+
34
+ if (!anchorMessage) throw new Error('Message not found')
35
+ assertOrganizationAccess(input as MessageScopeInput, anchorMessage)
36
+
37
+ const anchorRecipient = await em.findOne(MessageRecipient, {
38
+ messageId: input.anchorMessageId,
39
+ recipientUserId: input.userId,
40
+ deletedAt: null,
41
+ })
42
+
43
+ const canAccessAnchor = anchorMessage.senderUserId === input.userId || Boolean(anchorRecipient)
44
+ if (!canAccessAnchor) throw new Error('Access denied')
45
+
46
+ const threadId = anchorMessage.threadId ?? anchorMessage.id
47
+ const conversationMessages = await em.find(
48
+ Message,
49
+ {
50
+ threadId,
51
+ tenantId: input.tenantId,
52
+ organizationId: input.organizationId,
53
+ deletedAt: null,
54
+ isDraft: false,
55
+ },
56
+ {
57
+ orderBy: {
58
+ sentAt: 'ASC',
59
+ createdAt: 'ASC',
60
+ id: 'ASC',
61
+ },
62
+ },
63
+ )
64
+
65
+ const messagesById = new Map<string, Message>(conversationMessages.map((message) => [message.id, message]))
66
+ messagesById.set(anchorMessage.id, anchorMessage)
67
+
68
+ if (anchorMessage.threadId && !messagesById.has(anchorMessage.threadId)) {
69
+ const threadRoot = await em.findOne(Message, {
70
+ id: anchorMessage.threadId,
71
+ tenantId: input.tenantId,
72
+ organizationId: input.organizationId,
73
+ deletedAt: null,
74
+ isDraft: false,
75
+ })
76
+ if (threadRoot) {
77
+ messagesById.set(threadRoot.id, threadRoot)
78
+ }
79
+ }
80
+
81
+ let parentMessageId = anchorMessage.parentMessageId ?? null
82
+ while (parentMessageId) {
83
+ if (messagesById.has(parentMessageId)) break
84
+ const parentMessage = await em.findOne(Message, {
85
+ id: parentMessageId,
86
+ tenantId: input.tenantId,
87
+ organizationId: input.organizationId,
88
+ deletedAt: null,
89
+ isDraft: false,
90
+ })
91
+ if (!parentMessage) break
92
+ messagesById.set(parentMessage.id, parentMessage)
93
+ parentMessageId = parentMessage.parentMessageId ?? null
94
+ }
95
+
96
+ let frontier = Array.from(messagesById.keys())
97
+ while (frontier.length > 0) {
98
+ const children = await em.find(Message, {
99
+ parentMessageId: { $in: frontier },
100
+ tenantId: input.tenantId,
101
+ organizationId: input.organizationId,
102
+ deletedAt: null,
103
+ isDraft: false,
104
+ })
105
+
106
+ const nextFrontier: string[] = []
107
+ for (const child of children) {
108
+ if (messagesById.has(child.id)) continue
109
+ messagesById.set(child.id, child)
110
+ nextFrontier.push(child.id)
111
+ }
112
+ frontier = nextFrontier
113
+ }
114
+
115
+ const scopedMessages = Array.from(messagesById.values())
116
+ const messageIds = scopedMessages.map((message) => message.id)
117
+ const recipientRows = messageIds.length > 0
118
+ ? await em.find(MessageRecipient, {
119
+ messageId: { $in: messageIds },
120
+ recipientUserId: input.userId,
121
+ deletedAt: null,
122
+ })
123
+ : []
124
+
125
+ const recipientMessageIds = new Set(recipientRows.map((item) => item.messageId))
126
+ const senderMessageIds = new Set(
127
+ scopedMessages
128
+ .filter((item) => item.senderUserId === input.userId)
129
+ .map((item) => item.id),
130
+ )
131
+
132
+ const visibleMessageIds = scopedMessages
133
+ .map((item) => item.id)
134
+ .filter((messageId) => recipientMessageIds.has(messageId) || senderMessageIds.has(messageId))
135
+
136
+ if (visibleMessageIds.length === 0) throw new Error('Access denied')
137
+
138
+ return {
139
+ anchorMessageId: anchorMessage.id,
140
+ messageIds: visibleMessageIds,
141
+ recipientMessageIds,
142
+ senderMessageIds,
143
+ }
144
+ }
145
+
146
+ const archiveConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {
147
+ id: 'messages.conversation.archive_for_actor',
148
+ async execute(rawInput, ctx) {
149
+ const input = conversationCommandSchema.parse(rawInput)
150
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
151
+ const scope = await resolveConversationScope(em, input)
152
+ const messageIdsToArchive = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))
153
+
154
+ if (messageIdsToArchive.length === 0) {
155
+ return { ok: true, affectedCount: 0 }
156
+ }
157
+
158
+ const archivedAt = new Date()
159
+ const archivedIds: string[] = []
160
+ await em.transactional(async (trx) => {
161
+ const recipients = await trx.find(MessageRecipient, {
162
+ messageId: { $in: messageIdsToArchive },
163
+ recipientUserId: input.userId,
164
+ deletedAt: null,
165
+ })
166
+ for (const recipient of recipients) {
167
+ if (recipient.status !== 'archived' || recipient.archivedAt === null) {
168
+ recipient.archivedAt = archivedAt
169
+ recipient.status = 'archived'
170
+ archivedIds.push(recipient.messageId)
171
+ }
172
+ }
173
+ })
174
+
175
+ for (const messageId of archivedIds) {
176
+ await emitMessagesEvent('messages.message.archived', {
177
+ messageId,
178
+ recipientUserId: input.userId,
179
+ userId: input.userId,
180
+ tenantId: input.tenantId,
181
+ organizationId: input.organizationId,
182
+ }, { persistent: true })
183
+ }
184
+
185
+ return { ok: true, affectedCount: archivedIds.length }
186
+ },
187
+ }
188
+
189
+ const markConversationUnreadForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {
190
+ id: 'messages.conversation.mark_unread_for_actor',
191
+ async execute(rawInput, ctx) {
192
+ const input = conversationCommandSchema.parse(rawInput)
193
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
194
+ const scope = await resolveConversationScope(em, input)
195
+ const messageIdsToMarkUnread = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))
196
+
197
+ if (messageIdsToMarkUnread.length === 0) {
198
+ return { ok: true, affectedCount: 0 }
199
+ }
200
+
201
+ const markedIds: string[] = []
202
+ await em.transactional(async (trx) => {
203
+ const recipients = await trx.find(MessageRecipient, {
204
+ messageId: { $in: messageIdsToMarkUnread },
205
+ recipientUserId: input.userId,
206
+ deletedAt: null,
207
+ })
208
+ for (const recipient of recipients) {
209
+ if (recipient.status !== 'unread' || recipient.readAt !== null) {
210
+ recipient.status = 'unread'
211
+ recipient.readAt = null
212
+ markedIds.push(recipient.messageId)
213
+ }
214
+ }
215
+ })
216
+
217
+ for (const messageId of markedIds) {
218
+ await emitMessagesEvent('messages.message.marked_unread', {
219
+ messageId,
220
+ recipientUserId: input.userId,
221
+ userId: input.userId,
222
+ tenantId: input.tenantId,
223
+ organizationId: input.organizationId,
224
+ }, { persistent: true })
225
+ }
226
+
227
+ return { ok: true, affectedCount: markedIds.length }
228
+ },
229
+ }
230
+
231
+ type DeletedTarget = { messageId: string; target: 'sender' | 'recipient' }
232
+
233
+ const deleteConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {
234
+ id: 'messages.conversation.delete_for_actor',
235
+ async execute(rawInput, ctx) {
236
+ const input = conversationCommandSchema.parse(rawInput)
237
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
238
+ const scope = await resolveConversationScope(em, input)
239
+ const messageIdsToDelete = scope.messageIds.filter(
240
+ (messageId) => scope.recipientMessageIds.has(messageId) || scope.senderMessageIds.has(messageId),
241
+ )
242
+
243
+ if (messageIdsToDelete.length === 0) {
244
+ return { ok: true, affectedCount: 0 }
245
+ }
246
+
247
+ const deletedNow = new Date()
248
+ const deletedTargets: DeletedTarget[] = []
249
+ await em.transactional(async (trx) => {
250
+ for (const messageId of messageIdsToDelete) {
251
+ const recipient = await trx.findOne(MessageRecipient, {
252
+ messageId,
253
+ recipientUserId: input.userId,
254
+ deletedAt: null,
255
+ })
256
+ if (recipient) {
257
+ recipient.status = 'deleted'
258
+ recipient.deletedAt = deletedNow
259
+ deletedTargets.push({ messageId, target: 'recipient' })
260
+ continue
261
+ }
262
+ if (scope.senderMessageIds.has(messageId)) {
263
+ const message = await trx.findOne(Message, {
264
+ id: messageId,
265
+ tenantId: input.tenantId,
266
+ deletedAt: null,
267
+ })
268
+ if (message && message.senderUserId === input.userId) {
269
+ message.deletedAt = deletedNow
270
+ deletedTargets.push({ messageId, target: 'sender' })
271
+ }
272
+ }
273
+ }
274
+ })
275
+
276
+ for (const { messageId, target } of deletedTargets) {
277
+ await emitMessagesEvent('messages.message.deleted', {
278
+ messageId,
279
+ actorUserId: input.userId,
280
+ target,
281
+ tenantId: input.tenantId,
282
+ organizationId: input.organizationId,
283
+ }, { persistent: true })
284
+ }
285
+
286
+ return { ok: true, affectedCount: deletedTargets.length }
287
+ },
288
+ }
289
+
290
+ registerCommand(archiveConversationForActorCommand)
291
+ registerCommand(markConversationUnreadForActorCommand)
292
+ registerCommand(deleteConversationForActorCommand)