@antelopejs/dms-mailing 0.3.4 → 0.4.0

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 (705) hide show
  1. package/README.md +232 -22
  2. package/dist/automation/email-event-trigger.js +2 -2
  3. package/dist/automation/email-event-trigger.js.map +1 -1
  4. package/dist/automation/send-template-action.js +2 -2
  5. package/dist/automation/send-template-action.js.map +1 -1
  6. package/dist/config.js +3 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.d.ts +10 -1
  9. package/dist/constants.js +15 -3
  10. package/dist/constants.js.map +1 -1
  11. package/dist/crons/attention.d.ts +3 -0
  12. package/dist/crons/attention.js +19 -0
  13. package/dist/crons/attention.js.map +1 -0
  14. package/dist/crons/index.d.ts +2 -0
  15. package/dist/crons/index.js +2 -0
  16. package/dist/crons/index.js.map +1 -1
  17. package/dist/crons/jobs.d.ts +12 -0
  18. package/dist/crons/jobs.js +27 -0
  19. package/dist/crons/jobs.js.map +1 -0
  20. package/dist/crons/retention.d.ts +2 -4
  21. package/dist/crons/retention.js +7 -22
  22. package/dist/crons/retention.js.map +1 -1
  23. package/dist/data/displays.d.ts +5 -0
  24. package/dist/data/displays.js +23 -0
  25. package/dist/data/displays.js.map +1 -0
  26. package/dist/data/index.d.ts +1 -0
  27. package/dist/data/index.js +1 -0
  28. package/dist/data/index.js.map +1 -1
  29. package/dist/data/locale-fallback.d.ts +3 -0
  30. package/dist/data/locale-fallback.js +20 -0
  31. package/dist/data/locale-fallback.js.map +1 -0
  32. package/dist/data/read-only-routes.d.ts +7 -0
  33. package/dist/data/read-only-routes.js +13 -0
  34. package/dist/data/read-only-routes.js.map +1 -0
  35. package/dist/data/sends-table.d.ts +19 -5
  36. package/dist/data/sends-table.js +185 -17
  37. package/dist/data/sends-table.js.map +1 -1
  38. package/dist/data/suppressions-table.d.ts +24 -0
  39. package/dist/data/suppressions-table.js +120 -0
  40. package/dist/data/suppressions-table.js.map +1 -0
  41. package/dist/data/templates-table.d.ts +20 -2
  42. package/dist/data/templates-table.js +188 -31
  43. package/dist/data/templates-table.js.map +1 -1
  44. package/dist/data-types/categories-type.d.ts +11 -13
  45. package/dist/data-types/categories-type.js +3 -3
  46. package/dist/data-types/categories-type.js.map +1 -1
  47. package/dist/data-types/category-type.js +1 -1
  48. package/dist/data-types/category-type.js.map +1 -1
  49. package/dist/db/models/index.d.ts +3 -0
  50. package/dist/db/models/index.js +3 -0
  51. package/dist/db/models/index.js.map +1 -1
  52. package/dist/db/models/sends.model.d.ts +12 -1
  53. package/dist/db/models/sends.model.js +64 -0
  54. package/dist/db/models/sends.model.js.map +1 -1
  55. package/dist/db/models/settings.model.d.ts +0 -1
  56. package/dist/db/models/settings.model.js +0 -7
  57. package/dist/db/models/settings.model.js.map +1 -1
  58. package/dist/db/models/suppressions.model.d.ts +28 -0
  59. package/dist/db/models/suppressions.model.js +46 -0
  60. package/dist/db/models/suppressions.model.js.map +1 -0
  61. package/dist/db/models/template-versions.model.d.ts +24 -0
  62. package/dist/db/models/template-versions.model.js +17 -0
  63. package/dist/db/models/template-versions.model.js.map +1 -0
  64. package/dist/db/models/templates.model.d.ts +2 -0
  65. package/dist/db/models/templates.model.js +3 -0
  66. package/dist/db/models/templates.model.js.map +1 -1
  67. package/dist/db/models/webhook-status.model.d.ts +24 -0
  68. package/dist/db/models/webhook-status.model.js +21 -0
  69. package/dist/db/models/webhook-status.model.js.map +1 -0
  70. package/dist/db/tables/index.d.ts +3 -0
  71. package/dist/db/tables/index.js +3 -0
  72. package/dist/db/tables/index.js.map +1 -1
  73. package/dist/db/tables/send-events.table.d.ts +2 -2
  74. package/dist/db/tables/send-events.table.js.map +1 -1
  75. package/dist/db/tables/sends.table.d.ts +10 -2
  76. package/dist/db/tables/sends.table.js +34 -1
  77. package/dist/db/tables/sends.table.js.map +1 -1
  78. package/dist/db/tables/suppressions.table.d.ts +12 -0
  79. package/dist/db/tables/suppressions.table.js +53 -0
  80. package/dist/db/tables/suppressions.table.js.map +1 -0
  81. package/dist/db/tables/template-versions.table.d.ts +13 -0
  82. package/dist/db/tables/template-versions.table.js +59 -0
  83. package/dist/db/tables/template-versions.table.js.map +1 -0
  84. package/dist/db/tables/templates.table.d.ts +11 -0
  85. package/dist/db/tables/templates.table.js +45 -0
  86. package/dist/db/tables/templates.table.js.map +1 -1
  87. package/dist/db/tables/webhook-status.table.d.ts +14 -0
  88. package/dist/db/tables/webhook-status.table.js +58 -0
  89. package/dist/db/tables/webhook-status.table.js.map +1 -0
  90. package/dist/engine/changes.d.ts +12 -0
  91. package/dist/engine/changes.js +107 -0
  92. package/dist/engine/changes.js.map +1 -0
  93. package/dist/engine/conditions.js +0 -2
  94. package/dist/engine/conditions.js.map +1 -1
  95. package/dist/engine/coverage.d.ts +1 -0
  96. package/dist/engine/coverage.js +6 -2
  97. package/dist/engine/coverage.js.map +1 -1
  98. package/dist/engine/fallback-html.d.ts +1 -1
  99. package/dist/engine/fallback-html.js +51 -34
  100. package/dist/engine/fallback-html.js.map +1 -1
  101. package/dist/engine/index.d.ts +3 -0
  102. package/dist/engine/index.js +3 -0
  103. package/dist/engine/index.js.map +1 -1
  104. package/dist/engine/plain-text.d.ts +3 -0
  105. package/dist/engine/plain-text.js +60 -0
  106. package/dist/engine/plain-text.js.map +1 -0
  107. package/dist/engine/resolve.d.ts +2 -1
  108. package/dist/engine/resolve.js +17 -9
  109. package/dist/engine/resolve.js.map +1 -1
  110. package/dist/engine/system-variables.d.ts +10 -0
  111. package/dist/engine/system-variables.js +70 -0
  112. package/dist/engine/system-variables.js.map +1 -0
  113. package/dist/engine/tokens.d.ts +2 -1
  114. package/dist/engine/tokens.js +6 -2
  115. package/dist/engine/tokens.js.map +1 -1
  116. package/dist/hooks/tenant-export.js +13 -1
  117. package/dist/hooks/tenant-export.js.map +1 -1
  118. package/dist/implementations/dms-mailing/index.d.ts +1 -1
  119. package/dist/implementations/dms-mailing/index.js +2 -2
  120. package/dist/implementations/dms-mailing/index.js.map +1 -1
  121. package/dist/index.js +13 -11
  122. package/dist/index.js.map +1 -1
  123. package/dist/pages/editor.js +9 -2
  124. package/dist/pages/editor.js.map +1 -1
  125. package/dist/pages/index.d.ts +1 -0
  126. package/dist/pages/index.js +1 -0
  127. package/dist/pages/index.js.map +1 -1
  128. package/dist/pages/module.d.ts +2 -0
  129. package/dist/pages/module.js +26 -2
  130. package/dist/pages/module.js.map +1 -1
  131. package/dist/pages/overview.d.ts +2 -6
  132. package/dist/pages/overview.js +109 -52
  133. package/dist/pages/overview.js.map +1 -1
  134. package/dist/pages/sends.d.ts +3 -4
  135. package/dist/pages/sends.js +197 -60
  136. package/dist/pages/sends.js.map +1 -1
  137. package/dist/pages/settings/form.js +114 -53
  138. package/dist/pages/settings/form.js.map +1 -1
  139. package/dist/pages/settings/page.d.ts +0 -1
  140. package/dist/pages/settings/page.js +8 -11
  141. package/dist/pages/settings/page.js.map +1 -1
  142. package/dist/pages/suppressions.d.ts +9 -0
  143. package/dist/pages/suppressions.js +151 -0
  144. package/dist/pages/suppressions.js.map +1 -0
  145. package/dist/pages/templates.d.ts +0 -1
  146. package/dist/pages/templates.js +114 -40
  147. package/dist/pages/templates.js.map +1 -1
  148. package/dist/permissions.js +2 -2
  149. package/dist/permissions.js.map +1 -1
  150. package/dist/quick-actions.js +7 -4
  151. package/dist/quick-actions.js.map +1 -1
  152. package/dist/routes/events.d.ts +8 -5
  153. package/dist/routes/events.js +55 -24
  154. package/dist/routes/events.js.map +1 -1
  155. package/dist/routes/index.d.ts +2 -0
  156. package/dist/routes/index.js +2 -0
  157. package/dist/routes/index.js.map +1 -1
  158. package/dist/routes/metrics.d.ts +60 -23
  159. package/dist/routes/metrics.js +231 -83
  160. package/dist/routes/metrics.js.map +1 -1
  161. package/dist/routes/period.d.ts +2 -1
  162. package/dist/routes/period.js +9 -3
  163. package/dist/routes/period.js.map +1 -1
  164. package/dist/routes/sends.d.ts +49 -8
  165. package/dist/routes/sends.js +239 -24
  166. package/dist/routes/sends.js.map +1 -1
  167. package/dist/routes/settings.d.ts +44 -5
  168. package/dist/routes/settings.js +190 -15
  169. package/dist/routes/settings.js.map +1 -1
  170. package/dist/routes/suppressions.d.ts +16 -0
  171. package/dist/routes/suppressions.js +92 -0
  172. package/dist/routes/suppressions.js.map +1 -0
  173. package/dist/routes/template-versions.d.ts +28 -0
  174. package/dist/routes/template-versions.js +87 -0
  175. package/dist/routes/template-versions.js.map +1 -0
  176. package/dist/routes/templates.d.ts +60 -6
  177. package/dist/routes/templates.js +204 -35
  178. package/dist/routes/templates.js.map +1 -1
  179. package/dist/services/attention.d.ts +19 -6
  180. package/dist/services/attention.js +177 -12
  181. package/dist/services/attention.js.map +1 -1
  182. package/dist/services/catalog.d.ts +3 -0
  183. package/dist/services/catalog.js +25 -0
  184. package/dist/services/catalog.js.map +1 -0
  185. package/dist/services/categories.d.ts +8 -32
  186. package/dist/services/categories.js +14 -25
  187. package/dist/services/categories.js.map +1 -1
  188. package/dist/services/checked-send.d.ts +9 -0
  189. package/dist/services/checked-send.js +54 -0
  190. package/dist/services/checked-send.js.map +1 -0
  191. package/dist/services/events.d.ts +5 -0
  192. package/dist/services/events.js +102 -9
  193. package/dist/services/events.js.map +1 -1
  194. package/dist/services/metrics.d.ts +17 -6
  195. package/dist/services/metrics.js +57 -18
  196. package/dist/services/metrics.js.map +1 -1
  197. package/dist/services/notify.d.ts +18 -10
  198. package/dist/services/notify.js +82 -37
  199. package/dist/services/notify.js.map +1 -1
  200. package/dist/services/provider-banner.d.ts +14 -0
  201. package/dist/services/provider-banner.js +115 -0
  202. package/dist/services/provider-banner.js.map +1 -0
  203. package/dist/services/render.js.map +1 -1
  204. package/dist/services/send-stat-items.d.ts +7 -0
  205. package/dist/services/send-stat-items.js +127 -0
  206. package/dist/services/send-stat-items.js.map +1 -0
  207. package/dist/services/send-stats.d.ts +23 -0
  208. package/dist/services/send-stats.js +63 -0
  209. package/dist/services/send-stats.js.map +1 -0
  210. package/dist/services/send.d.ts +18 -3
  211. package/dist/services/send.js +81 -17
  212. package/dist/services/send.js.map +1 -1
  213. package/dist/services/settings.d.ts +5 -0
  214. package/dist/services/settings.js +28 -7
  215. package/dist/services/settings.js.map +1 -1
  216. package/dist/services/starters.d.ts +15 -0
  217. package/dist/services/starters.js +264 -0
  218. package/dist/services/starters.js.map +1 -0
  219. package/dist/services/suppressions.d.ts +20 -0
  220. package/dist/services/suppressions.js +53 -0
  221. package/dist/services/suppressions.js.map +1 -0
  222. package/dist/services/template-attention.d.ts +4 -0
  223. package/dist/services/template-attention.js +43 -0
  224. package/dist/services/template-attention.js.map +1 -0
  225. package/dist/services/template-stats.d.ts +27 -0
  226. package/dist/services/template-stats.js +59 -0
  227. package/dist/services/template-stats.js.map +1 -0
  228. package/dist/services/templates.d.ts +31 -4
  229. package/dist/services/templates.js +240 -21
  230. package/dist/services/templates.js.map +1 -1
  231. package/dist/services/webhook-status.d.ts +27 -0
  232. package/dist/services/webhook-status.js +134 -0
  233. package/dist/services/webhook-status.js.map +1 -0
  234. package/dist/types/blocks.d.ts +1 -0
  235. package/dist/types/index.d.ts +2 -0
  236. package/dist/types/index.js +2 -0
  237. package/dist/types/index.js.map +1 -1
  238. package/dist/types/sends.d.ts +10 -1
  239. package/dist/types/sends.js +35 -2
  240. package/dist/types/sends.js.map +1 -1
  241. package/dist/types/suppressions.d.ts +9 -0
  242. package/dist/types/suppressions.js +10 -0
  243. package/dist/types/suppressions.js.map +1 -0
  244. package/dist/types/webhook.d.ts +17 -0
  245. package/dist/types/webhook.js +3 -0
  246. package/dist/types/webhook.js.map +1 -0
  247. package/dist/validation/settings.schema.d.ts +144 -9
  248. package/dist/validation/settings.schema.js +113 -16
  249. package/dist/validation/settings.schema.js.map +1 -1
  250. package/dist/validation/templates.schema.d.ts +137 -10
  251. package/dist/validation/templates.schema.js +28 -2
  252. package/dist/validation/templates.schema.js.map +1 -1
  253. package/frontend-vue/app/build/components/FormFieldRow.vue +90 -0
  254. package/frontend-vue/app/build/components/FormRows.vue +26 -0
  255. package/frontend-vue/app/build/components/RequiredLegend.vue +11 -0
  256. package/frontend-vue/app/components/CategoriesInput.vue +323 -77
  257. package/frontend-vue/app/components/CategoryInput.vue +13 -14
  258. package/frontend-vue/app/components/CategoryLabel.vue +34 -0
  259. package/frontend-vue/app/components/DuplicateModal.vue +201 -0
  260. package/frontend-vue/app/components/Editor.vue +55 -20
  261. package/frontend-vue/app/components/FixAddressModal.vue +133 -0
  262. package/frontend-vue/app/components/FunnelCard.vue +146 -43
  263. package/frontend-vue/app/components/LocaleSelect.vue +98 -0
  264. package/frontend-vue/app/components/NewTemplateModal.vue +388 -102
  265. package/frontend-vue/app/components/NewTemplateSourceTile.vue +70 -0
  266. package/frontend-vue/app/components/OverviewGate.vue +220 -0
  267. package/frontend-vue/app/components/PageHeader.vue +98 -0
  268. package/frontend-vue/app/components/RealSendModal.vue +555 -0
  269. package/frontend-vue/app/components/RecipientsField.vue +41 -0
  270. package/frontend-vue/app/components/RenderingModal.vue +189 -0
  271. package/frontend-vue/app/components/RetentionInput.vue +212 -0
  272. package/frontend-vue/app/components/SendDrawer.vue +542 -209
  273. package/frontend-vue/app/components/StarterGallery.vue +153 -0
  274. package/frontend-vue/app/components/SystemVariables.vue +63 -0
  275. package/frontend-vue/app/components/TemplateCard.vue +218 -57
  276. package/frontend-vue/app/components/TemplateDrawer.vue +785 -274
  277. package/frontend-vue/app/components/TemplateGallery.vue +312 -56
  278. package/frontend-vue/app/components/TemplatePreviewFrame.vue +112 -22
  279. package/frontend-vue/app/components/TestSendModal.vue +266 -95
  280. package/frontend-vue/app/components/TokenText.vue +61 -10
  281. package/frontend-vue/app/components/VariablePath.vue +20 -0
  282. package/frontend-vue/app/components/VersionHistory.vue +196 -0
  283. package/frontend-vue/app/components/VersionPreview.vue +164 -0
  284. package/frontend-vue/app/components/WebhookStatus.vue +130 -0
  285. package/frontend-vue/app/components/blocks/Button.vue +1 -1
  286. package/frontend-vue/app/components/blocks/Code.vue +1 -1
  287. package/frontend-vue/app/components/blocks/Footer.vue +56 -7
  288. package/frontend-vue/app/components/blocks/Heading.vue +1 -1
  289. package/frontend-vue/app/components/blocks/Hero.vue +13 -7
  290. package/frontend-vue/app/components/blocks/List.vue +50 -6
  291. package/frontend-vue/app/components/blocks/Paragraph.vue +1 -1
  292. package/frontend-vue/app/components/blocks/Total.vue +3 -3
  293. package/frontend-vue/app/components/editor/EditorAutocompleteMenu.vue +50 -0
  294. package/frontend-vue/app/components/editor/EditorBlock.vue +37 -90
  295. package/frontend-vue/app/components/editor/EditorBlockFields.vue +140 -0
  296. package/frontend-vue/app/components/editor/EditorBlockInspector.vue +92 -0
  297. package/frontend-vue/app/components/editor/EditorBlockList.vue +32 -25
  298. package/frontend-vue/app/components/editor/EditorBlockPicker.vue +58 -0
  299. package/frontend-vue/app/components/editor/EditorBlockTools.vue +71 -0
  300. package/frontend-vue/app/components/editor/EditorCanvas.vue +105 -70
  301. package/frontend-vue/app/components/editor/EditorCompare.vue +211 -0
  302. package/frontend-vue/app/components/editor/EditorConditionFields.vue +56 -46
  303. package/frontend-vue/app/components/editor/EditorConditionInspector.vue +54 -0
  304. package/frontend-vue/app/components/editor/EditorEnvelope.vue +77 -0
  305. package/frontend-vue/app/components/editor/EditorIfBlock.vue +36 -86
  306. package/frontend-vue/app/components/editor/EditorInsertPoint.vue +64 -0
  307. package/frontend-vue/app/components/editor/EditorInspector.vue +80 -0
  308. package/frontend-vue/app/components/editor/EditorLeftPane.vue +103 -0
  309. package/frontend-vue/app/components/editor/EditorLifecycle.vue +67 -0
  310. package/frontend-vue/app/components/editor/EditorLoading.vue +23 -0
  311. package/frontend-vue/app/components/editor/EditorLocaleSwitch.vue +61 -0
  312. package/frontend-vue/app/components/editor/EditorMissingLocale.vue +72 -48
  313. package/frontend-vue/app/components/editor/EditorMissingPolicy.vue +42 -0
  314. package/frontend-vue/app/components/editor/EditorNotice.vue +127 -0
  315. package/frontend-vue/app/components/editor/EditorOutline.vue +65 -0
  316. package/frontend-vue/app/components/editor/EditorPalette.vue +95 -42
  317. package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +66 -0
  318. package/frontend-vue/app/components/editor/EditorPublishReview.vue +102 -0
  319. package/frontend-vue/app/components/editor/EditorSaveState.vue +88 -0
  320. package/frontend-vue/app/components/editor/EditorShortcutHint.vue +17 -0
  321. package/frontend-vue/app/components/editor/EditorSimChip.vue +51 -0
  322. package/frontend-vue/app/components/editor/EditorTemplatePane.vue +131 -0
  323. package/frontend-vue/app/components/editor/EditorTestDataPane.vue +98 -0
  324. package/frontend-vue/app/components/editor/EditorTitle.vue +82 -0
  325. package/frontend-vue/app/components/editor/EditorTokenField.vue +224 -0
  326. package/frontend-vue/app/components/editor/EditorToolbar.vue +34 -134
  327. package/frontend-vue/app/components/editor/EditorVariableForm.vue +100 -0
  328. package/frontend-vue/app/components/editor/EditorVariableRow.vue +87 -0
  329. package/frontend-vue/app/components/editor/EditorVariablesPane.vue +161 -0
  330. package/frontend-vue/app/components/editor/EditorViewSwitches.vue +76 -0
  331. package/frontend-vue/app/components/editor/EditorVisibility.vue +101 -0
  332. package/frontend-vue/app/components/editor/EditorWorkspace.vue +62 -149
  333. package/frontend-vue/app/composables/useBlockContext.ts +16 -0
  334. package/frontend-vue/app/composables/useEditorActions.ts +381 -0
  335. package/frontend-vue/app/composables/useEditorAnnouncements.ts +51 -0
  336. package/frontend-vue/app/composables/useEditorContext.ts +30 -23
  337. package/frontend-vue/app/composables/useEditorSession.ts +299 -0
  338. package/frontend-vue/app/composables/useEditorShortcuts.ts +81 -0
  339. package/frontend-vue/app/composables/useLocalizedDefaults.ts +17 -0
  340. package/frontend-vue/app/composables/useMailingApi.ts +142 -27
  341. package/frontend-vue/app/composables/useOverviewSetup.ts +13 -0
  342. package/frontend-vue/app/composables/useReplayToast.ts +72 -0
  343. package/frontend-vue/app/composables/useTemplateCategories.ts +35 -0
  344. package/frontend-vue/app/composables/useTemplateDrawer.ts +196 -62
  345. package/frontend-vue/app/composables/useTemplateEditor.ts +318 -86
  346. package/frontend-vue/app/emails/EmailMailingTemplate.vue +16 -4
  347. package/frontend-vue/app/emails/components/MailingBlocks.vue +96 -38
  348. package/frontend-vue/app/emails/components/MailingTextLines.vue +20 -0
  349. package/frontend-vue/app/plugins/column-displays.ts +65 -0
  350. package/frontend-vue/app/plugins/{template-gallery-display.client.ts → template-gallery-display.ts} +1 -1
  351. package/frontend-vue/app/types/mailing.ts +193 -18
  352. package/frontend-vue/app/utils/api-error.ts +33 -0
  353. package/frontend-vue/app/utils/autocomplete.ts +120 -0
  354. package/frontend-vue/app/utils/blocks/components.ts +10 -10
  355. package/frontend-vue/app/utils/blocks/dnd.ts +7 -3
  356. package/frontend-vue/app/utils/blocks/factory.ts +199 -38
  357. package/frontend-vue/app/utils/blocks/fields.ts +39 -19
  358. package/frontend-vue/app/utils/blocks/index.ts +1 -0
  359. package/frontend-vue/app/utils/blocks/outline.ts +91 -0
  360. package/frontend-vue/app/utils/blocks/tree.ts +114 -2
  361. package/frontend-vue/app/utils/categories.ts +201 -15
  362. package/frontend-vue/app/utils/changes.ts +152 -0
  363. package/frontend-vue/app/utils/conditions.ts +25 -19
  364. package/frontend-vue/app/utils/editor-checks.ts +266 -0
  365. package/frontend-vue/app/utils/editor-route.ts +43 -4
  366. package/frontend-vue/app/utils/funnel.ts +74 -0
  367. package/frontend-vue/app/utils/gallery.ts +179 -1
  368. package/frontend-vue/app/utils/history.ts +66 -0
  369. package/frontend-vue/app/utils/lifecycle.ts +185 -0
  370. package/frontend-vue/app/utils/locales.ts +107 -2
  371. package/frontend-vue/app/utils/overview-setup.ts +83 -0
  372. package/frontend-vue/app/utils/pages.ts +5 -0
  373. package/frontend-vue/app/utils/paths.ts +51 -15
  374. package/frontend-vue/app/utils/period.ts +62 -22
  375. package/frontend-vue/app/utils/problems.ts +181 -0
  376. package/frontend-vue/app/utils/recipients.ts +49 -0
  377. package/frontend-vue/app/utils/rendering-title.ts +23 -0
  378. package/frontend-vue/app/utils/send-actions.ts +207 -0
  379. package/frontend-vue/app/utils/send-link.ts +28 -0
  380. package/frontend-vue/app/utils/send-snippet.ts +97 -0
  381. package/frontend-vue/app/utils/send-stats.ts +24 -0
  382. package/frontend-vue/app/utils/send-values.ts +156 -0
  383. package/frontend-vue/app/utils/shortcuts.ts +113 -0
  384. package/frontend-vue/app/utils/slug.ts +82 -0
  385. package/frontend-vue/app/utils/starters.ts +23 -0
  386. package/frontend-vue/app/utils/suppression.ts +112 -0
  387. package/frontend-vue/app/utils/system-variables.ts +165 -0
  388. package/frontend-vue/app/utils/template-attention.ts +104 -0
  389. package/frontend-vue/app/utils/timeline.ts +221 -19
  390. package/frontend-vue/app/utils/tokens.ts +115 -13
  391. package/frontend-vue/app/utils/variable-types.ts +23 -0
  392. package/frontend-vue/app/utils/variables.ts +208 -1
  393. package/frontend-vue/app/utils/version-history.ts +53 -0
  394. package/frontend-vue/app/utils/webhook-status.ts +170 -0
  395. package/frontend-vue/dms.frontend.ts +19 -11
  396. package/frontend-vue/i18n/locales/mailing-en-GB.json +1173 -161
  397. package/frontend-vue/i18n/locales/mailing-fr-FR.json +1220 -208
  398. package/frontend-vue/package.json +2 -2
  399. package/frontend-vue/pnpm-lock.yaml +46 -161
  400. package/frontend-vue/tests/api-error.test.ts +27 -0
  401. package/frontend-vue/tests/autocomplete.test.ts +84 -0
  402. package/frontend-vue/tests/block-components.test.ts +1 -1
  403. package/frontend-vue/tests/block-fields.test.ts +12 -2
  404. package/frontend-vue/tests/blocks-factory.test.ts +46 -0
  405. package/frontend-vue/tests/blocks-outline.test.ts +102 -0
  406. package/frontend-vue/tests/blocks-tree.test.ts +28 -1
  407. package/frontend-vue/tests/categories-input.test.ts +139 -0
  408. package/frontend-vue/tests/categories.test.ts +148 -15
  409. package/frontend-vue/tests/changes.test.ts +136 -0
  410. package/frontend-vue/tests/column-displays.test.ts +73 -0
  411. package/frontend-vue/tests/conditions.test.ts +49 -19
  412. package/frontend-vue/tests/dnd.test.ts +10 -0
  413. package/frontend-vue/tests/editor-block-list.test.ts +24 -2
  414. package/frontend-vue/tests/editor-checks.test.ts +178 -0
  415. package/frontend-vue/tests/editor-route.test.ts +37 -9
  416. package/frontend-vue/tests/editor-session.test.ts +206 -0
  417. package/frontend-vue/tests/footer-block.test.ts +88 -0
  418. package/frontend-vue/tests/funnel-card.test.ts +116 -0
  419. package/frontend-vue/tests/funnel.test.ts +81 -0
  420. package/frontend-vue/tests/gallery.test.ts +124 -2
  421. package/frontend-vue/tests/helpers/nuxt-globals.ts +232 -0
  422. package/frontend-vue/tests/history.test.ts +48 -0
  423. package/frontend-vue/tests/i18n-editor.test.ts +38 -0
  424. package/frontend-vue/tests/i18n.test.ts +55 -0
  425. package/frontend-vue/tests/lifecycle.test.ts +145 -0
  426. package/frontend-vue/tests/locales.test.ts +60 -0
  427. package/frontend-vue/tests/localized-defaults.test.ts +54 -0
  428. package/frontend-vue/tests/new-template-modal.test.ts +210 -0
  429. package/frontend-vue/tests/overview-gate.test.ts +146 -0
  430. package/frontend-vue/tests/overview-setup.test.ts +78 -0
  431. package/frontend-vue/tests/paths.test.ts +23 -0
  432. package/frontend-vue/tests/period.test.ts +59 -10
  433. package/frontend-vue/tests/problems.test.ts +153 -0
  434. package/frontend-vue/tests/recipients.test.ts +47 -0
  435. package/frontend-vue/tests/rendering-title.test.ts +24 -0
  436. package/frontend-vue/tests/replay-toast.test.ts +67 -0
  437. package/frontend-vue/tests/retention-input.test.ts +129 -0
  438. package/frontend-vue/tests/send-actions.test.ts +128 -0
  439. package/frontend-vue/tests/send-modals.test.ts +415 -0
  440. package/frontend-vue/tests/send-snippet.test.ts +51 -0
  441. package/frontend-vue/tests/send-stats.test.ts +12 -0
  442. package/frontend-vue/tests/send-values.test.ts +119 -0
  443. package/frontend-vue/tests/shortcuts.test.ts +75 -0
  444. package/frontend-vue/tests/slug.test.ts +88 -0
  445. package/frontend-vue/tests/suppression.test.ts +159 -0
  446. package/frontend-vue/tests/system-variables.test.ts +133 -0
  447. package/frontend-vue/tests/template-attention.test.ts +123 -0
  448. package/frontend-vue/tests/template-drawer.test.ts +449 -0
  449. package/frontend-vue/tests/template-editor.test.ts +218 -9
  450. package/frontend-vue/tests/template-gallery-display.test.ts +32 -0
  451. package/frontend-vue/tests/template-gallery.test.ts +370 -0
  452. package/frontend-vue/tests/template-overlays.test.ts +125 -0
  453. package/frontend-vue/tests/timeline.test.ts +175 -20
  454. package/frontend-vue/tests/tokens.test.ts +79 -1
  455. package/frontend-vue/tests/variable-path.test.ts +22 -0
  456. package/frontend-vue/tests/variable-types.test.ts +20 -0
  457. package/frontend-vue/tests/variables.test.ts +90 -2
  458. package/frontend-vue/tests/version-history.test.ts +41 -0
  459. package/frontend-vue/tests/webhook-status.test.ts +108 -0
  460. package/package.json +11 -9
  461. package/src/automation/email-event-trigger.ts +3 -3
  462. package/src/automation/send-template-action.ts +7 -5
  463. package/src/config.ts +3 -1
  464. package/src/constants.ts +18 -2
  465. package/src/crons/attention.ts +21 -0
  466. package/src/crons/index.ts +2 -0
  467. package/src/crons/jobs.ts +35 -0
  468. package/src/crons/retention.ts +7 -22
  469. package/src/data/displays.ts +18 -0
  470. package/src/data/index.ts +1 -0
  471. package/src/data/locale-fallback.ts +26 -0
  472. package/src/data/read-only-routes.ts +14 -0
  473. package/src/data/sends-table.ts +204 -19
  474. package/src/data/suppressions-table.ts +127 -0
  475. package/src/data/templates-table.ts +295 -63
  476. package/src/data-types/categories-type.ts +4 -5
  477. package/src/data-types/category-type.ts +1 -1
  478. package/src/db/models/index.ts +3 -0
  479. package/src/db/models/sends.model.ts +102 -1
  480. package/src/db/models/settings.model.ts +2 -12
  481. package/src/db/models/suppressions.model.ts +53 -0
  482. package/src/db/models/template-versions.model.ts +23 -0
  483. package/src/db/models/templates.model.ts +17 -0
  484. package/src/db/models/webhook-status.model.ts +25 -0
  485. package/src/db/tables/index.ts +3 -0
  486. package/src/db/tables/send-events.table.ts +6 -2
  487. package/src/db/tables/sends.table.ts +37 -2
  488. package/src/db/tables/suppressions.table.ts +46 -0
  489. package/src/db/tables/template-versions.table.ts +44 -0
  490. package/src/db/tables/templates.table.ts +67 -5
  491. package/src/db/tables/webhook-status.table.ts +44 -0
  492. package/src/engine/changes.ts +198 -0
  493. package/src/engine/conditions.ts +1 -7
  494. package/src/engine/coverage.ts +7 -2
  495. package/src/engine/fallback-html.ts +80 -42
  496. package/src/engine/index.ts +3 -0
  497. package/src/engine/plain-text.ts +86 -0
  498. package/src/engine/resolve.ts +49 -15
  499. package/src/engine/system-variables.ts +124 -0
  500. package/src/engine/tokens.ts +13 -1
  501. package/src/hooks/tenant-export.ts +21 -2
  502. package/src/implementations/dms-mailing/index.ts +1 -1
  503. package/src/index.ts +29 -13
  504. package/src/pages/editor.ts +13 -2
  505. package/src/pages/index.ts +6 -3
  506. package/src/pages/module.ts +31 -2
  507. package/src/pages/overview.ts +187 -128
  508. package/src/pages/sends.ts +271 -112
  509. package/src/pages/settings/form.ts +144 -54
  510. package/src/pages/settings/page.ts +9 -17
  511. package/src/pages/suppressions.ts +157 -0
  512. package/src/pages/templates.ts +139 -42
  513. package/src/permissions.ts +6 -4
  514. package/src/quick-actions.ts +12 -5
  515. package/src/routes/events.ts +110 -33
  516. package/src/routes/index.ts +2 -0
  517. package/src/routes/metrics.ts +348 -98
  518. package/src/routes/period.ts +23 -6
  519. package/src/routes/sends.ts +413 -41
  520. package/src/routes/settings.ts +291 -26
  521. package/src/routes/suppressions.ts +87 -0
  522. package/src/routes/template-versions.ts +116 -0
  523. package/src/routes/templates.ts +334 -48
  524. package/src/services/attention.ts +283 -30
  525. package/src/services/catalog.ts +28 -0
  526. package/src/services/categories.ts +28 -27
  527. package/src/services/checked-send.ts +123 -0
  528. package/src/services/events.ts +174 -20
  529. package/src/services/metrics.ts +137 -28
  530. package/src/services/notify.ts +126 -44
  531. package/src/services/provider-banner.ts +165 -0
  532. package/src/services/render.ts +1 -0
  533. package/src/services/send-stat-items.ts +160 -0
  534. package/src/services/send-stats.ts +122 -0
  535. package/src/services/send.ts +193 -31
  536. package/src/services/settings.ts +52 -8
  537. package/src/services/starters.ts +383 -0
  538. package/src/services/suppressions.ts +116 -0
  539. package/src/services/template-attention.ts +82 -0
  540. package/src/services/template-stats.ts +97 -0
  541. package/src/services/templates.ts +400 -37
  542. package/src/services/webhook-status.ts +202 -0
  543. package/src/types/blocks.ts +6 -0
  544. package/src/types/index.ts +2 -0
  545. package/src/types/sends.ts +64 -2
  546. package/src/types/suppressions.ts +27 -0
  547. package/src/types/webhook.ts +29 -0
  548. package/src/validation/settings.schema.ts +173 -17
  549. package/src/validation/templates.schema.ts +53 -1
  550. package/dist/antelope-modules.d.ts +0 -32
  551. package/dist/antelope-modules.js +0 -36
  552. package/dist/antelope-modules.js.map +0 -1
  553. package/dist/test/antelope.test.d.ts +0 -2
  554. package/dist/test/antelope.test.js +0 -180
  555. package/dist/test/antelope.test.js.map +0 -1
  556. package/dist/test/helpers/http.d.ts +0 -4
  557. package/dist/test/helpers/http.js +0 -28
  558. package/dist/test/helpers/http.js.map +0 -1
  559. package/dist/test/helpers/owner.d.ts +0 -5
  560. package/dist/test/helpers/owner.js +0 -30
  561. package/dist/test/helpers/owner.js.map +0 -1
  562. package/dist/test/integration/events.test.d.ts +0 -1
  563. package/dist/test/integration/events.test.js +0 -143
  564. package/dist/test/integration/events.test.js.map +0 -1
  565. package/dist/test/integration/retention.test.d.ts +0 -1
  566. package/dist/test/integration/retention.test.js +0 -291
  567. package/dist/test/integration/retention.test.js.map +0 -1
  568. package/dist/test/integration/sends.test.d.ts +0 -1
  569. package/dist/test/integration/sends.test.js +0 -210
  570. package/dist/test/integration/sends.test.js.map +0 -1
  571. package/dist/test/integration/settings.test.d.ts +0 -1
  572. package/dist/test/integration/settings.test.js +0 -137
  573. package/dist/test/integration/settings.test.js.map +0 -1
  574. package/dist/test/integration/setup.test.d.ts +0 -1
  575. package/dist/test/integration/setup.test.js +0 -32
  576. package/dist/test/integration/setup.test.js.map +0 -1
  577. package/dist/test/integration/templates.test.d.ts +0 -1
  578. package/dist/test/integration/templates.test.js +0 -233
  579. package/dist/test/integration/templates.test.js.map +0 -1
  580. package/dist/test/integration/tenant-export.test.d.ts +0 -1
  581. package/dist/test/integration/tenant-export.test.js +0 -85
  582. package/dist/test/integration/tenant-export.test.js.map +0 -1
  583. package/dist/test/unit/attention.test.d.ts +0 -1
  584. package/dist/test/unit/attention.test.js +0 -109
  585. package/dist/test/unit/attention.test.js.map +0 -1
  586. package/dist/test/unit/automation.test.d.ts +0 -1
  587. package/dist/test/unit/automation.test.js +0 -104
  588. package/dist/test/unit/automation.test.js.map +0 -1
  589. package/dist/test/unit/categories.test.d.ts +0 -1
  590. package/dist/test/unit/categories.test.js +0 -38
  591. package/dist/test/unit/categories.test.js.map +0 -1
  592. package/dist/test/unit/engine/conditions.test.d.ts +0 -1
  593. package/dist/test/unit/engine/conditions.test.js +0 -23
  594. package/dist/test/unit/engine/conditions.test.js.map +0 -1
  595. package/dist/test/unit/engine/coverage.test.d.ts +0 -1
  596. package/dist/test/unit/engine/coverage.test.js +0 -106
  597. package/dist/test/unit/engine/coverage.test.js.map +0 -1
  598. package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
  599. package/dist/test/unit/engine/fallback-html.test.js +0 -78
  600. package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
  601. package/dist/test/unit/engine/paths.test.d.ts +0 -1
  602. package/dist/test/unit/engine/paths.test.js +0 -33
  603. package/dist/test/unit/engine/paths.test.js.map +0 -1
  604. package/dist/test/unit/engine/resolve.test.d.ts +0 -1
  605. package/dist/test/unit/engine/resolve.test.js +0 -157
  606. package/dist/test/unit/engine/resolve.test.js.map +0 -1
  607. package/dist/test/unit/engine/tokens.test.d.ts +0 -1
  608. package/dist/test/unit/engine/tokens.test.js +0 -26
  609. package/dist/test/unit/engine/tokens.test.js.map +0 -1
  610. package/dist/test/unit/events.test.d.ts +0 -1
  611. package/dist/test/unit/events.test.js +0 -13
  612. package/dist/test/unit/events.test.js.map +0 -1
  613. package/dist/test/unit/metrics.test.d.ts +0 -1
  614. package/dist/test/unit/metrics.test.js +0 -108
  615. package/dist/test/unit/metrics.test.js.map +0 -1
  616. package/dist/test/unit/notify.test.d.ts +0 -1
  617. package/dist/test/unit/notify.test.js +0 -49
  618. package/dist/test/unit/notify.test.js.map +0 -1
  619. package/dist/test/unit/period.test.d.ts +0 -1
  620. package/dist/test/unit/period.test.js +0 -61
  621. package/dist/test/unit/period.test.js.map +0 -1
  622. package/dist/test/unit/permissions.test.d.ts +0 -1
  623. package/dist/test/unit/permissions.test.js +0 -27
  624. package/dist/test/unit/permissions.test.js.map +0 -1
  625. package/dist/test/unit/provider.test.d.ts +0 -1
  626. package/dist/test/unit/provider.test.js +0 -47
  627. package/dist/test/unit/provider.test.js.map +0 -1
  628. package/dist/test/unit/realtime.test.d.ts +0 -1
  629. package/dist/test/unit/realtime.test.js +0 -13
  630. package/dist/test/unit/realtime.test.js.map +0 -1
  631. package/dist/test/unit/render.test.d.ts +0 -1
  632. package/dist/test/unit/render.test.js +0 -23
  633. package/dist/test/unit/render.test.js.map +0 -1
  634. package/dist/test/unit/retention.test.d.ts +0 -1
  635. package/dist/test/unit/retention.test.js +0 -66
  636. package/dist/test/unit/retention.test.js.map +0 -1
  637. package/dist/test/unit/send-batch.test.d.ts +0 -1
  638. package/dist/test/unit/send-batch.test.js +0 -46
  639. package/dist/test/unit/send-batch.test.js.map +0 -1
  640. package/dist/test/unit/send.test.d.ts +0 -1
  641. package/dist/test/unit/send.test.js +0 -120
  642. package/dist/test/unit/send.test.js.map +0 -1
  643. package/dist/test/unit/smoke.test.d.ts +0 -1
  644. package/dist/test/unit/smoke.test.js +0 -10
  645. package/dist/test/unit/smoke.test.js.map +0 -1
  646. package/dist/test/unit/status.test.d.ts +0 -1
  647. package/dist/test/unit/status.test.js +0 -16
  648. package/dist/test/unit/status.test.js.map +0 -1
  649. package/dist/test/unit/templates.test.d.ts +0 -1
  650. package/dist/test/unit/templates.test.js +0 -48
  651. package/dist/test/unit/templates.test.js.map +0 -1
  652. package/dist/test/unit/tenant-export.test.d.ts +0 -1
  653. package/dist/test/unit/tenant-export.test.js +0 -59
  654. package/dist/test/unit/tenant-export.test.js.map +0 -1
  655. package/dist/test/unit/webhook-secret.test.d.ts +0 -1
  656. package/dist/test/unit/webhook-secret.test.js +0 -43
  657. package/dist/test/unit/webhook-secret.test.js.map +0 -1
  658. package/frontend-vue/app/components/AttentionCard.vue +0 -78
  659. package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
  660. package/frontend-vue/app/components/ProviderChip.vue +0 -39
  661. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
  662. package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
  663. package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
  664. package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
  665. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
  666. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
  667. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
  668. package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
  669. package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
  670. package/src/antelope-modules.ts +0 -48
  671. package/src/test/antelope.test.ts +0 -190
  672. package/src/test/helpers/http.ts +0 -23
  673. package/src/test/helpers/owner.ts +0 -37
  674. package/src/test/integration/events.test.ts +0 -159
  675. package/src/test/integration/retention.test.ts +0 -363
  676. package/src/test/integration/sends.test.ts +0 -233
  677. package/src/test/integration/settings.test.ts +0 -128
  678. package/src/test/integration/setup.test.ts +0 -35
  679. package/src/test/integration/templates.test.ts +0 -272
  680. package/src/test/integration/tenant-export.test.ts +0 -125
  681. package/src/test/unit/attention.test.ts +0 -129
  682. package/src/test/unit/automation.test.ts +0 -130
  683. package/src/test/unit/categories.test.ts +0 -51
  684. package/src/test/unit/engine/conditions.test.ts +0 -35
  685. package/src/test/unit/engine/coverage.test.ts +0 -118
  686. package/src/test/unit/engine/fallback-html.test.ts +0 -86
  687. package/src/test/unit/engine/paths.test.ts +0 -39
  688. package/src/test/unit/engine/resolve.test.ts +0 -176
  689. package/src/test/unit/engine/tokens.test.ts +0 -26
  690. package/src/test/unit/events.test.ts +0 -19
  691. package/src/test/unit/metrics.test.ts +0 -170
  692. package/src/test/unit/notify.test.ts +0 -71
  693. package/src/test/unit/period.test.ts +0 -91
  694. package/src/test/unit/permissions.test.ts +0 -30
  695. package/src/test/unit/provider.test.ts +0 -57
  696. package/src/test/unit/realtime.test.ts +0 -11
  697. package/src/test/unit/render.test.ts +0 -23
  698. package/src/test/unit/retention.test.ts +0 -91
  699. package/src/test/unit/send-batch.test.ts +0 -72
  700. package/src/test/unit/send.test.ts +0 -161
  701. package/src/test/unit/smoke.test.ts +0 -8
  702. package/src/test/unit/status.test.ts +0 -14
  703. package/src/test/unit/templates.test.ts +0 -52
  704. package/src/test/unit/tenant-export.test.ts +0 -88
  705. package/src/test/unit/webhook-secret.test.ts +0 -51
