@antelopejs/dms-mailing 0.2.2
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.
- package/LICENSE +190 -0
- package/README.md +162 -0
- package/dist/antelope-modules.d.ts +32 -0
- package/dist/antelope-modules.js +36 -0
- package/dist/antelope-modules.js.map +1 -0
- package/dist/automation/email-event-trigger.d.ts +16 -0
- package/dist/automation/email-event-trigger.js +70 -0
- package/dist/automation/email-event-trigger.js.map +1 -0
- package/dist/automation/index.d.ts +4 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/send-template-action.d.ts +13 -0
- package/dist/automation/send-template-action.js +71 -0
- package/dist/automation/send-template-action.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +32 -0
- package/dist/constants.js.map +1 -0
- package/dist/crons/index.d.ts +1 -0
- package/dist/crons/index.js +18 -0
- package/dist/crons/index.js.map +1 -0
- package/dist/crons/retention.d.ts +18 -0
- package/dist/crons/retention.js +107 -0
- package/dist/crons/retention.js.map +1 -0
- package/dist/data/body.d.ts +1 -0
- package/dist/data/body.js +18 -0
- package/dist/data/body.js.map +1 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +20 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/sends-table.d.ts +28 -0
- package/dist/data/sends-table.js +161 -0
- package/dist/data/sends-table.js.map +1 -0
- package/dist/data/templates-table.d.ts +31 -0
- package/dist/data/templates-table.js +173 -0
- package/dist/data/templates-table.js.map +1 -0
- package/dist/data-types/categories-type.d.ts +35 -0
- package/dist/data-types/categories-type.js +37 -0
- package/dist/data-types/categories-type.js.map +1 -0
- package/dist/data-types/category-type.d.ts +8 -0
- package/dist/data-types/category-type.js +43 -0
- package/dist/data-types/category-type.js.map +1 -0
- package/dist/data-types/index.d.ts +2 -0
- package/dist/data-types/index.js +19 -0
- package/dist/data-types/index.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.js +19 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/models/index.d.ts +4 -0
- package/dist/db/models/index.js +21 -0
- package/dist/db/models/index.js.map +1 -0
- package/dist/db/models/send-events.model.d.ts +27 -0
- package/dist/db/models/send-events.model.js +35 -0
- package/dist/db/models/send-events.model.js.map +1 -0
- package/dist/db/models/sends.model.d.ts +32 -0
- package/dist/db/models/sends.model.js +115 -0
- package/dist/db/models/sends.model.js.map +1 -0
- package/dist/db/models/settings.model.d.ts +23 -0
- package/dist/db/models/settings.model.js +13 -0
- package/dist/db/models/settings.model.js.map +1 -0
- package/dist/db/models/templates.model.d.ts +23 -0
- package/dist/db/models/templates.model.js +13 -0
- package/dist/db/models/templates.model.js.map +1 -0
- package/dist/db/tables/index.d.ts +4 -0
- package/dist/db/tables/index.js +21 -0
- package/dist/db/tables/index.js.map +1 -0
- package/dist/db/tables/send-events.table.d.ts +10 -0
- package/dist/db/tables/send-events.table.js +44 -0
- package/dist/db/tables/send-events.table.js.map +1 -0
- package/dist/db/tables/sends.table.d.ts +26 -0
- package/dist/db/tables/sends.table.js +113 -0
- package/dist/db/tables/sends.table.js.map +1 -0
- package/dist/db/tables/settings.table.d.ts +15 -0
- package/dist/db/tables/settings.table.js +68 -0
- package/dist/db/tables/settings.table.js.map +1 -0
- package/dist/db/tables/templates.table.d.ts +18 -0
- package/dist/db/tables/templates.table.js +81 -0
- package/dist/db/tables/templates.table.js.map +1 -0
- package/dist/engine/conditions.d.ts +2 -0
- package/dist/engine/conditions.js +20 -0
- package/dist/engine/conditions.js.map +1 -0
- package/dist/engine/coverage.d.ts +4 -0
- package/dist/engine/coverage.js +45 -0
- package/dist/engine/coverage.js.map +1 -0
- package/dist/engine/fallback-html.d.ts +4 -0
- package/dist/engine/fallback-html.js +84 -0
- package/dist/engine/fallback-html.js.map +1 -0
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/index.js +24 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/paths.d.ts +3 -0
- package/dist/engine/paths.js +32 -0
- package/dist/engine/paths.js.map +1 -0
- package/dist/engine/resolve.d.ts +4 -0
- package/dist/engine/resolve.js +139 -0
- package/dist/engine/resolve.js.map +1 -0
- package/dist/engine/tokens.d.ts +7 -0
- package/dist/engine/tokens.js +29 -0
- package/dist/engine/tokens.js.map +1 -0
- package/dist/engine/walk.d.ts +3 -0
- package/dist/engine/walk.js +14 -0
- package/dist/engine/walk.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +18 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/tenant-export.d.ts +14 -0
- package/dist/hooks/tenant-export.js +84 -0
- package/dist/hooks/tenant-export.js.map +1 -0
- package/dist/implementations/dms-mailing/index.d.ts +2 -0
- package/dist/implementations/dms-mailing/index.js +8 -0
- package/dist/implementations/dms-mailing/index.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/editor.d.ts +9 -0
- package/dist/pages/editor.js +30 -0
- package/dist/pages/editor.js.map +1 -0
- package/dist/pages/index.d.ts +7 -0
- package/dist/pages/index.js +24 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/module.d.ts +1 -0
- package/dist/pages/module.js +12 -0
- package/dist/pages/module.js.map +1 -0
- package/dist/pages/overview.d.ts +14 -0
- package/dist/pages/overview.js +92 -0
- package/dist/pages/overview.js.map +1 -0
- package/dist/pages/sends.d.ts +13 -0
- package/dist/pages/sends.js +125 -0
- package/dist/pages/sends.js.map +1 -0
- package/dist/pages/settings/form.d.ts +1 -0
- package/dist/pages/settings/form.js +68 -0
- package/dist/pages/settings/form.js.map +1 -0
- package/dist/pages/settings/index.d.ts +2 -0
- package/dist/pages/settings/index.js +19 -0
- package/dist/pages/settings/index.js.map +1 -0
- package/dist/pages/settings/page.d.ts +10 -0
- package/dist/pages/settings/page.js +35 -0
- package/dist/pages/settings/page.js.map +1 -0
- package/dist/pages/templates.d.ts +10 -0
- package/dist/pages/templates.js +140 -0
- package/dist/pages/templates.js.map +1 -0
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.js +37 -0
- package/dist/permissions.js.map +1 -0
- package/dist/quick-actions.d.ts +2 -0
- package/dist/quick-actions.js +18 -0
- package/dist/quick-actions.js.map +1 -0
- package/dist/realtime.d.ts +4 -0
- package/dist/realtime.js +44 -0
- package/dist/realtime.js.map +1 -0
- package/dist/routes/events.d.ts +9 -0
- package/dist/routes/events.js +63 -0
- package/dist/routes/events.js.map +1 -0
- package/dist/routes/index.d.ts +7 -0
- package/dist/routes/index.js +24 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/metrics.d.ts +54 -0
- package/dist/routes/metrics.js +235 -0
- package/dist/routes/metrics.js.map +1 -0
- package/dist/routes/period.d.ts +5 -0
- package/dist/routes/period.js +36 -0
- package/dist/routes/period.js.map +1 -0
- package/dist/routes/provider.d.ts +13 -0
- package/dist/routes/provider.js +41 -0
- package/dist/routes/provider.js.map +1 -0
- package/dist/routes/sends.d.ts +31 -0
- package/dist/routes/sends.js +178 -0
- package/dist/routes/sends.js.map +1 -0
- package/dist/routes/settings.d.ts +14 -0
- package/dist/routes/settings.js +72 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/routes/templates.d.ts +61 -0
- package/dist/routes/templates.js +230 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/services/attention.d.ts +23 -0
- package/dist/services/attention.js +104 -0
- package/dist/services/attention.js.map +1 -0
- package/dist/services/categories.d.ts +34 -0
- package/dist/services/categories.js +34 -0
- package/dist/services/categories.js.map +1 -0
- package/dist/services/events.d.ts +10 -0
- package/dist/services/events.js +89 -0
- package/dist/services/events.js.map +1 -0
- package/dist/services/metrics.d.ts +34 -0
- package/dist/services/metrics.js +103 -0
- package/dist/services/metrics.js.map +1 -0
- package/dist/services/notify.d.ts +20 -0
- package/dist/services/notify.js +63 -0
- package/dist/services/notify.js.map +1 -0
- package/dist/services/permissions.d.ts +2 -0
- package/dist/services/permissions.js +15 -0
- package/dist/services/permissions.js.map +1 -0
- package/dist/services/provider.d.ts +6 -0
- package/dist/services/provider.js +25 -0
- package/dist/services/provider.js.map +1 -0
- package/dist/services/render.d.ts +6 -0
- package/dist/services/render.js +21 -0
- package/dist/services/render.js.map +1 -0
- package/dist/services/send.d.ts +49 -0
- package/dist/services/send.js +259 -0
- package/dist/services/send.js.map +1 -0
- package/dist/services/settings.d.ts +4 -0
- package/dist/services/settings.js +61 -0
- package/dist/services/settings.js.map +1 -0
- package/dist/services/status.d.ts +3 -0
- package/dist/services/status.js +16 -0
- package/dist/services/status.js.map +1 -0
- package/dist/services/templates.d.ts +17 -0
- package/dist/services/templates.js +92 -0
- package/dist/services/templates.js.map +1 -0
- package/dist/test/antelope.test.d.ts +2 -0
- package/dist/test/antelope.test.js +179 -0
- package/dist/test/antelope.test.js.map +1 -0
- package/dist/test/helpers/http.d.ts +4 -0
- package/dist/test/helpers/http.js +28 -0
- package/dist/test/helpers/http.js.map +1 -0
- package/dist/test/helpers/owner.d.ts +5 -0
- package/dist/test/helpers/owner.js +30 -0
- package/dist/test/helpers/owner.js.map +1 -0
- package/dist/test/integration/events.test.d.ts +1 -0
- package/dist/test/integration/events.test.js +132 -0
- package/dist/test/integration/events.test.js.map +1 -0
- package/dist/test/integration/retention.test.d.ts +1 -0
- package/dist/test/integration/retention.test.js +291 -0
- package/dist/test/integration/retention.test.js.map +1 -0
- package/dist/test/integration/sends.test.d.ts +1 -0
- package/dist/test/integration/sends.test.js +157 -0
- package/dist/test/integration/sends.test.js.map +1 -0
- package/dist/test/integration/settings.test.d.ts +1 -0
- package/dist/test/integration/settings.test.js +45 -0
- package/dist/test/integration/settings.test.js.map +1 -0
- package/dist/test/integration/setup.test.d.ts +1 -0
- package/dist/test/integration/setup.test.js +32 -0
- package/dist/test/integration/setup.test.js.map +1 -0
- package/dist/test/integration/templates.test.d.ts +1 -0
- package/dist/test/integration/templates.test.js +167 -0
- package/dist/test/integration/templates.test.js.map +1 -0
- package/dist/test/integration/tenant-export.test.d.ts +1 -0
- package/dist/test/integration/tenant-export.test.js +85 -0
- package/dist/test/integration/tenant-export.test.js.map +1 -0
- package/dist/test/unit/attention.test.d.ts +1 -0
- package/dist/test/unit/attention.test.js +109 -0
- package/dist/test/unit/attention.test.js.map +1 -0
- package/dist/test/unit/automation.test.d.ts +1 -0
- package/dist/test/unit/automation.test.js +104 -0
- package/dist/test/unit/automation.test.js.map +1 -0
- package/dist/test/unit/categories.test.d.ts +1 -0
- package/dist/test/unit/categories.test.js +38 -0
- package/dist/test/unit/categories.test.js.map +1 -0
- package/dist/test/unit/engine/conditions.test.d.ts +1 -0
- package/dist/test/unit/engine/conditions.test.js +23 -0
- package/dist/test/unit/engine/conditions.test.js.map +1 -0
- package/dist/test/unit/engine/coverage.test.d.ts +1 -0
- package/dist/test/unit/engine/coverage.test.js +106 -0
- package/dist/test/unit/engine/coverage.test.js.map +1 -0
- package/dist/test/unit/engine/fallback-html.test.d.ts +1 -0
- package/dist/test/unit/engine/fallback-html.test.js +78 -0
- package/dist/test/unit/engine/fallback-html.test.js.map +1 -0
- package/dist/test/unit/engine/paths.test.d.ts +1 -0
- package/dist/test/unit/engine/paths.test.js +33 -0
- package/dist/test/unit/engine/paths.test.js.map +1 -0
- package/dist/test/unit/engine/resolve.test.d.ts +1 -0
- package/dist/test/unit/engine/resolve.test.js +157 -0
- package/dist/test/unit/engine/resolve.test.js.map +1 -0
- package/dist/test/unit/engine/tokens.test.d.ts +1 -0
- package/dist/test/unit/engine/tokens.test.js +26 -0
- package/dist/test/unit/engine/tokens.test.js.map +1 -0
- package/dist/test/unit/events.test.d.ts +1 -0
- package/dist/test/unit/events.test.js +13 -0
- package/dist/test/unit/events.test.js.map +1 -0
- package/dist/test/unit/metrics.test.d.ts +1 -0
- package/dist/test/unit/metrics.test.js +108 -0
- package/dist/test/unit/metrics.test.js.map +1 -0
- package/dist/test/unit/notify.test.d.ts +1 -0
- package/dist/test/unit/notify.test.js +49 -0
- package/dist/test/unit/notify.test.js.map +1 -0
- package/dist/test/unit/period.test.d.ts +1 -0
- package/dist/test/unit/period.test.js +44 -0
- package/dist/test/unit/period.test.js.map +1 -0
- package/dist/test/unit/provider.test.d.ts +1 -0
- package/dist/test/unit/provider.test.js +47 -0
- package/dist/test/unit/provider.test.js.map +1 -0
- package/dist/test/unit/realtime.test.d.ts +1 -0
- package/dist/test/unit/realtime.test.js +13 -0
- package/dist/test/unit/realtime.test.js.map +1 -0
- package/dist/test/unit/render.test.d.ts +1 -0
- package/dist/test/unit/render.test.js +23 -0
- package/dist/test/unit/render.test.js.map +1 -0
- package/dist/test/unit/retention.test.d.ts +1 -0
- package/dist/test/unit/retention.test.js +66 -0
- package/dist/test/unit/retention.test.js.map +1 -0
- package/dist/test/unit/send-batch.test.d.ts +1 -0
- package/dist/test/unit/send-batch.test.js +46 -0
- package/dist/test/unit/send-batch.test.js.map +1 -0
- package/dist/test/unit/send.test.d.ts +1 -0
- package/dist/test/unit/send.test.js +120 -0
- package/dist/test/unit/send.test.js.map +1 -0
- package/dist/test/unit/smoke.test.d.ts +1 -0
- package/dist/test/unit/smoke.test.js +10 -0
- package/dist/test/unit/smoke.test.js.map +1 -0
- package/dist/test/unit/status.test.d.ts +1 -0
- package/dist/test/unit/status.test.js +16 -0
- package/dist/test/unit/status.test.js.map +1 -0
- package/dist/test/unit/templates.test.d.ts +1 -0
- package/dist/test/unit/templates.test.js +48 -0
- package/dist/test/unit/templates.test.js.map +1 -0
- package/dist/test/unit/tenant-export.test.d.ts +1 -0
- package/dist/test/unit/tenant-export.test.js +59 -0
- package/dist/test/unit/tenant-export.test.js.map +1 -0
- package/dist/test/unit/webhook-secret.test.d.ts +1 -0
- package/dist/test/unit/webhook-secret.test.js +42 -0
- package/dist/test/unit/webhook-secret.test.js.map +1 -0
- package/dist/types/blocks.d.ts +2 -0
- package/dist/types/blocks.js +3 -0
- package/dist/types/blocks.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/resolved.d.ts +68 -0
- package/dist/types/resolved.js +3 -0
- package/dist/types/resolved.js.map +1 -0
- package/dist/types/sends.d.ts +14 -0
- package/dist/types/sends.js +19 -0
- package/dist/types/sends.js.map +1 -0
- package/dist/types/settings.d.ts +16 -0
- package/dist/types/settings.js +11 -0
- package/dist/types/settings.js.map +1 -0
- package/dist/validation/settings.schema.d.ts +63 -0
- package/dist/validation/settings.schema.js +29 -0
- package/dist/validation/settings.schema.js.map +1 -0
- package/dist/validation/templates.schema.d.ts +248 -0
- package/dist/validation/templates.schema.js +126 -0
- package/dist/validation/templates.schema.js.map +1 -0
- package/frontend-vue/.editorconfig +12 -0
- package/frontend-vue/.prettierrc +11 -0
- package/frontend-vue/app/components/AttentionCard.vue +78 -0
- package/frontend-vue/app/components/CategoriesInput.vue +126 -0
- package/frontend-vue/app/components/CategoryInput.vue +67 -0
- package/frontend-vue/app/components/Editor.vue +63 -0
- package/frontend-vue/app/components/FunnelCard.vue +66 -0
- package/frontend-vue/app/components/HtmlPreviewModal.vue +20 -0
- package/frontend-vue/app/components/NewTemplateModal.vue +158 -0
- package/frontend-vue/app/components/ProviderChip.vue +39 -0
- package/frontend-vue/app/components/SendDrawer.vue +298 -0
- package/frontend-vue/app/components/TemplateCard.vue +127 -0
- package/frontend-vue/app/components/TemplateDrawer.vue +394 -0
- package/frontend-vue/app/components/TemplateGallery.vue +112 -0
- package/frontend-vue/app/components/TemplatePreviewFrame.vue +103 -0
- package/frontend-vue/app/components/TestSendModal.vue +162 -0
- package/frontend-vue/app/components/TokenText.vue +26 -0
- package/frontend-vue/app/components/blocks/Button.vue +19 -0
- package/frontend-vue/app/components/blocks/Code.vue +17 -0
- package/frontend-vue/app/components/blocks/Divider.vue +3 -0
- package/frontend-vue/app/components/blocks/Footer.vue +22 -0
- package/frontend-vue/app/components/blocks/Heading.vue +18 -0
- package/frontend-vue/app/components/blocks/Hero.vue +32 -0
- package/frontend-vue/app/components/blocks/List.vue +33 -0
- package/frontend-vue/app/components/blocks/Paragraph.vue +18 -0
- package/frontend-vue/app/components/blocks/Total.vue +18 -0
- package/frontend-vue/app/components/editor/EditorBlock.vue +121 -0
- package/frontend-vue/app/components/editor/EditorBlockList.vue +44 -0
- package/frontend-vue/app/components/editor/EditorBlockSettings.vue +186 -0
- package/frontend-vue/app/components/editor/EditorCanvas.vue +91 -0
- package/frontend-vue/app/components/editor/EditorConditionFields.vue +88 -0
- package/frontend-vue/app/components/editor/EditorDataPanel.vue +128 -0
- package/frontend-vue/app/components/editor/EditorDraftBar.vue +35 -0
- package/frontend-vue/app/components/editor/EditorIfBlock.vue +120 -0
- package/frontend-vue/app/components/editor/EditorMissingLocale.vue +62 -0
- package/frontend-vue/app/components/editor/EditorPalette.vue +62 -0
- package/frontend-vue/app/components/editor/EditorRail.vue +58 -0
- package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +37 -0
- package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +87 -0
- package/frontend-vue/app/components/editor/EditorTestSendModal.vue +66 -0
- package/frontend-vue/app/components/editor/EditorToolbar.vue +155 -0
- package/frontend-vue/app/components/editor/EditorVariables.vue +163 -0
- package/frontend-vue/app/components/editor/EditorWorkspace.vue +189 -0
- package/frontend-vue/app/composables/useEditorContext.ts +44 -0
- package/frontend-vue/app/composables/useMailingApi.ts +197 -0
- package/frontend-vue/app/composables/useMailingPeriodData.ts +39 -0
- package/frontend-vue/app/composables/useTemplateDrawer.ts +90 -0
- package/frontend-vue/app/composables/useTemplateEditor.ts +228 -0
- package/frontend-vue/app/emails/EmailMailingTemplate.vue +24 -0
- package/frontend-vue/app/emails/components/MailingBlocks.vue +117 -0
- package/frontend-vue/app/plugins/template-gallery-display.client.ts +14 -0
- package/frontend-vue/app/types/component.ts +29 -0
- package/frontend-vue/app/types/mailing.ts +365 -0
- package/frontend-vue/app/utils/blocks/components.ts +14 -0
- package/frontend-vue/app/utils/blocks/dnd.ts +15 -0
- package/frontend-vue/app/utils/blocks/factory.ts +100 -0
- package/frontend-vue/app/utils/blocks/fields.ts +58 -0
- package/frontend-vue/app/utils/blocks/index.ts +5 -0
- package/frontend-vue/app/utils/blocks/tree.ts +74 -0
- package/frontend-vue/app/utils/categories.ts +54 -0
- package/frontend-vue/app/utils/clone.ts +3 -0
- package/frontend-vue/app/utils/conditions.ts +50 -0
- package/frontend-vue/app/utils/editor-route.ts +18 -0
- package/frontend-vue/app/utils/gallery.ts +41 -0
- package/frontend-vue/app/utils/locales.ts +41 -0
- package/frontend-vue/app/utils/paths.ts +84 -0
- package/frontend-vue/app/utils/period.ts +23 -0
- package/frontend-vue/app/utils/testData.ts +10 -0
- package/frontend-vue/app/utils/timeline.ts +68 -0
- package/frontend-vue/app/utils/tokens.ts +24 -0
- package/frontend-vue/app/utils/variables.ts +48 -0
- package/frontend-vue/dms.email.ts +9 -0
- package/frontend-vue/dms.frontend.ts +29 -0
- package/frontend-vue/eslint.config.mjs +13 -0
- package/frontend-vue/i18n/locales/mailing-en-GB.json +397 -0
- package/frontend-vue/i18n/locales/mailing-fr-FR.json +397 -0
- package/frontend-vue/package.json +32 -0
- package/frontend-vue/pnpm-lock.yaml +6446 -0
- package/frontend-vue/pnpm-workspace.yaml +2 -0
- package/frontend-vue/tests/block-components.test.ts +14 -0
- package/frontend-vue/tests/block-fields.test.ts +23 -0
- package/frontend-vue/tests/blocks-factory.test.ts +28 -0
- package/frontend-vue/tests/blocks-tree.test.ts +49 -0
- package/frontend-vue/tests/categories.test.ts +64 -0
- package/frontend-vue/tests/conditions.test.ts +36 -0
- package/frontend-vue/tests/dnd.test.ts +11 -0
- package/frontend-vue/tests/editor-block-list.test.ts +54 -0
- package/frontend-vue/tests/editor-route.test.ts +23 -0
- package/frontend-vue/tests/email-render-cases.json +77 -0
- package/frontend-vue/tests/gallery.test.ts +39 -0
- package/frontend-vue/tests/i18n-editor.test.ts +21 -0
- package/frontend-vue/tests/i18n.test.ts +83 -0
- package/frontend-vue/tests/locales.test.ts +38 -0
- package/frontend-vue/tests/mailing-api.test.ts +10 -0
- package/frontend-vue/tests/paths.test.ts +128 -0
- package/frontend-vue/tests/period.test.ts +13 -0
- package/frontend-vue/tests/smoke.test.ts +8 -0
- package/frontend-vue/tests/template-editor.test.ts +65 -0
- package/frontend-vue/tests/test-data.test.ts +15 -0
- package/frontend-vue/tests/timeline.test.ts +29 -0
- package/frontend-vue/tests/tokens.test.ts +12 -0
- package/frontend-vue/tests/variables.test.ts +46 -0
- package/frontend-vue/vitest.config.ts +10 -0
- package/package.json +94 -0
- package/src/antelope-modules.ts +48 -0
- package/src/automation/email-event-trigger.ts +112 -0
- package/src/automation/index.ts +33 -0
- package/src/automation/send-template-action.ts +100 -0
- package/src/config.ts +15 -0
- package/src/constants.ts +42 -0
- package/src/crons/index.ts +1 -0
- package/src/crons/retention.ts +126 -0
- package/src/data/body.ts +14 -0
- package/src/data/index.ts +3 -0
- package/src/data/sends-table.ts +154 -0
- package/src/data/templates-table.ts +202 -0
- package/src/data-types/categories-type.ts +29 -0
- package/src/data-types/category-type.ts +44 -0
- package/src/data-types/index.ts +2 -0
- package/src/db/index.ts +2 -0
- package/src/db/models/index.ts +4 -0
- package/src/db/models/send-events.model.ts +46 -0
- package/src/db/models/sends.model.ts +147 -0
- package/src/db/models/settings.model.ts +12 -0
- package/src/db/models/templates.model.ts +12 -0
- package/src/db/tables/index.ts +4 -0
- package/src/db/tables/send-events.table.ts +31 -0
- package/src/db/tables/sends.table.ts +85 -0
- package/src/db/tables/settings.table.ts +49 -0
- package/src/db/tables/templates.table.ts +70 -0
- package/src/engine/conditions.ts +30 -0
- package/src/engine/coverage.ts +67 -0
- package/src/engine/fallback-html.ts +116 -0
- package/src/engine/index.ts +7 -0
- package/src/engine/paths.ts +33 -0
- package/src/engine/resolve.ts +178 -0
- package/src/engine/tokens.ts +35 -0
- package/src/engine/walk.ts +18 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/tenant-export.ts +127 -0
- package/src/implementations/dms-mailing/index.ts +2 -0
- package/src/index.ts +62 -0
- package/src/pages/editor.ts +23 -0
- package/src/pages/index.ts +11 -0
- package/src/pages/module.ts +9 -0
- package/src/pages/overview.ts +175 -0
- package/src/pages/sends.ts +174 -0
- package/src/pages/settings/form.ts +67 -0
- package/src/pages/settings/index.ts +2 -0
- package/src/pages/settings/page.ts +34 -0
- package/src/pages/templates.ts +141 -0
- package/src/permissions.ts +44 -0
- package/src/quick-actions.ts +20 -0
- package/src/realtime.ts +53 -0
- package/src/routes/events.ts +57 -0
- package/src/routes/index.ts +7 -0
- package/src/routes/metrics.ts +243 -0
- package/src/routes/period.ts +46 -0
- package/src/routes/provider.ts +31 -0
- package/src/routes/sends.ts +215 -0
- package/src/routes/settings.ts +66 -0
- package/src/routes/templates.ts +256 -0
- package/src/services/attention.ts +151 -0
- package/src/services/categories.ts +43 -0
- package/src/services/events.ts +121 -0
- package/src/services/metrics.ts +175 -0
- package/src/services/notify.ts +90 -0
- package/src/services/permissions.ts +42 -0
- package/src/services/provider.ts +46 -0
- package/src/services/render.ts +33 -0
- package/src/services/send.ts +409 -0
- package/src/services/settings.ts +74 -0
- package/src/services/status.ts +16 -0
- package/src/services/templates.ts +157 -0
- package/src/test/antelope.test.ts +188 -0
- package/src/test/helpers/http.ts +23 -0
- package/src/test/helpers/owner.ts +37 -0
- package/src/test/integration/events.test.ts +136 -0
- package/src/test/integration/retention.test.ts +363 -0
- package/src/test/integration/sends.test.ts +200 -0
- package/src/test/integration/settings.test.ts +50 -0
- package/src/test/integration/setup.test.ts +35 -0
- package/src/test/integration/templates.test.ts +229 -0
- package/src/test/integration/tenant-export.test.ts +125 -0
- package/src/test/unit/attention.test.ts +129 -0
- package/src/test/unit/automation.test.ts +130 -0
- package/src/test/unit/categories.test.ts +51 -0
- package/src/test/unit/engine/conditions.test.ts +35 -0
- package/src/test/unit/engine/coverage.test.ts +118 -0
- package/src/test/unit/engine/fallback-html.test.ts +86 -0
- package/src/test/unit/engine/paths.test.ts +39 -0
- package/src/test/unit/engine/resolve.test.ts +176 -0
- package/src/test/unit/engine/tokens.test.ts +26 -0
- package/src/test/unit/events.test.ts +19 -0
- package/src/test/unit/metrics.test.ts +170 -0
- package/src/test/unit/notify.test.ts +71 -0
- package/src/test/unit/period.test.ts +64 -0
- package/src/test/unit/provider.test.ts +57 -0
- package/src/test/unit/realtime.test.ts +11 -0
- package/src/test/unit/render.test.ts +23 -0
- package/src/test/unit/retention.test.ts +91 -0
- package/src/test/unit/send-batch.test.ts +72 -0
- package/src/test/unit/send.test.ts +161 -0
- package/src/test/unit/smoke.test.ts +8 -0
- package/src/test/unit/status.test.ts +14 -0
- package/src/test/unit/templates.test.ts +52 -0
- package/src/test/unit/tenant-export.test.ts +88 -0
- package/src/test/unit/webhook-secret.test.ts +50 -0
- package/src/types/blocks.ts +31 -0
- package/src/types/index.ts +4 -0
- package/src/types/resolved.ts +89 -0
- package/src/types/sends.ts +44 -0
- package/src/types/settings.ts +24 -0
- package/src/validation/settings.schema.ts +33 -0
- package/src/validation/templates.schema.ts +145 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActionType,
|
|
3
|
+
JsonSchema,
|
|
4
|
+
} from "@antelopejs/interface-dms-automation";
|
|
5
|
+
import type { EmailAddress } from "@antelopejs/interface-email";
|
|
6
|
+
import type { SendTemplateResult } from "@antelopejs/interface-dms-mailing";
|
|
7
|
+
import { sendTemplate } from "../services/send";
|
|
8
|
+
import { SEND_STATUSES } from "../types";
|
|
9
|
+
|
|
10
|
+
export interface SendTemplateActionInput {
|
|
11
|
+
tenantId: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
to: EmailAddress | EmailAddress[];
|
|
14
|
+
locale?: string;
|
|
15
|
+
variables?: Record<string, unknown>;
|
|
16
|
+
source?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SEND_TEMPLATE_ACTION_ID = "mailing.send-template";
|
|
20
|
+
|
|
21
|
+
const AUTOMATION_SOURCE_PREFIX = "automation:";
|
|
22
|
+
|
|
23
|
+
const INPUT_SCHEMA: JsonSchema = {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
tenantId: { type: "string" },
|
|
27
|
+
slug: { type: "string", description: "Slug of the live mailing template" },
|
|
28
|
+
// A list, not just one address: the automation module has no loop node, so
|
|
29
|
+
// a procedure cannot call this action once per contact — the fan-out has to
|
|
30
|
+
// happen here. `sendTemplate` already accepts both shapes.
|
|
31
|
+
to: {
|
|
32
|
+
type: ["string", "array"],
|
|
33
|
+
items: { type: "string" },
|
|
34
|
+
description: "Recipient address, or the list of addresses to send to",
|
|
35
|
+
},
|
|
36
|
+
locale: { type: "string" },
|
|
37
|
+
variables: { type: "object" },
|
|
38
|
+
source: { type: "string" },
|
|
39
|
+
},
|
|
40
|
+
required: ["tenantId", "slug", "to"],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const RESULT_PROPERTIES: JsonSchema = {
|
|
44
|
+
sendId: { type: "string" },
|
|
45
|
+
status: { type: "string", enum: SEND_STATUSES },
|
|
46
|
+
messageId: { type: "string" },
|
|
47
|
+
error: { type: "string" },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const OUTPUT_SCHEMA: JsonSchema = {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
...RESULT_PROPERTIES,
|
|
54
|
+
results: {
|
|
55
|
+
type: "array",
|
|
56
|
+
description: "One entry per recipient",
|
|
57
|
+
items: { type: "object", properties: RESULT_PROPERTIES },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const failedRecipients = (result: SendTemplateResult): SendTemplateResult[] =>
|
|
63
|
+
(result.results ?? [result]).filter((entry) => entry.status === "failed");
|
|
64
|
+
|
|
65
|
+
export const sendTemplateAction: ActionType<
|
|
66
|
+
SendTemplateActionInput,
|
|
67
|
+
SendTemplateResult
|
|
68
|
+
> = {
|
|
69
|
+
id: SEND_TEMPLATE_ACTION_ID,
|
|
70
|
+
name: "Send e-mail template",
|
|
71
|
+
description: "Render a live mailing template and send it to a recipient",
|
|
72
|
+
icon: "i-ph-envelope-simple",
|
|
73
|
+
inputSchema: INPUT_SCHEMA,
|
|
74
|
+
outputSchema: OUTPUT_SCHEMA,
|
|
75
|
+
async execute(input, ctx) {
|
|
76
|
+
const source =
|
|
77
|
+
input.source ?? `${AUTOMATION_SOURCE_PREFIX}${ctx.procedureId}`;
|
|
78
|
+
try {
|
|
79
|
+
const result = await sendTemplate(input.slug, {
|
|
80
|
+
tenantId: input.tenantId,
|
|
81
|
+
to: input.to,
|
|
82
|
+
locale: input.locale,
|
|
83
|
+
variables: input.variables,
|
|
84
|
+
source,
|
|
85
|
+
});
|
|
86
|
+
const failures = failedRecipients(result);
|
|
87
|
+
if (failures.length > 0) {
|
|
88
|
+
ctx.log(
|
|
89
|
+
"error",
|
|
90
|
+
`Template "${input.slug}" was rejected for ${failures.length} recipient(s)`,
|
|
91
|
+
failures,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
ctx.log("error", `Template "${input.slug}" could not be sent`, error);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface DmsMailingConfig {
|
|
2
|
+
useHtmlRender?: boolean;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const DEFAULTS: Required<DmsMailingConfig> = { useHtmlRender: true };
|
|
6
|
+
|
|
7
|
+
let current: Required<DmsMailingConfig> = DEFAULTS;
|
|
8
|
+
|
|
9
|
+
export function setConfig(config: DmsMailingConfig): void {
|
|
10
|
+
current = { ...DEFAULTS, ...config };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getConfig(): Required<DmsMailingConfig> {
|
|
14
|
+
return current;
|
|
15
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const MODULE_ID = "mailing";
|
|
2
|
+
export const API_BASE_PATH = "/api/mailing";
|
|
3
|
+
export const TABLES_BASE_PATH = `${API_BASE_PATH}/tables`;
|
|
4
|
+
export const FRONTEND_MODULE_NAME = "@antelopejs/dms-mailing-frontend-vue";
|
|
5
|
+
export const EMAIL_TEMPLATE_NAME = "EmailMailingTemplate";
|
|
6
|
+
|
|
7
|
+
export const MAILING_ACCESS_PERMISSION = "mailing.access";
|
|
8
|
+
export const MAILING_TEMPLATES_MANAGE_PERMISSION = "mailing.templates.manage";
|
|
9
|
+
export const MAILING_SEND_PERMISSION = "mailing.send";
|
|
10
|
+
export const MAILING_SETTINGS_MANAGE_PERMISSION = "mailing.settings.manage";
|
|
11
|
+
|
|
12
|
+
export const OVERVIEW_PERIOD_SCOPE = "mailing-overview";
|
|
13
|
+
export const SENDS_PERIOD_SCOPE = "mailing-sends";
|
|
14
|
+
|
|
15
|
+
export const MAILING_SENDS_TOPIC = "mailing:sends";
|
|
16
|
+
|
|
17
|
+
export const GALLERY_DISPLAY_ID = "gallery";
|
|
18
|
+
export const TEMPLATE_CATEGORIES_TYPE_ID = "mailing_template_categories";
|
|
19
|
+
export const TEMPLATE_CATEGORY_TYPE_ID = "mailing_template_category";
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_FALLBACK_LOCALE = "en";
|
|
22
|
+
export const DEFAULT_LOG_RETENTION_DAYS = 90;
|
|
23
|
+
export const MAX_SLUG_LENGTH = 64;
|
|
24
|
+
export const MAX_NAME_LENGTH = 120;
|
|
25
|
+
export const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
26
|
+
|
|
27
|
+
export const HTTP_BAD_REQUEST = 400;
|
|
28
|
+
export const HTTP_FORBIDDEN = 403;
|
|
29
|
+
export const HTTP_NOT_FOUND = 404;
|
|
30
|
+
export const HTTP_CONFLICT = 409;
|
|
31
|
+
export const HTTP_UNPROCESSABLE = 422;
|
|
32
|
+
|
|
33
|
+
export const LOCALE_LIST_SEPARATOR = ",";
|
|
34
|
+
|
|
35
|
+
export const SYSTEM_VARIABLE_PATHS = ["unsubscribeUrl", "preferencesUrl"];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ceiling on a metrics window. `from`/`to` are plain query parameters, and the
|
|
39
|
+
* daily series allocates one bucket per day, so an unbounded window lets a
|
|
40
|
+
* caller size the response by hand.
|
|
41
|
+
*/
|
|
42
|
+
export const MAX_WINDOW_DAYS = 400;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./retention";
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Logging } from "@antelopejs/interface-core/logging";
|
|
2
|
+
import { GetModel } from "@antelopejs/interface-database-decorators";
|
|
3
|
+
import { TenantModel } from "@antelopejs/interface-dms/db";
|
|
4
|
+
import cron, { type ScheduledTask } from "node-cron";
|
|
5
|
+
import { SendEventModel, SendModel, SettingsModel } from "../db";
|
|
6
|
+
|
|
7
|
+
export const RETENTION_CRON_NAME = "mailing-retention";
|
|
8
|
+
|
|
9
|
+
export interface RetentionLimits {
|
|
10
|
+
pageSize: number;
|
|
11
|
+
maxRows: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RetentionPass {
|
|
15
|
+
loadPage(size: number): Promise<string[]>;
|
|
16
|
+
purgeSend(sendId: string): Promise<number>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How many stale sends one pass reads at a time, and how many it retires per
|
|
21
|
+
* tenant per run. What the cap leaves behind drains over the following nights.
|
|
22
|
+
*/
|
|
23
|
+
export const RETENTION_LIMITS: RetentionLimits = {
|
|
24
|
+
pageSize: 500,
|
|
25
|
+
maxRows: 20_000,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const DAY_MS = 86_400_000;
|
|
29
|
+
const RETENTION_SCHEDULE = "15 3 * * *";
|
|
30
|
+
const MIN_RETENTION_DAYS = 1;
|
|
31
|
+
const NO_ROWS = 0;
|
|
32
|
+
|
|
33
|
+
export function retentionLimit(days: number, now: Date): Date {
|
|
34
|
+
return new Date(now.getTime() - days * DAY_MS);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function purgeInPages(
|
|
38
|
+
pass: RetentionPass,
|
|
39
|
+
limits: RetentionLimits = RETENTION_LIMITS,
|
|
40
|
+
): Promise<number> {
|
|
41
|
+
let purged = NO_ROWS;
|
|
42
|
+
let inspected = NO_ROWS;
|
|
43
|
+
while (inspected < limits.maxRows) {
|
|
44
|
+
const size = Math.min(limits.pageSize, limits.maxRows - inspected);
|
|
45
|
+
const page = await pass.loadPage(size);
|
|
46
|
+
for (const sendId of page) purged += await pass.purgeSend(sendId);
|
|
47
|
+
inspected += page.length;
|
|
48
|
+
if (page.length < size) break;
|
|
49
|
+
}
|
|
50
|
+
return purged;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Replays interrupted cleanup without touching a send that became active after discovery. */
|
|
54
|
+
export async function purgeSend(
|
|
55
|
+
tenantId: string,
|
|
56
|
+
sendId: string,
|
|
57
|
+
limit: Date,
|
|
58
|
+
): Promise<number> {
|
|
59
|
+
const sends = GetModel(SendModel, tenantId);
|
|
60
|
+
if (!(await sends.retire(sendId, limit))) return NO_ROWS;
|
|
61
|
+
await GetModel(SendEventModel, tenantId).deleteBySend(sendId);
|
|
62
|
+
return sends.deleteRetired(sendId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function tenantPass(tenantId: string, limit: Date): RetentionPass {
|
|
66
|
+
const sends = GetModel(SendModel, tenantId);
|
|
67
|
+
return {
|
|
68
|
+
async loadPage(size) {
|
|
69
|
+
const stale = await sends.listOlderThan(limit, size);
|
|
70
|
+
return stale.map((send) => send._id);
|
|
71
|
+
},
|
|
72
|
+
purgeSend: (sendId) => purgeSend(tenantId, sendId, limit),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Reconciles late inserts whose writer crashed before checking the retired parent. */
|
|
77
|
+
export async function purgeOrphanEvents(
|
|
78
|
+
tenantId: string,
|
|
79
|
+
pageSize = RETENTION_LIMITS.pageSize,
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const sends = GetModel(SendModel, tenantId);
|
|
82
|
+
const events = GetModel(SendEventModel, tenantId);
|
|
83
|
+
let after = "";
|
|
84
|
+
for (;;) {
|
|
85
|
+
const page = await events.listAfter(after, pageSize);
|
|
86
|
+
for (const sendId of new Set(page.map((event) => event.sendId))) {
|
|
87
|
+
if (!(await sends.get(sendId))) await events.deleteBySend(sendId);
|
|
88
|
+
}
|
|
89
|
+
if (page.length < pageSize) return;
|
|
90
|
+
after = page[page.length - 1]._id;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function purgeTenant(tenantId: string, now: Date): Promise<number> {
|
|
95
|
+
const settings = await GetModel(SettingsModel, tenantId).getSingleton();
|
|
96
|
+
if (!settings || settings.logRetentionDays < MIN_RETENTION_DAYS) return 0;
|
|
97
|
+
const limit = retentionLimit(settings.logRetentionDays, now);
|
|
98
|
+
const purged = await purgeInPages(tenantPass(tenantId, limit));
|
|
99
|
+
await purgeOrphanEvents(tenantId);
|
|
100
|
+
return purged;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function runRetention(now: Date = new Date()): Promise<number> {
|
|
104
|
+
const tenants = await GetModel(TenantModel).getAll();
|
|
105
|
+
let purged = 0;
|
|
106
|
+
for (const tenant of tenants) purged += await purgeTenant(tenant._id, now);
|
|
107
|
+
Logging.Info(`[dms-mailing] Retention purged ${purged} sends`);
|
|
108
|
+
return purged;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Stops the task and drops it from node-cron's module-level registry. */
|
|
112
|
+
export async function stopRetention(
|
|
113
|
+
task: ScheduledTask | undefined,
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
if (!task) return;
|
|
116
|
+
await task.stop();
|
|
117
|
+
await task.destroy();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function scheduleRetention(): ScheduledTask {
|
|
121
|
+
return cron.schedule(RETENTION_SCHEDULE, () => {
|
|
122
|
+
void runRetention().catch((error: unknown) => {
|
|
123
|
+
Logging.Error(`Cron '${RETENTION_CRON_NAME}' failed:`, error);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
package/src/data/body.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HTTPResult } from "@antelopejs/interface-api";
|
|
2
|
+
import { HTTP_BAD_REQUEST } from "../constants";
|
|
3
|
+
|
|
4
|
+
const INVALID_BODY = "$dms_mailing.errors.invalid_body";
|
|
5
|
+
|
|
6
|
+
export function parseBody(body: Buffer | string): Record<string, unknown> {
|
|
7
|
+
const raw = typeof body === "string" ? body : body.toString("utf8");
|
|
8
|
+
if (!raw) return {};
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(raw) as Record<string, unknown>;
|
|
11
|
+
} catch {
|
|
12
|
+
throw new HTTPResult(HTTP_BAD_REQUEST, INVALID_BODY);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Controller } from "@antelopejs/interface-api";
|
|
2
|
+
import {
|
|
3
|
+
DataController,
|
|
4
|
+
RegisterDataController,
|
|
5
|
+
} from "@antelopejs/interface-data-api";
|
|
6
|
+
import {
|
|
7
|
+
Access,
|
|
8
|
+
AccessMode,
|
|
9
|
+
Listable,
|
|
10
|
+
ModelReference,
|
|
11
|
+
Sortable,
|
|
12
|
+
} from "@antelopejs/interface-data-api/metadata";
|
|
13
|
+
import { TenantScopedModel } from "@antelopejs/interface-dms/tenant-scoped-model";
|
|
14
|
+
import {
|
|
15
|
+
Column,
|
|
16
|
+
Exported,
|
|
17
|
+
Searchable,
|
|
18
|
+
Select,
|
|
19
|
+
TableViewRoutes,
|
|
20
|
+
} from "@antelopejs/interface-dms/base";
|
|
21
|
+
import { DefaultDataTypes } from "@antelopejs/interface-dms/base/data-types/default-types";
|
|
22
|
+
import { TABLES_BASE_PATH } from "../constants";
|
|
23
|
+
import { MailingSend, SendModel } from "../db";
|
|
24
|
+
import { SEND_STATUSES, type SendStatus } from "../types";
|
|
25
|
+
|
|
26
|
+
const STATUS_ITEMS = SEND_STATUSES.map((value) => ({
|
|
27
|
+
value,
|
|
28
|
+
label: `$dms_mailing.sends.status.${value}`,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const readOnlyRoutes = {
|
|
32
|
+
get: TableViewRoutes.Get,
|
|
33
|
+
list: TableViewRoutes.List,
|
|
34
|
+
count: TableViewRoutes.Count,
|
|
35
|
+
select: TableViewRoutes.Select,
|
|
36
|
+
...TableViewRoutes.ExportRoutes,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
@RegisterDataController()
|
|
40
|
+
export class SendsTableAPI extends DataController(
|
|
41
|
+
MailingSend,
|
|
42
|
+
readOnlyRoutes,
|
|
43
|
+
Controller(`${TABLES_BASE_PATH}/sends`),
|
|
44
|
+
) {
|
|
45
|
+
@ModelReference()
|
|
46
|
+
@TenantScopedModel(SendModel)
|
|
47
|
+
declare model: SendModel;
|
|
48
|
+
|
|
49
|
+
@Select()
|
|
50
|
+
@Listable()
|
|
51
|
+
@Exported()
|
|
52
|
+
@Access(AccessMode.ReadOnly)
|
|
53
|
+
declare _id: string;
|
|
54
|
+
|
|
55
|
+
@Listable()
|
|
56
|
+
@Exported()
|
|
57
|
+
@Searchable()
|
|
58
|
+
@Sortable()
|
|
59
|
+
@Access(AccessMode.ReadOnly)
|
|
60
|
+
@Column({
|
|
61
|
+
name: "$dms_mailing.sends.cols.recipientEmail",
|
|
62
|
+
type: new DefaultDataTypes.EmailType({}),
|
|
63
|
+
})
|
|
64
|
+
declare recipientEmail: string;
|
|
65
|
+
|
|
66
|
+
@Listable()
|
|
67
|
+
@Exported()
|
|
68
|
+
@Searchable()
|
|
69
|
+
@Access(AccessMode.ReadOnly)
|
|
70
|
+
@Column({
|
|
71
|
+
name: "$dms_mailing.sends.cols.recipientName",
|
|
72
|
+
type: new DefaultDataTypes.StringType({}),
|
|
73
|
+
})
|
|
74
|
+
declare recipientName: string;
|
|
75
|
+
|
|
76
|
+
@Listable()
|
|
77
|
+
@Exported()
|
|
78
|
+
@Searchable()
|
|
79
|
+
@Access(AccessMode.ReadOnly)
|
|
80
|
+
@Column({
|
|
81
|
+
name: "$dms_mailing.sends.cols.templateSlug",
|
|
82
|
+
type: new DefaultDataTypes.StringType({}),
|
|
83
|
+
filterable: true,
|
|
84
|
+
})
|
|
85
|
+
declare templateSlug: string;
|
|
86
|
+
|
|
87
|
+
@Listable()
|
|
88
|
+
@Exported()
|
|
89
|
+
@Access(AccessMode.ReadOnly)
|
|
90
|
+
@Column({
|
|
91
|
+
name: "$dms_mailing.sends.cols.status",
|
|
92
|
+
type: new DefaultDataTypes.SelectType({ items: STATUS_ITEMS }),
|
|
93
|
+
filterable: true,
|
|
94
|
+
})
|
|
95
|
+
declare status: SendStatus;
|
|
96
|
+
|
|
97
|
+
@Listable()
|
|
98
|
+
@Exported()
|
|
99
|
+
@Access(AccessMode.ReadOnly)
|
|
100
|
+
@Column({
|
|
101
|
+
name: "$dms_mailing.sends.cols.locale",
|
|
102
|
+
type: new DefaultDataTypes.StringType({}),
|
|
103
|
+
})
|
|
104
|
+
declare locale: string;
|
|
105
|
+
|
|
106
|
+
@Listable()
|
|
107
|
+
@Exported()
|
|
108
|
+
@Sortable()
|
|
109
|
+
@Access(AccessMode.ReadOnly)
|
|
110
|
+
@Column({
|
|
111
|
+
name: "$dms_mailing.sends.cols.latencyMs",
|
|
112
|
+
type: new DefaultDataTypes.NumberType({}),
|
|
113
|
+
})
|
|
114
|
+
declare latencyMs: number;
|
|
115
|
+
|
|
116
|
+
@Listable()
|
|
117
|
+
@Exported()
|
|
118
|
+
@Sortable()
|
|
119
|
+
@Access(AccessMode.ReadOnly)
|
|
120
|
+
@Column({
|
|
121
|
+
name: "$dms_mailing.sends.cols.createdAt",
|
|
122
|
+
type: new DefaultDataTypes.DateType({}),
|
|
123
|
+
})
|
|
124
|
+
declare createdAt: Date;
|
|
125
|
+
|
|
126
|
+
@Listable()
|
|
127
|
+
@Exported()
|
|
128
|
+
@Access(AccessMode.ReadOnly)
|
|
129
|
+
@Column({
|
|
130
|
+
name: "$dms_mailing.sends.cols.source",
|
|
131
|
+
type: new DefaultDataTypes.StringType({}),
|
|
132
|
+
})
|
|
133
|
+
declare source: string;
|
|
134
|
+
|
|
135
|
+
@Listable()
|
|
136
|
+
@Exported()
|
|
137
|
+
@Access(AccessMode.ReadOnly)
|
|
138
|
+
@Column({
|
|
139
|
+
name: "$dms_mailing.sends.cols.isTest",
|
|
140
|
+
type: new DefaultDataTypes.BooleanType({}),
|
|
141
|
+
filterable: true,
|
|
142
|
+
})
|
|
143
|
+
declare isTest: boolean;
|
|
144
|
+
|
|
145
|
+
@Listable()
|
|
146
|
+
@Exported()
|
|
147
|
+
@Access(AccessMode.ReadOnly)
|
|
148
|
+
@Column({
|
|
149
|
+
name: "$dms_mailing.sends.cols.error",
|
|
150
|
+
type: new DefaultDataTypes.StringType({}),
|
|
151
|
+
isVisible: false,
|
|
152
|
+
})
|
|
153
|
+
declare error: string;
|
|
154
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controller,
|
|
3
|
+
HTTPResult,
|
|
4
|
+
type RequestContext,
|
|
5
|
+
} from "@antelopejs/interface-api";
|
|
6
|
+
import {
|
|
7
|
+
DataController,
|
|
8
|
+
type DataControllerCallback,
|
|
9
|
+
RegisterDataController,
|
|
10
|
+
} from "@antelopejs/interface-data-api";
|
|
11
|
+
import {
|
|
12
|
+
Access,
|
|
13
|
+
AccessMode,
|
|
14
|
+
Listable,
|
|
15
|
+
Mandatory,
|
|
16
|
+
ModelReference,
|
|
17
|
+
Sortable,
|
|
18
|
+
} from "@antelopejs/interface-data-api/metadata";
|
|
19
|
+
import { GetModel } from "@antelopejs/interface-database-decorators";
|
|
20
|
+
import { authenticateTenantRequest } from "@antelopejs/interface-dms/guards";
|
|
21
|
+
import { getRequestTenantId } from "@antelopejs/interface-dms/request-tenant";
|
|
22
|
+
import { TenantScopedModel } from "@antelopejs/interface-dms/tenant-scoped-model";
|
|
23
|
+
import {
|
|
24
|
+
Column,
|
|
25
|
+
Exported,
|
|
26
|
+
Searchable,
|
|
27
|
+
Select,
|
|
28
|
+
TableViewRoutes,
|
|
29
|
+
} from "@antelopejs/interface-dms/base";
|
|
30
|
+
import { DefaultDataTypes } from "@antelopejs/interface-dms/base/data-types/default-types";
|
|
31
|
+
import { ReadonlyBehaviorType } from "@antelopejs/interface-dms/base/types";
|
|
32
|
+
import { HTTP_CONFLICT, HTTP_NOT_FOUND, TABLES_BASE_PATH } from "../constants";
|
|
33
|
+
import { TemplateCategoryType } from "../data-types";
|
|
34
|
+
import { MailingTemplate, TemplateModel } from "../db";
|
|
35
|
+
import { getSettings } from "../services/settings";
|
|
36
|
+
import { displayName, initializeTemplate } from "../services/templates";
|
|
37
|
+
|
|
38
|
+
import { createTemplateSchema } from "../validation/templates.schema";
|
|
39
|
+
import { parseBody } from "./body";
|
|
40
|
+
|
|
41
|
+
const DUPLICATE_SLUG = "$dms_mailing.errors.duplicate_slug";
|
|
42
|
+
const SOURCE_NOT_FOUND = "$dms_mailing.errors.template_not_found";
|
|
43
|
+
const CREATE_ONLY = { edit: ReadonlyBehaviorType.disabled };
|
|
44
|
+
// Read-only columns render as empty dashes on the create form; the four fields
|
|
45
|
+
// a template is actually created from are the only ones worth asking for.
|
|
46
|
+
const NEVER_ON_CREATE = { new: ReadonlyBehaviorType.hidden };
|
|
47
|
+
|
|
48
|
+
const STATUS_ITEMS = ["draft", "live", "archived"].map((value) => ({
|
|
49
|
+
value,
|
|
50
|
+
label: `$dms_mailing.templates.status.${value}`,
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
/** The template a creation copies from, refused rather than silently ignored. */
|
|
54
|
+
async function resolveSource(
|
|
55
|
+
tenantId: string,
|
|
56
|
+
sourceTemplateId: string | undefined,
|
|
57
|
+
): Promise<MailingTemplate | undefined> {
|
|
58
|
+
if (!sourceTemplateId) return undefined;
|
|
59
|
+
const source = await GetModel(TemplateModel, tenantId).get(sourceTemplateId);
|
|
60
|
+
if (!source) throw new HTTPResult(HTTP_NOT_FOUND, SOURCE_NOT_FOUND);
|
|
61
|
+
return source;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function withSeeding(base: DataControllerCallback): DataControllerCallback {
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
func: async function (
|
|
68
|
+
this: unknown,
|
|
69
|
+
ctx: RequestContext,
|
|
70
|
+
params: unknown,
|
|
71
|
+
body: Buffer | string,
|
|
72
|
+
...rest: unknown[]
|
|
73
|
+
) {
|
|
74
|
+
const seed = createTemplateSchema.parse(parseBody(body));
|
|
75
|
+
const tenantId = getRequestTenantId(ctx);
|
|
76
|
+
const existing = await GetModel(TemplateModel, tenantId).getBySlug(
|
|
77
|
+
seed.slug,
|
|
78
|
+
);
|
|
79
|
+
if (existing) throw new HTTPResult(HTTP_CONFLICT, DUPLICATE_SLUG);
|
|
80
|
+
const source = await resolveSource(tenantId, seed.sourceTemplateId);
|
|
81
|
+
const ids = (await base.func.call(
|
|
82
|
+
this,
|
|
83
|
+
ctx,
|
|
84
|
+
params,
|
|
85
|
+
body,
|
|
86
|
+
...rest,
|
|
87
|
+
)) as string[];
|
|
88
|
+
const user = await authenticateTenantRequest(ctx);
|
|
89
|
+
const settings = await getSettings(tenantId);
|
|
90
|
+
await initializeTemplate(
|
|
91
|
+
tenantId,
|
|
92
|
+
ids[0] as string,
|
|
93
|
+
settings.fallbackLocale,
|
|
94
|
+
displayName(user),
|
|
95
|
+
source,
|
|
96
|
+
);
|
|
97
|
+
return ids;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const templateRoutes = {
|
|
103
|
+
...TableViewRoutes.All,
|
|
104
|
+
new: withSeeding(TableViewRoutes.New),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
@RegisterDataController()
|
|
108
|
+
export class TemplatesTableAPI extends DataController(
|
|
109
|
+
MailingTemplate,
|
|
110
|
+
templateRoutes,
|
|
111
|
+
Controller(`${TABLES_BASE_PATH}/templates`),
|
|
112
|
+
) {
|
|
113
|
+
@ModelReference()
|
|
114
|
+
@TenantScopedModel(TemplateModel)
|
|
115
|
+
declare model: TemplateModel;
|
|
116
|
+
|
|
117
|
+
@Select()
|
|
118
|
+
@Listable()
|
|
119
|
+
@Exported()
|
|
120
|
+
@Access(AccessMode.ReadOnly)
|
|
121
|
+
declare _id: string;
|
|
122
|
+
|
|
123
|
+
@Select()
|
|
124
|
+
@Listable()
|
|
125
|
+
@Sortable()
|
|
126
|
+
@Exported()
|
|
127
|
+
@Searchable()
|
|
128
|
+
@Mandatory("new")
|
|
129
|
+
@Access(AccessMode.ReadWrite)
|
|
130
|
+
@Column({
|
|
131
|
+
name: "$dms_mailing.templates.cols.name",
|
|
132
|
+
type: new DefaultDataTypes.StringType({}),
|
|
133
|
+
})
|
|
134
|
+
declare name: string;
|
|
135
|
+
|
|
136
|
+
@Select()
|
|
137
|
+
@Listable()
|
|
138
|
+
@Sortable()
|
|
139
|
+
@Exported()
|
|
140
|
+
@Searchable()
|
|
141
|
+
@Mandatory("new")
|
|
142
|
+
@Access(AccessMode.ReadWrite)
|
|
143
|
+
@Column({
|
|
144
|
+
name: "$dms_mailing.templates.cols.slug",
|
|
145
|
+
type: new DefaultDataTypes.StringType({}),
|
|
146
|
+
readonlyBehavior: CREATE_ONLY,
|
|
147
|
+
})
|
|
148
|
+
declare slug: string;
|
|
149
|
+
|
|
150
|
+
@Listable()
|
|
151
|
+
@Exported()
|
|
152
|
+
@Access(AccessMode.ReadWrite)
|
|
153
|
+
@Column({
|
|
154
|
+
name: "$dms_mailing.templates.cols.category",
|
|
155
|
+
type: new TemplateCategoryType(),
|
|
156
|
+
filterable: true,
|
|
157
|
+
})
|
|
158
|
+
declare category: string;
|
|
159
|
+
|
|
160
|
+
@Listable()
|
|
161
|
+
@Sortable()
|
|
162
|
+
@Exported()
|
|
163
|
+
@Access(AccessMode.ReadOnly)
|
|
164
|
+
@Column({
|
|
165
|
+
name: "$dms_mailing.templates.cols.status",
|
|
166
|
+
type: new DefaultDataTypes.SelectType({ items: STATUS_ITEMS }),
|
|
167
|
+
filterable: true,
|
|
168
|
+
readonlyBehavior: NEVER_ON_CREATE,
|
|
169
|
+
})
|
|
170
|
+
declare status: string;
|
|
171
|
+
|
|
172
|
+
@Listable()
|
|
173
|
+
@Sortable()
|
|
174
|
+
@Exported()
|
|
175
|
+
@Access(AccessMode.ReadOnly)
|
|
176
|
+
@Column({
|
|
177
|
+
name: "$dms_mailing.templates.cols.updatedAt",
|
|
178
|
+
type: new DefaultDataTypes.DateType({}),
|
|
179
|
+
readonlyBehavior: NEVER_ON_CREATE,
|
|
180
|
+
})
|
|
181
|
+
declare updatedAt: Date;
|
|
182
|
+
|
|
183
|
+
@Listable()
|
|
184
|
+
@Exported()
|
|
185
|
+
@Access(AccessMode.ReadOnly)
|
|
186
|
+
@Column({
|
|
187
|
+
name: "$dms_mailing.templates.cols.locales",
|
|
188
|
+
type: new DefaultDataTypes.StringType({}),
|
|
189
|
+
readonlyBehavior: NEVER_ON_CREATE,
|
|
190
|
+
})
|
|
191
|
+
declare locales: string;
|
|
192
|
+
|
|
193
|
+
@Listable()
|
|
194
|
+
@Exported()
|
|
195
|
+
@Access(AccessMode.ReadOnly)
|
|
196
|
+
@Column({
|
|
197
|
+
name: "$dms_mailing.templates.cols.updatedBy",
|
|
198
|
+
type: new DefaultDataTypes.StringType({}),
|
|
199
|
+
readonlyBehavior: NEVER_ON_CREATE,
|
|
200
|
+
})
|
|
201
|
+
declare updatedBy: string;
|
|
202
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CustomComponent } from "@antelopejs/interface-dms/base/custom";
|
|
2
|
+
import {
|
|
3
|
+
DataType,
|
|
4
|
+
RegisterDataType,
|
|
5
|
+
} from "@antelopejs/interface-dms/base/data-types";
|
|
6
|
+
import { TEMPLATE_CATEGORIES_TYPE_ID } from "../constants";
|
|
7
|
+
import { CATEGORIES_SCHEMA } from "../services/categories";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The tenant's template categories as a settings field — a repeatable list of
|
|
11
|
+
* {id, label, icon} that no built-in DataType models: the input is ours, and
|
|
12
|
+
* the validation accepts the array or the JSON string a form field stores.
|
|
13
|
+
*/
|
|
14
|
+
@RegisterDataType(TEMPLATE_CATEGORIES_TYPE_ID)
|
|
15
|
+
export class TemplateCategoriesType extends DataType {
|
|
16
|
+
constructor(public readonly options: Record<string, unknown> = {}) {
|
|
17
|
+
super([], undefined, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected defaultInputComponent() {
|
|
21
|
+
return CustomComponent("DmsMailingCategoriesInput")
|
|
22
|
+
.options(this.options)
|
|
23
|
+
.serializeSync();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getValidation() {
|
|
27
|
+
return CATEGORIES_SCHEMA;
|
|
28
|
+
}
|
|
29
|
+
}
|