@@ -1,128 +0,0 @@
1
- import { expect } from "chai";
2
- import { authorizedClient } from "../helpers/http";
3
- import { ensureOwnerSession } from "../helpers/owner";
4
-
5
- const HTTP_OK = 200;
6
- const HTTP_BAD_REQUEST = 400;
7
- const DEFAULT_CATEGORY_COUNT = 5;
8
- const DEFAULT_RETENTION_DAYS = 90;
9
- const UPDATED_RETENTION_DAYS = 30;
10
- const INVALID_RETENTION_DAYS = 0;
11
-
12
- describe("[integration] settings", () => {
13
- it("returns defaults, accepts an update and rejects an invalid retention", async () => {
14
- const session = await ensureOwnerSession();
15
- const client = authorizedClient(session.accessToken);
16
- const initial = await client.get("/api/mailing/settings");
17
- expect(initial.status).to.equal(HTTP_OK);
18
- expect(initial.data.fallbackLocale).to.equal("en");
19
- expect(initial.data.logRetentionDays).to.equal(DEFAULT_RETENTION_DAYS);
20
- expect(initial.data.categories).to.have.length(DEFAULT_CATEGORY_COUNT);
21
-
22
- const update = await client.post("/api/mailing/settings", {
23
- ...initial.data,
24
- fallbackLocale: "fr",
25
- logRetentionDays: UPDATED_RETENTION_DAYS,
26
- blockOnMissingVariables: true,
27
- });
28
- expect(update.status).to.equal(HTTP_OK);
29
- const after = await client.get("/api/mailing/settings");
30
- expect(after.data.fallbackLocale).to.equal("fr");
31
- expect(after.data.blockOnMissingVariables).to.equal(true);
32
-
33
- const invalid = await client.post("/api/mailing/settings", {
34
- ...after.data,
35
- logRetentionDays: INVALID_RETENTION_DAYS,
36
- });
37
- expect(invalid.status).to.equal(HTTP_BAD_REQUEST);
38
-
39
- // Blanking the secret would leave the unauthenticated event endpoint with
40
- // no gate, so the write has to be refused rather than silently accepted.
41
- const blankSecret = await client.post("/api/mailing/settings", {
42
- ...after.data,
43
- webhookSecret: "",
44
- });
45
- expect(blankSecret.status).to.equal(HTTP_BAD_REQUEST);
46
-
47
- const restored = await client.post("/api/mailing/settings", initial.data);
48
- expect(restored.status).to.equal(HTTP_OK);
49
- });
50
- });
51
-
52
- const CONCURRENT_READERS = 5;
53
- const LEGACY_RETENTION_DAYS = 45;
54
- const LEGACY_SECRET = "l".repeat(48);
55
-
56
- async function loadSettingsModules() {
57
- const { GetModel } =
58
- await import("@antelopejs/interface-database-decorators");
59
- const { SettingsModel } = await import("../../db");
60
- const settings = await import("../../services/settings");
61
- return {
62
- ...settings,
63
- rowsOf: (tenantId: string) => GetModel(SettingsModel, tenantId).getAll(),
64
- insertLegacyRow: (tenantId: string) =>
65
- GetModel(SettingsModel, tenantId).insert({
66
- fallbackLocale: "en",
67
- logRetentionDays: LEGACY_RETENTION_DAYS,
68
- blockOnMissingVariables: false,
69
- senderName: "",
70
- senderEmail: "",
71
- replyTo: "",
72
- json_categories: "[]",
73
- webhookSecret: LEGACY_SECRET,
74
- }),
75
- };
76
- }
77
-
78
- describe("[integration] settings row per tenant", () => {
79
- // Every request path reads the settings, so a tenant's first reads arrive
80
- // together. Each used to insert its own default row with its own webhook
81
- // secret; the row is now keyed by the tenant id so only one can land.
82
- it("creates a single row when first readers race", async () => {
83
- const { getSettings, rowsOf } = await loadSettingsModules();
84
- const tenantId = `settings-race-${Date.now()}`;
85
-
86
- const reads = await Promise.all(
87
- Array.from({ length: CONCURRENT_READERS }, () => getSettings(tenantId)),
88
- );
89
-
90
- const rows = await rowsOf(tenantId);
91
- expect(rows).to.have.lengthOf(1);
92
- expect(rows[0]?._id).to.equal(tenantId);
93
- for (const read of reads) {
94
- expect(read.webhookSecret).to.equal(rows[0]?.webhookSecret);
95
- }
96
- });
97
-
98
- it("keeps reading and updating a row written before the key existed", async () => {
99
- const { getSettings, saveSettings, rowsOf, insertLegacyRow } =
100
- await loadSettingsModules();
101
- const tenantId = `settings-legacy-${Date.now()}`;
102
- await insertLegacyRow(tenantId);
103
-
104
- const read = await getSettings(tenantId);
105
- expect(read.webhookSecret).to.equal(LEGACY_SECRET);
106
- await saveSettings(tenantId, {
107
- ...read,
108
- logRetentionDays: UPDATED_RETENTION_DAYS,
109
- });
110
-
111
- const rows = await rowsOf(tenantId);
112
- expect(rows).to.have.lengthOf(1);
113
- expect(rows[0]?.logRetentionDays).to.equal(UPDATED_RETENTION_DAYS);
114
- });
115
-
116
- it("saves a tenant's first settings under the tenant id", async () => {
117
- const { getSettings, saveSettings, rowsOf } = await loadSettingsModules();
118
- const tenantId = `settings-first-save-${Date.now()}`;
119
- const values = await getSettings(`settings-template-${Date.now()}`);
120
-
121
- await saveSettings(tenantId, { ...values, webhookSecret: LEGACY_SECRET });
122
-
123
- const rows = await rowsOf(tenantId);
124
- expect(rows).to.have.lengthOf(1);
125
- expect(rows[0]?._id).to.equal(tenantId);
126
- expect(rows[0]?.webhookSecret).to.equal(LEGACY_SECRET);
127
- });
128
- });
@@ -1,35 +0,0 @@
1
- import { createClient } from "../helpers/http";
2
-
3
- const READY_TIMEOUT_MS = 30_000;
4
- const POLL_INTERVAL_MS = 100;
5
- const HTTP_OK = 200;
6
-
7
- function delay(ms: number): Promise<void> {
8
- return new Promise((resolve) => setTimeout(resolve, ms));
9
- }
10
-
11
- async function waitFor(
12
- description: string,
13
- check: () => Promise<boolean>,
14
- ): Promise<void> {
15
- const deadline = Date.now() + READY_TIMEOUT_MS;
16
- while (Date.now() < deadline) {
17
- if (await check()) return;
18
- await delay(POLL_INTERVAL_MS);
19
- }
20
- throw new Error(`Timed out waiting for ${description}`);
21
- }
22
-
23
- before(async function () {
24
- this.timeout(READY_TIMEOUT_MS + 5_000);
25
-
26
- const client = createClient();
27
- await waitFor("the DMS runtime to accept onboarding calls", async () => {
28
- try {
29
- const response = await client.get("/api/onboarding/informations");
30
- return response.status === HTTP_OK;
31
- } catch {
32
- return false;
33
- }
34
- });
35
- });
@@ -1,272 +0,0 @@
1
- import { expect } from "chai";
2
- import { authorizedClient } from "../helpers/http";
3
- import { ensureOwnerSession } from "../helpers/owner";
4
-
5
- const HTTP_OK = 200;
6
- const HTTP_BAD_REQUEST = 400;
7
- const HTTP_NOT_FOUND = 404;
8
- const HTTP_CONFLICT = 409;
9
- const TABLE = "/api/mailing/tables/templates";
10
-
11
- interface TemplateRow {
12
- _id: string;
13
- slug: string;
14
- status: string;
15
- locales: string;
16
- }
17
-
18
- interface BlockRow {
19
- type: string;
20
- }
21
-
22
- describe("[integration] templates table", () => {
23
- it("creates a blank template with only a footer and lists it", async () => {
24
- const session = await ensureOwnerSession();
25
- const client = authorizedClient(session.accessToken);
26
- const created = await client.post(`${TABLE}/new`, {
27
- slug: "order-confirmed",
28
- name: "Order confirmed",
29
- category: "orders",
30
- });
31
- expect(created.status, JSON.stringify(created.data)).to.equal(HTTP_OK);
32
-
33
- const list = await client.get(`${TABLE}/list`);
34
- expect(list.status).to.equal(HTTP_OK);
35
- const row = list.data.results.find(
36
- (item: TemplateRow) => item.slug === "order-confirmed",
37
- ) as TemplateRow;
38
- expect(row.status).to.equal("draft");
39
- expect(row.locales).to.equal("en");
40
-
41
- const content = await client.get(
42
- `/api/mailing/templates/${row._id}/content`,
43
- );
44
- expect(content.status).to.equal(HTTP_OK);
45
- expect(
46
- content.data.content.locales.en.blocks.map(
47
- (block: BlockRow) => block.type,
48
- ),
49
- ).to.deep.equal(["footer"]);
50
- expect(content.data.content.locales.en.subject).to.equal("");
51
- expect(content.data.variables).to.deep.equal([]);
52
- });
53
-
54
- it("creates from an existing template by copying its content", async () => {
55
- const session = await ensureOwnerSession();
56
- const client = authorizedClient(session.accessToken);
57
- const source = await client.post(`${TABLE}/new`, {
58
- slug: "copy-source",
59
- name: "Copy source",
60
- category: "orders",
61
- });
62
- const sourceId = source.data[0] as string;
63
- await client.post(`/api/mailing/templates/${sourceId}/content`, {
64
- content: {
65
- locales: {
66
- en: {
67
- subject: "Hello {{customer.firstName}}",
68
- preheader: "",
69
- blocks: [],
70
- },
71
- },
72
- },
73
- });
74
- await client.post(`/api/mailing/templates/${sourceId}/variables`, {
75
- variables: [
76
- { path: "customer.firstName", type: "string", required: true },
77
- ],
78
- });
79
-
80
- const copy = await client.post(`${TABLE}/new`, {
81
- slug: "copy-target",
82
- name: "Copy target",
83
- sourceTemplateId: sourceId,
84
- });
85
- expect(copy.status, JSON.stringify(copy.data)).to.equal(HTTP_OK);
86
- const content = await client.get(
87
- `/api/mailing/templates/${copy.data[0] as string}/content`,
88
- );
89
- expect(content.data.content.locales.en.subject).to.equal(
90
- "Hello {{customer.firstName}}",
91
- );
92
- expect(content.data.variables).to.deep.equal([
93
- { path: "customer.firstName", type: "string", required: true },
94
- ]);
95
- });
96
-
97
- it("keeps the slug when only the name and category are edited", async () => {
98
- const session = await ensureOwnerSession();
99
- const client = authorizedClient(session.accessToken);
100
- const created = await client.post(`${TABLE}/new`, {
101
- slug: "keeps-slug",
102
- name: "Keeps slug",
103
- category: "orders",
104
- });
105
- const id = created.data[0] as string;
106
- // The data-api `edit` route replaces the writable fields: a body missing
107
- // one nulls it. The editor must send the whole writable set.
108
- const edited = await client.put(`${TABLE}/edit?id=${id}`, {
109
- slug: "keeps-slug",
110
- name: "Renamed",
111
- category: "billing",
112
- });
113
- expect(edited.status, JSON.stringify(edited.data)).to.equal(HTTP_OK);
114
- const row = await client.get(`${TABLE}/get?id=${id}`);
115
- expect(row.data.slug).to.equal("keeps-slug");
116
- expect(row.data.name).to.equal("Renamed");
117
- expect(row.data.category).to.equal("billing");
118
- });
119
-
120
- // Sends resolve a template by slug and only the create paths check it is
121
- // free, so an edit must neither move it nor, by omitting it, clear it.
122
- it("refuses a slug change on edit and keeps the slug when it is omitted", async () => {
123
- const session = await ensureOwnerSession();
124
- const client = authorizedClient(session.accessToken);
125
- const created = await client.post(`${TABLE}/new`, {
126
- slug: "fixed-slug",
127
- name: "Fixed slug",
128
- category: "orders",
129
- });
130
- const id = created.data[0] as string;
131
-
132
- const moved = await client.put(`${TABLE}/edit?id=${id}`, {
133
- slug: "order-confirmed",
134
- name: "Fixed slug",
135
- category: "orders",
136
- });
137
- expect(moved.status).to.equal(HTTP_CONFLICT);
138
- expect(JSON.stringify(moved.data)).to.contain("slug_immutable");
139
-
140
- const omitted = await client.put(`${TABLE}/edit?id=${id}`, {
141
- name: "Renamed without slug",
142
- category: "orders",
143
- });
144
- expect(omitted.status, JSON.stringify(omitted.data)).to.equal(HTTP_OK);
145
- const row = await client.get(`${TABLE}/get?id=${id}`);
146
- expect(row.data.slug).to.equal("fixed-slug");
147
- expect(row.data.name).to.equal("Renamed without slug");
148
-
149
- const { GetModel } =
150
- await import("@antelopejs/interface-database-decorators");
151
- const { DEFAULT_TENANT_ID } =
152
- await import("@antelopejs/interface-dms/constants");
153
- const { TemplateModel } = await import("../../db");
154
- await GetModel(TemplateModel, DEFAULT_TENANT_ID).delete(id);
155
- });
156
-
157
- it("refuses an unknown source template", async () => {
158
- const session = await ensureOwnerSession();
159
- const client = authorizedClient(session.accessToken);
160
- const created = await client.post(`${TABLE}/new`, {
161
- slug: "copy-missing",
162
- name: "Copy missing",
163
- sourceTemplateId: "does-not-exist",
164
- });
165
- expect(created.status).to.equal(HTTP_NOT_FOUND);
166
- expect(JSON.stringify(created.data)).to.contain("template_not_found");
167
- });
168
-
169
- it("rejects a duplicate slug", async () => {
170
- const session = await ensureOwnerSession();
171
- const client = authorizedClient(session.accessToken);
172
- const dup = await client.post(`${TABLE}/new`, {
173
- slug: "order-confirmed",
174
- name: "Again",
175
- category: "orders",
176
- });
177
- expect(dup.status).to.be.at.least(HTTP_BAD_REQUEST);
178
- });
179
- });
180
-
181
- describe("[integration] template content and status", () => {
182
- async function createTemplate(
183
- client: ReturnType<typeof authorizedClient>,
184
- slug: string,
185
- ): Promise<string> {
186
- const created = await client.post(`${TABLE}/new`, {
187
- slug,
188
- name: slug,
189
- category: "orders",
190
- });
191
- expect(created.status, JSON.stringify(created.data)).to.equal(HTTP_OK);
192
- return created.data[0] as string;
193
- }
194
-
195
- it("replaces the content, publishes and duplicates", async () => {
196
- const session = await ensureOwnerSession();
197
- const client = authorizedClient(session.accessToken);
198
- const id = await createTemplate(client, "welcome");
199
- const before = await client.get(`/api/mailing/templates/${id}/content`);
200
- const content = before.data.content;
201
- content.locales.en.subject = "Welcome {{customer.firstName}}";
202
- const saved = await client.post(`/api/mailing/templates/${id}/content`, {
203
- content,
204
- });
205
- expect(saved.status, JSON.stringify(saved.data)).to.equal(HTTP_OK);
206
- expect(saved.data.saved).to.equal(true);
207
-
208
- const publish = await client.post(
209
- `/api/mailing/templates/${id}/publish`,
210
- {},
211
- );
212
- expect(publish.status).to.equal(HTTP_OK);
213
- const after = await client.get(`/api/mailing/templates/${id}/content`);
214
- expect(after.data.template.status).to.equal("live");
215
- expect(after.data.content.locales.en.subject).to.equal(
216
- "Welcome {{customer.firstName}}",
217
- );
218
-
219
- const variables = await client.post(
220
- `/api/mailing/templates/${id}/variables`,
221
- {
222
- variables: [
223
- { path: "customer.firstName", type: "string", required: false },
224
- ],
225
- },
226
- );
227
- expect(variables.status).to.equal(HTTP_OK);
228
- const testData = await client.post(
229
- `/api/mailing/templates/${id}/test-data`,
230
- { data: { customer: { firstName: "Sofie" } } },
231
- );
232
- expect(testData.status).to.equal(HTTP_OK);
233
-
234
- const duplicate = await client.post(
235
- `/api/mailing/templates/${id}/duplicate`,
236
- { slug: "welcome-copy", name: "Welcome copy" },
237
- );
238
- expect(duplicate.status, JSON.stringify(duplicate.data)).to.equal(HTTP_OK);
239
- const copy = await client.get(
240
- `/api/mailing/templates/${duplicate.data.id}/content`,
241
- );
242
- expect(copy.data.template.status).to.equal("draft");
243
- expect(copy.data.content.locales.en.subject).to.equal(
244
- "Welcome {{customer.firstName}}",
245
- );
246
- expect(copy.data.variables).to.have.length(1);
247
- });
248
-
249
- it("previews the current content with test data and reports missing variables", async () => {
250
- const session = await ensureOwnerSession();
251
- const client = authorizedClient(session.accessToken);
252
- // Looked up by slug rather than on the table's first page, which other
253
- // tests fill with their own templates.
254
- const { GetModel } =
255
- await import("@antelopejs/interface-database-decorators");
256
- const { DEFAULT_TENANT_ID } =
257
- await import("@antelopejs/interface-dms/constants");
258
- const { TemplateModel } = await import("../../db");
259
- const welcome = await GetModel(TemplateModel, DEFAULT_TENANT_ID).getBySlug(
260
- "welcome",
261
- );
262
- const id = welcome?._id as string;
263
- const preview = await client.post(`/api/mailing/templates/${id}/preview`, {
264
- locale: "en",
265
- data: {},
266
- });
267
- expect(preview.status, JSON.stringify(preview.data)).to.equal(HTTP_OK);
268
- expect(preview.data.html).to.include("<!DOCTYPE html>");
269
- expect(preview.data.missing).to.include("customer.firstName");
270
- expect(preview.data.subject).to.equal("Welcome {{customer.firstName}}");
271
- });
272
- });
@@ -1,125 +0,0 @@
1
- import type { Readable } from "node:stream";
2
- import { DEFAULT_TENANT_ID } from "@antelopejs/interface-dms/constants";
3
- import type { TenantExportArchive } from "@antelopejs/interface-dms/hooks";
4
- import { expect } from "chai";
5
- import {
6
- contributeTenantExport,
7
- type ExportedCollection,
8
- exportedCollections,
9
- jsonArrayChunks,
10
- } from "../../hooks/tenant-export";
11
- import { authorizedClient } from "../helpers/http";
12
- import { ensureOwnerSession } from "../helpers/owner";
13
-
14
- const HTTP_OK = 200;
15
- const TABLE = "/api/mailing/tables/templates";
16
- const TEMPLATES_ENTRY = "templates.json";
17
- const SETTINGS_ENTRY = "settings.json";
18
- const EXPORTED_SLUG = "exported-template";
19
- const SINGLE_ROW_PAGE = 1;
20
- const LIST_ALL = 100;
21
-
22
- interface TemplateRow {
23
- slug: string;
24
- }
25
-
26
- class CollectingArchive implements TenantExportArchive {
27
- readonly entries = new Map<string, string>();
28
-
29
- addJson(path: string, data: unknown): Promise<void> {
30
- this.entries.set(path, JSON.stringify(data));
31
- return Promise.resolve();
32
- }
33
-
34
- addFile(path: string): Promise<void> {
35
- return Promise.reject(new Error(`Unexpected file entry "${path}"`));
36
- }
37
-
38
- async addStream(path: string, stream: Readable): Promise<void> {
39
- let text = "";
40
- for await (const chunk of stream) text += String(chunk);
41
- this.entries.set(path, text);
42
- }
43
- }
44
-
45
- function parsed<T>(archive: CollectingArchive, entry: string): T {
46
- return JSON.parse(archive.entries.get(entry) as string) as T;
47
- }
48
-
49
- describe("[integration] tenant export", () => {
50
- it("streams the mailing data into the archive instead of one blob", async () => {
51
- const session = await ensureOwnerSession();
52
- const client = authorizedClient(session.accessToken);
53
- const created = await client.post(`${TABLE}/new`, {
54
- slug: EXPORTED_SLUG,
55
- name: "Exported template",
56
- category: "orders",
57
- });
58
- expect(created.status, JSON.stringify(created.data)).to.equal(HTTP_OK);
59
-
60
- const archive = new CollectingArchive();
61
- const contribution = await contributeTenantExport(
62
- DEFAULT_TENANT_ID,
63
- archive,
64
- new AbortController().signal,
65
- );
66
-
67
- expect(contribution).to.equal(undefined);
68
- expect([...archive.entries.keys()]).to.have.members([
69
- TEMPLATES_ENTRY,
70
- SETTINGS_ENTRY,
71
- ]);
72
- const templates = parsed<TemplateRow[]>(archive, TEMPLATES_ENTRY);
73
- expect(templates.map((template) => template.slug)).to.include(
74
- EXPORTED_SLUG,
75
- );
76
- expect(archive.entries.get(SETTINGS_ENTRY)).to.not.contain("webhookSecret");
77
- });
78
-
79
- it("stops contributing when the export is aborted", async () => {
80
- const archive = new CollectingArchive();
81
- const controller = new AbortController();
82
- controller.abort();
83
-
84
- let aborted = false;
85
- try {
86
- await contributeTenantExport(
87
- DEFAULT_TENANT_ID,
88
- archive,
89
- controller.signal,
90
- );
91
- } catch {
92
- aborted = true;
93
- }
94
-
95
- expect(aborted).to.equal(true);
96
- expect(archive.entries.size).to.equal(0);
97
- });
98
-
99
- it("pages the reads without skipping or repeating a row", async () => {
100
- const session = await ensureOwnerSession();
101
- const client = authorizedClient(session.accessToken);
102
- // The list answers 10 rows unless told otherwise; the export reads them all.
103
- const list = await client.get(`${TABLE}/list?limit=${LIST_ALL}`);
104
- expect(list.status, JSON.stringify(list.data)).to.equal(HTTP_OK);
105
- const slugs = (list.data.results as TemplateRow[]).map(
106
- (template) => template.slug,
107
- );
108
- const [templates] = exportedCollections(DEFAULT_TENANT_ID);
109
-
110
- let text = "";
111
- for await (const chunk of jsonArrayChunks(
112
- (templates as ExportedCollection).load,
113
- new AbortController().signal,
114
- SINGLE_ROW_PAGE,
115
- )) {
116
- text += chunk;
117
- }
118
-
119
- const paged = (JSON.parse(text) as TemplateRow[]).map(
120
- (template) => template.slug,
121
- );
122
- expect(paged).to.have.members(slugs);
123
- expect(new Set(paged).size).to.equal(paged.length);
124
- });
125
- });
@@ -1,129 +0,0 @@
1
- import { expect } from "chai";
2
- import type { MailingSend, MailingTemplate } from "../../db";
3
- import {
4
- type AttentionItem,
5
- buildAttentionItems,
6
- } from "../../services/attention";
7
-
8
- const send = (isTest: boolean): MailingSend =>
9
- ({ status: "sent", templateSlug: "welcome", isTest }) as MailingSend;
10
-
11
- const NO_TEMPLATES: MailingTemplate[] = [];
12
-
13
- const findExcluded = (items: AttentionItem[]) =>
14
- items.find((item) => item.id === "test-only-window");
15
-
16
- describe("[unit] services/attention test-only window", () => {
17
- it("explains the empty figures when every send in the window is a test", () => {
18
- const items = buildAttentionItems({
19
- templates: NO_TEMPLATES,
20
- sends: [],
21
- excluded: [send(true), send(true)],
22
- tracking: null,
23
- });
24
- const item = findExcluded(items);
25
- expect(item).to.not.equal(undefined);
26
- expect(item).to.deep.include({
27
- tone: "neutral",
28
- title: "$dms_mailing.attention.test_only_window.title",
29
- description: "$dms_mailing.attention.test_only_window.description",
30
- });
31
- expect(item?.params).to.deep.equal({ count: 2 });
32
- });
33
-
34
- it("stays quiet when the window holds real sends too", () => {
35
- const items = buildAttentionItems({
36
- templates: NO_TEMPLATES,
37
- sends: [send(false)],
38
- excluded: [send(true)],
39
- tracking: null,
40
- });
41
- expect(findExcluded(items)).to.equal(undefined);
42
- });
43
-
44
- it("stays quiet when the window is genuinely empty", () => {
45
- const items = buildAttentionItems({
46
- templates: NO_TEMPLATES,
47
- sends: [],
48
- excluded: [],
49
- tracking: null,
50
- });
51
- expect(findExcluded(items)).to.equal(undefined);
52
- });
53
- });
54
-
55
- const findUntracked = (items: AttentionItem[]) =>
56
- items.find((item) => item.id === "untracked-rates");
57
-
58
- const TRACKS_NOTHING = {
59
- name: "smtp",
60
- openTracking: false,
61
- clickTracking: false,
62
- };
63
- const TRACKS_EVERYTHING = {
64
- name: "postmark",
65
- openTracking: true,
66
- clickTracking: true,
67
- };
68
-
69
- describe("[unit] services/attention untracked rates", () => {
70
- it("explains the flat rates when the provider reports neither opens nor clicks", () => {
71
- const items = buildAttentionItems({
72
- templates: NO_TEMPLATES,
73
- sends: [send(false)],
74
- excluded: [],
75
- tracking: TRACKS_NOTHING,
76
- });
77
- const item = findUntracked(items);
78
- expect(item).to.not.equal(undefined);
79
- expect(item).to.deep.include({
80
- tone: "neutral",
81
- title: "$dms_mailing.attention.untracked_rates.title",
82
- description: "$dms_mailing.attention.untracked_rates.description",
83
- });
84
- expect(item?.params).to.deep.equal({ provider: "smtp" });
85
- });
86
-
87
- it("stays quiet when the provider tracks both", () => {
88
- const items = buildAttentionItems({
89
- templates: NO_TEMPLATES,
90
- sends: [send(false)],
91
- excluded: [],
92
- tracking: TRACKS_EVERYTHING,
93
- });
94
- expect(findUntracked(items)).to.equal(undefined);
95
- });
96
-
97
- it("still fires when only one of the two is tracked, since a rate stays flat", () => {
98
- const items = buildAttentionItems({
99
- templates: NO_TEMPLATES,
100
- sends: [send(false)],
101
- excluded: [],
102
- tracking: { ...TRACKS_NOTHING, openTracking: true },
103
- });
104
- expect(findUntracked(items)).to.not.equal(undefined);
105
- });
106
-
107
- // The notice explains figures the window actually shows. With nothing sent
108
- // there are no rates on screen to explain, and it would just be noise.
109
- it("stays quiet when the window holds no send at all", () => {
110
- const items = buildAttentionItems({
111
- templates: NO_TEMPLATES,
112
- sends: [],
113
- excluded: [],
114
- tracking: TRACKS_NOTHING,
115
- });
116
- expect(findUntracked(items)).to.equal(undefined);
117
- });
118
-
119
- // The provider may be unreachable; that is the provider card's business.
120
- it("stays quiet when the provider capabilities are unknown", () => {
121
- const items = buildAttentionItems({
122
- templates: NO_TEMPLATES,
123
- sends: [send(false)],
124
- excluded: [],
125
- tracking: null,
126
- });
127
- expect(findUntracked(items)).to.equal(undefined);
128
- });
129
- });