@endora-commerce/mod-newsletter 0.100.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.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/admin/api/newsletter-client.d.ts +64 -0
- package/dist/admin/api/newsletter-client.d.ts.map +1 -0
- package/dist/admin/api/newsletter-client.js +71 -0
- package/dist/admin/api/newsletter-client.js.map +1 -0
- package/dist/admin/email-variables.d.ts +21 -0
- package/dist/admin/email-variables.d.ts.map +1 -0
- package/dist/admin/email-variables.js +48 -0
- package/dist/admin/email-variables.js.map +1 -0
- package/dist/admin/index.d.ts +44 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +149 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/AutomationBuilder.d.ts +8 -0
- package/dist/admin/pages/AutomationBuilder.d.ts.map +1 -0
- package/dist/admin/pages/AutomationBuilder.js +131 -0
- package/dist/admin/pages/AutomationBuilder.js.map +1 -0
- package/dist/admin/pages/AutomationsPage.d.ts +8 -0
- package/dist/admin/pages/AutomationsPage.d.ts.map +1 -0
- package/dist/admin/pages/AutomationsPage.js +43 -0
- package/dist/admin/pages/AutomationsPage.js.map +1 -0
- package/dist/admin/pages/BlocksPage.d.ts +8 -0
- package/dist/admin/pages/BlocksPage.d.ts.map +1 -0
- package/dist/admin/pages/BlocksPage.js +110 -0
- package/dist/admin/pages/BlocksPage.js.map +1 -0
- package/dist/admin/pages/CampaignEditor.d.ts +8 -0
- package/dist/admin/pages/CampaignEditor.d.ts.map +1 -0
- package/dist/admin/pages/CampaignEditor.js +144 -0
- package/dist/admin/pages/CampaignEditor.js.map +1 -0
- package/dist/admin/pages/CampaignStats.d.ts +8 -0
- package/dist/admin/pages/CampaignStats.d.ts.map +1 -0
- package/dist/admin/pages/CampaignStats.js +45 -0
- package/dist/admin/pages/CampaignStats.js.map +1 -0
- package/dist/admin/pages/CampaignsPage.d.ts +8 -0
- package/dist/admin/pages/CampaignsPage.d.ts.map +1 -0
- package/dist/admin/pages/CampaignsPage.js +28 -0
- package/dist/admin/pages/CampaignsPage.js.map +1 -0
- package/dist/admin/pages/ProviderSettingsPage.d.ts +8 -0
- package/dist/admin/pages/ProviderSettingsPage.d.ts.map +1 -0
- package/dist/admin/pages/ProviderSettingsPage.js +64 -0
- package/dist/admin/pages/ProviderSettingsPage.js.map +1 -0
- package/dist/admin/pages/SubscribersPage.d.ts +8 -0
- package/dist/admin/pages/SubscribersPage.d.ts.map +1 -0
- package/dist/admin/pages/SubscribersPage.js +55 -0
- package/dist/admin/pages/SubscribersPage.js.map +1 -0
- package/dist/admin/pages/TagsPage.d.ts +8 -0
- package/dist/admin/pages/TagsPage.d.ts.map +1 -0
- package/dist/admin/pages/TagsPage.js +114 -0
- package/dist/admin/pages/TagsPage.js.map +1 -0
- package/dist/backend/entities/newsletter-automation-run.entity.d.ts +19 -0
- package/dist/backend/entities/newsletter-automation-run.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-automation-run.entity.js +72 -0
- package/dist/backend/entities/newsletter-automation-run.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-automation.entity.d.ts +22 -0
- package/dist/backend/entities/newsletter-automation.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-automation.entity.js +86 -0
- package/dist/backend/entities/newsletter-automation.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-campaign-subscriber.entity.d.ts +6 -0
- package/dist/backend/entities/newsletter-campaign-subscriber.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-campaign-subscriber.entity.js +30 -0
- package/dist/backend/entities/newsletter-campaign-subscriber.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-campaign.entity.d.ts +22 -0
- package/dist/backend/entities/newsletter-campaign.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-campaign.entity.js +102 -0
- package/dist/backend/entities/newsletter-campaign.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-custom-field.entity.d.ts +12 -0
- package/dist/backend/entities/newsletter-custom-field.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-custom-field.entity.js +53 -0
- package/dist/backend/entities/newsletter-custom-field.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-email-block-sales-channel.entity.d.ts +7 -0
- package/dist/backend/entities/newsletter-email-block-sales-channel.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-email-block-sales-channel.entity.js +35 -0
- package/dist/backend/entities/newsletter-email-block-sales-channel.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-email-block.entity.d.ts +21 -0
- package/dist/backend/entities/newsletter-email-block.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-email-block.entity.js +78 -0
- package/dist/backend/entities/newsletter-email-block.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-engagement-event.entity.d.ts +12 -0
- package/dist/backend/entities/newsletter-engagement-event.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-engagement-event.entity.js +54 -0
- package/dist/backend/entities/newsletter-engagement-event.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-send-record.entity.d.ts +23 -0
- package/dist/backend/entities/newsletter-send-record.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-send-record.entity.js +89 -0
- package/dist/backend/entities/newsletter-send-record.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-subscriber-tag.entity.d.ts +6 -0
- package/dist/backend/entities/newsletter-subscriber-tag.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-subscriber-tag.entity.js +31 -0
- package/dist/backend/entities/newsletter-subscriber-tag.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-subscriber.entity.d.ts +63 -0
- package/dist/backend/entities/newsletter-subscriber.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-subscriber.entity.js +147 -0
- package/dist/backend/entities/newsletter-subscriber.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-suppression.entity.d.ts +14 -0
- package/dist/backend/entities/newsletter-suppression.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-suppression.entity.js +51 -0
- package/dist/backend/entities/newsletter-suppression.entity.js.map +1 -0
- package/dist/backend/entities/newsletter-tag.entity.d.ts +12 -0
- package/dist/backend/entities/newsletter-tag.entity.d.ts.map +1 -0
- package/dist/backend/entities/newsletter-tag.entity.js +53 -0
- package/dist/backend/entities/newsletter-tag.entity.js.map +1 -0
- package/dist/backend/index.d.ts +120 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +139 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +114 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +171 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +27 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +118 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.self.d.ts +15 -0
- package/dist/backend/routes.self.d.ts.map +1 -0
- package/dist/backend/routes.self.js +28 -0
- package/dist/backend/routes.self.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +24 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +79 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/audience-resolver.d.ts +20 -0
- package/dist/backend/services/audience-resolver.d.ts.map +1 -0
- package/dist/backend/services/audience-resolver.js +41 -0
- package/dist/backend/services/audience-resolver.js.map +1 -0
- package/dist/backend/services/automation.service.d.ts +58 -0
- package/dist/backend/services/automation.service.d.ts.map +1 -0
- package/dist/backend/services/automation.service.js +301 -0
- package/dist/backend/services/automation.service.js.map +1 -0
- package/dist/backend/services/campaign-dispatch.service.d.ts +50 -0
- package/dist/backend/services/campaign-dispatch.service.d.ts.map +1 -0
- package/dist/backend/services/campaign-dispatch.service.js +130 -0
- package/dist/backend/services/campaign-dispatch.service.js.map +1 -0
- package/dist/backend/services/campaign.service.d.ts +40 -0
- package/dist/backend/services/campaign.service.d.ts.map +1 -0
- package/dist/backend/services/campaign.service.js +195 -0
- package/dist/backend/services/campaign.service.js.map +1 -0
- package/dist/backend/services/consent-block-seeder.d.ts +30 -0
- package/dist/backend/services/consent-block-seeder.d.ts.map +1 -0
- package/dist/backend/services/consent-block-seeder.js +52 -0
- package/dist/backend/services/consent-block-seeder.js.map +1 -0
- package/dist/backend/services/content.service.d.ts +52 -0
- package/dist/backend/services/content.service.d.ts.map +1 -0
- package/dist/backend/services/content.service.js +48 -0
- package/dist/backend/services/content.service.js.map +1 -0
- package/dist/backend/services/custom-field.service.d.ts +16 -0
- package/dist/backend/services/custom-field.service.d.ts.map +1 -0
- package/dist/backend/services/custom-field.service.js +67 -0
- package/dist/backend/services/custom-field.service.js.map +1 -0
- package/dist/backend/services/email-block.service.d.ts +26 -0
- package/dist/backend/services/email-block.service.d.ts.map +1 -0
- package/dist/backend/services/email-block.service.js +120 -0
- package/dist/backend/services/email-block.service.js.map +1 -0
- package/dist/backend/services/opt-in.service.d.ts +25 -0
- package/dist/backend/services/opt-in.service.d.ts.map +1 -0
- package/dist/backend/services/opt-in.service.js +46 -0
- package/dist/backend/services/opt-in.service.js.map +1 -0
- package/dist/backend/services/provider/console-provider.d.ts +32 -0
- package/dist/backend/services/provider/console-provider.d.ts.map +1 -0
- package/dist/backend/services/provider/console-provider.js +31 -0
- package/dist/backend/services/provider/console-provider.js.map +1 -0
- package/dist/backend/services/provider/provider-registry.d.ts +51 -0
- package/dist/backend/services/provider/provider-registry.d.ts.map +1 -0
- package/dist/backend/services/provider/provider-registry.js +84 -0
- package/dist/backend/services/provider/provider-registry.js.map +1 -0
- package/dist/backend/services/provider/smtp-provider.d.ts +30 -0
- package/dist/backend/services/provider/smtp-provider.d.ts.map +1 -0
- package/dist/backend/services/provider/smtp-provider.js +42 -0
- package/dist/backend/services/provider/smtp-provider.js.map +1 -0
- package/dist/backend/services/provider-admin.service.d.ts +39 -0
- package/dist/backend/services/provider-admin.service.d.ts.map +1 -0
- package/dist/backend/services/provider-admin.service.js +59 -0
- package/dist/backend/services/provider-admin.service.js.map +1 -0
- package/dist/backend/services/provider-admin.types.d.ts +6 -0
- package/dist/backend/services/provider-admin.types.d.ts.map +1 -0
- package/dist/backend/services/provider-admin.types.js +2 -0
- package/dist/backend/services/provider-admin.types.js.map +1 -0
- package/dist/backend/services/queues/newsletter-queues.d.ts +34 -0
- package/dist/backend/services/queues/newsletter-queues.d.ts.map +1 -0
- package/dist/backend/services/queues/newsletter-queues.js +58 -0
- package/dist/backend/services/queues/newsletter-queues.js.map +1 -0
- package/dist/backend/services/self.service.d.ts +17 -0
- package/dist/backend/services/self.service.d.ts.map +1 -0
- package/dist/backend/services/self.service.js +44 -0
- package/dist/backend/services/self.service.js.map +1 -0
- package/dist/backend/services/stats.service.d.ts +10 -0
- package/dist/backend/services/stats.service.d.ts.map +1 -0
- package/dist/backend/services/stats.service.js +45 -0
- package/dist/backend/services/stats.service.js.map +1 -0
- package/dist/backend/services/subscriber-admin.service.d.ts +26 -0
- package/dist/backend/services/subscriber-admin.service.d.ts.map +1 -0
- package/dist/backend/services/subscriber-admin.service.js +163 -0
- package/dist/backend/services/subscriber-admin.service.js.map +1 -0
- package/dist/backend/services/subscriber.service.d.ts +91 -0
- package/dist/backend/services/subscriber.service.d.ts.map +1 -0
- package/dist/backend/services/subscriber.service.js +280 -0
- package/dist/backend/services/subscriber.service.js.map +1 -0
- package/dist/backend/services/tag.service.d.ts +16 -0
- package/dist/backend/services/tag.service.d.ts.map +1 -0
- package/dist/backend/services/tag.service.js +79 -0
- package/dist/backend/services/tag.service.js.map +1 -0
- package/dist/backend/services/token.helper.d.ts +32 -0
- package/dist/backend/services/token.helper.d.ts.map +1 -0
- package/dist/backend/services/token.helper.js +76 -0
- package/dist/backend/services/token.helper.js.map +1 -0
- package/dist/backend/services/tracking.service.d.ts +14 -0
- package/dist/backend/services/tracking.service.d.ts.map +1 -0
- package/dist/backend/services/tracking.service.js +45 -0
- package/dist/backend/services/tracking.service.js.map +1 -0
- package/dist/manifest.d.ts +192 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +211 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260629T200954_newsletter_init.d.ts +6 -0
- package/dist/migrations/20260629T200954_newsletter_init.d.ts.map +1 -0
- package/dist/migrations/20260629T200954_newsletter_init.js +261 -0
- package/dist/migrations/20260629T200954_newsletter_init.js.map +1 -0
- package/dist/migrations/20260830T212736_newsletter_organization_attribution.d.ts +116 -0
- package/dist/migrations/20260830T212736_newsletter_organization_attribution.d.ts.map +1 -0
- package/dist/migrations/20260830T212736_newsletter_organization_attribution.js +186 -0
- package/dist/migrations/20260830T212736_newsletter_organization_attribution.js.map +1 -0
- package/dist/migrations/20260903T101752_newsletter_namespace_block_names.d.ts +6 -0
- package/dist/migrations/20260903T101752_newsletter_namespace_block_names.d.ts.map +1 -0
- package/dist/migrations/20260903T101752_newsletter_namespace_block_names.js +58 -0
- package/dist/migrations/20260903T101752_newsletter_namespace_block_names.js.map +1 -0
- package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.d.ts +23 -0
- package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.d.ts.map +1 -0
- package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.js +27 -0
- package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.js.map +1 -0
- package/dist/migrations/index.d.ts +30 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +35 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/newsletter.md +94 -0
- package/i18n/en.json +27 -0
- package/i18n/pl.json +27 -0
- package/package.json +119 -0
- package/tailwind.css +14 -0
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { defineModuleManifest, defineModuleSettingsManifest } from '@endora-commerce/contracts';
|
|
2
|
+
import { NEWSLETTER_SETTING_CODES } from '@endora-commerce/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* Newsletter module (feature 048). Owns the subscriber list, tags, custom
|
|
5
|
+
* fields, campaigns, linear automations, reusable email-safe blocks, the
|
|
6
|
+
* bulk-sending provider (SMTP / Amazon SES via nodemailer), and engagement
|
|
7
|
+
* tracking. Bulk delivery is independent of the transactional-email transport.
|
|
8
|
+
* The SMTP connection + credentials come from a reusable `email_adapter`
|
|
9
|
+
* credential configuration referenced by `newsletter.email_credentials`
|
|
10
|
+
* (feature 058); when unset, dispatch uses the console (dev) sink.
|
|
11
|
+
*/
|
|
12
|
+
export const newsletterSettingsManifest = defineModuleSettingsManifest({
|
|
13
|
+
moduleCode: 'newsletter',
|
|
14
|
+
groups: [{ code: 'newsletter', name: 'Newsletter' }],
|
|
15
|
+
settings: [
|
|
16
|
+
{
|
|
17
|
+
// Feature 073 — the operator's activation control. Platform-wide.
|
|
18
|
+
code: 'newsletter.enabled',
|
|
19
|
+
name: 'Newsletter enabled',
|
|
20
|
+
description: 'Switches the newsletter on or off: the storefront signup and confirmation links, the admin subscriber, campaign and automation screens, and bulk dispatch. Nothing is dropped — subscribers, their consent history, campaigns and stats stay in the database, and a campaign left mid-send resumes where it stopped.',
|
|
21
|
+
groupCode: 'newsletter',
|
|
22
|
+
valueType: 'boolean',
|
|
23
|
+
defaultValue: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
code: NEWSLETTER_SETTING_CODES.OPT_IN_MODE,
|
|
27
|
+
name: 'Opt-in mode',
|
|
28
|
+
description: 'Single (immediate) or double (confirmation email) opt-in. Scopable per sales channel.',
|
|
29
|
+
groupCode: 'newsletter',
|
|
30
|
+
valueType: 'string',
|
|
31
|
+
enumOptions: ['single', 'double'],
|
|
32
|
+
defaultValue: 'double',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: NEWSLETTER_SETTING_CODES.CONFIRM_TTL_HOURS,
|
|
36
|
+
name: 'Confirmation link TTL (hours)',
|
|
37
|
+
description: 'How long a double opt-in confirmation link stays valid before the pending subscriber expires.',
|
|
38
|
+
groupCode: 'newsletter',
|
|
39
|
+
valueType: 'number',
|
|
40
|
+
defaultValue: 168,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
code: NEWSLETTER_SETTING_CODES.EMAIL_CREDENTIALS,
|
|
44
|
+
name: 'Email credentials',
|
|
45
|
+
description: 'Reference a reusable Email adapter credential configuration (Credentials screen) providing the SMTP host/port/security/username/password. Required to send real mail; when empty, newsletter dispatch uses the console (dev) sink.',
|
|
46
|
+
groupCode: 'newsletter',
|
|
47
|
+
valueType: 'credential_ref',
|
|
48
|
+
configurationType: 'email_adapter',
|
|
49
|
+
defaultValue: '',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: NEWSLETTER_SETTING_CODES.SENDER_FROM_EMAIL,
|
|
53
|
+
name: 'Sender email',
|
|
54
|
+
description: 'Verified From address for newsletter mail.',
|
|
55
|
+
groupCode: 'newsletter',
|
|
56
|
+
valueType: 'string',
|
|
57
|
+
defaultValue: '',
|
|
58
|
+
hidden: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
code: NEWSLETTER_SETTING_CODES.SENDER_FROM_NAME,
|
|
62
|
+
name: 'Sender name',
|
|
63
|
+
description: 'Display name shown in the From header.',
|
|
64
|
+
groupCode: 'newsletter',
|
|
65
|
+
valueType: 'string',
|
|
66
|
+
defaultValue: '',
|
|
67
|
+
hidden: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
code: NEWSLETTER_SETTING_CODES.RATE_LIMIT_PER_SECOND,
|
|
71
|
+
name: 'Send rate limit (per second)',
|
|
72
|
+
description: 'Maximum messages dispatched per second (provider throttle).',
|
|
73
|
+
groupCode: 'newsletter',
|
|
74
|
+
valueType: 'number',
|
|
75
|
+
defaultValue: 14,
|
|
76
|
+
hidden: true,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
export const manifest = defineModuleManifest({
|
|
81
|
+
id: 'newsletter',
|
|
82
|
+
name: 'Newsletter',
|
|
83
|
+
description: 'Own-infrastructure newsletter: subscribers with tags + custom fields, campaigns, linear automations, email-safe templates and variables, engagement stats, and a configurable bulk-sending provider.',
|
|
84
|
+
version: '1.0.0',
|
|
85
|
+
// `cms` owns `cmsBlockSeedPort`, the seam this module's registration-consent
|
|
86
|
+
// block is kept in place through (feature 075 / D-87). Declared here rather
|
|
87
|
+
// than withheld as non-binding because the edge is binding in the direction
|
|
88
|
+
// that matters to an operator: the consent label the storefront renders on
|
|
89
|
+
// the registration form *is* that block, and a `newsletter` with no consent
|
|
90
|
+
// text to show is not a reduced newsletter but a form that cannot lawfully
|
|
91
|
+
// collect the consent.
|
|
92
|
+
dependencies: [
|
|
93
|
+
'audit_logs',
|
|
94
|
+
'auth',
|
|
95
|
+
'cms',
|
|
96
|
+
'credentials',
|
|
97
|
+
'customers',
|
|
98
|
+
'email',
|
|
99
|
+
'sales_channels',
|
|
100
|
+
'settings',
|
|
101
|
+
],
|
|
102
|
+
settings: newsletterSettingsManifest,
|
|
103
|
+
activation: { settingCode: 'newsletter.enabled', default: true },
|
|
104
|
+
/**
|
|
105
|
+
* `ALREADY_SUBSCRIBED` — D-129's remaining sweep, MR 4
|
|
106
|
+
* (`specs/090-module-owned-error-codes/d129-sweep.md` §5.2, Appendix A;
|
|
107
|
+
* D-186 in `specs/080-f4-real-scope/rulings.md`). The first error code this
|
|
108
|
+
* module declares.
|
|
109
|
+
*
|
|
110
|
+
* It was `_i18n`'s until now, not because anybody judged it the platform's
|
|
111
|
+
* but because the deleted prefix chain had no rule for it and its last line
|
|
112
|
+
* was `return 'core'`. **D-121 T1 puts it here**: the noun is a subscription,
|
|
113
|
+
* and `NewsletterSubscriber` — with the double opt-in that decides when a
|
|
114
|
+
* second sign-up is a duplicate rather than a re-confirmation — is this
|
|
115
|
+
* module's own entity.
|
|
116
|
+
*
|
|
117
|
+
* **The attribution is the sweep's own word "weakly", and the reason to
|
|
118
|
+
* record it here is that the tree holds one counter-signal.** Nothing raises
|
|
119
|
+
* this code anywhere (class D). The only place it is named outside a
|
|
120
|
+
* declaration is a doc comment in `inventory`'s
|
|
121
|
+
* `availability-notification-service.ts`, which says the restock subscribe
|
|
122
|
+
* refuses `409 ALREADY_SUBSCRIBED` for an idempotent re-subscribe — and that
|
|
123
|
+
* method does not refuse at all: it returns the existing row
|
|
124
|
+
* (`if (existing) return existing;`). So the comment describes a refusal that
|
|
125
|
+
* does not exist, and the one competing claim on the noun is a claim nothing
|
|
126
|
+
* implements. If a back-in-stock subscription ever does start refusing, the
|
|
127
|
+
* question is open again and `REHOMED_ERROR_CODES` is where this decision is
|
|
128
|
+
* written down.
|
|
129
|
+
*
|
|
130
|
+
* **It arrives without a sentence, deliberately.** It carried a placeholder
|
|
131
|
+
* in `_i18n`'s bundle, the code rewritten twice —
|
|
132
|
+
* `"Already Subscribed."` and `"Błąd: already subscribed."` — which D-186 §2
|
|
133
|
+
* deletes rather than moves: in this module's own bundle it would read as
|
|
134
|
+
* this module's
|
|
135
|
+
* answer and every instrument would count the code as translated for good.
|
|
136
|
+
* `d129-sweep.md` §5.4 keeps writing real prose available and calls it the
|
|
137
|
+
* better outcome, and it is available whenever a raise site says what the
|
|
138
|
+
* refusal means. There is no raise site here, so a sentence could only be
|
|
139
|
+
* invented from the code's own name — the placeholder again in longer words,
|
|
140
|
+
* rendered for nobody. It is a `check-error-translations.ts`
|
|
141
|
+
* `UNTRANSLATED_ERROR_CODES` entry under `newsletter` instead, where the debt
|
|
142
|
+
* is findable and attached to whoever implements the refusal.
|
|
143
|
+
*
|
|
144
|
+
* No `tokens`, for the same reason and from the same direction: there is no
|
|
145
|
+
* raise site to put a `details.code` on the wire.
|
|
146
|
+
*/
|
|
147
|
+
errorCodes: [{ code: 'ALREADY_SUBSCRIBED' }],
|
|
148
|
+
i18n: { bundlesDir: 'i18n' },
|
|
149
|
+
docs: { dir: 'docs' },
|
|
150
|
+
permissions: [
|
|
151
|
+
{ code: 'newsletter:read', label: 'View newsletter' },
|
|
152
|
+
{
|
|
153
|
+
code: 'newsletter:write',
|
|
154
|
+
label: 'Manage newsletter subscribers, campaigns, automations, and provider',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
actions: [
|
|
158
|
+
{
|
|
159
|
+
id: 'open-newsletter',
|
|
160
|
+
labelKey: 'actions.openNewsletter.label',
|
|
161
|
+
descriptionKey: 'actions.openNewsletter.description',
|
|
162
|
+
icon: 'Inbox',
|
|
163
|
+
targetRoute: '/newsletter/subscribers',
|
|
164
|
+
requiredPermission: 'newsletter:read',
|
|
165
|
+
keywords: ['newsletter', 'campaign', 'subscribers', 'marketing', 'automation'],
|
|
166
|
+
weight: 230,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'new-newsletter-campaign',
|
|
170
|
+
labelKey: 'actions.newCampaign.label',
|
|
171
|
+
descriptionKey: 'actions.newCampaign.description',
|
|
172
|
+
icon: 'Plus',
|
|
173
|
+
targetRoute: '/newsletter/campaigns/new',
|
|
174
|
+
requiredPermission: 'newsletter:write',
|
|
175
|
+
keywords: ['newsletter', 'campaign', 'new', 'create', 'send'],
|
|
176
|
+
weight: 231,
|
|
177
|
+
},
|
|
178
|
+
/**
|
|
179
|
+
* The two `AppShell.tsx` `PALETTE_ITEMS` rows feature 091's batch 11
|
|
180
|
+
* deletes, arriving as declarations (Principle XVI).
|
|
181
|
+
*
|
|
182
|
+
* Same destinations, same codes and the same keywords the hand-written
|
|
183
|
+
* rows carried, English and Polish alike — the palette an operator sees is
|
|
184
|
+
* unchanged. What changes is who answers for it: a hand-written row is a
|
|
185
|
+
* copy the server was never asked about, so it went on advertising these
|
|
186
|
+
* two screens after an operator switched `newsletter` off, and this module
|
|
187
|
+
* is one that really can be switched off.
|
|
188
|
+
*/
|
|
189
|
+
{
|
|
190
|
+
id: 'open-newsletter-campaigns',
|
|
191
|
+
labelKey: 'actions.openCampaigns.label',
|
|
192
|
+
descriptionKey: 'actions.openCampaigns.description',
|
|
193
|
+
icon: 'Inbox',
|
|
194
|
+
targetRoute: '/newsletter/campaigns',
|
|
195
|
+
requiredPermission: 'newsletter:read',
|
|
196
|
+
keywords: ['newsletter', 'campaigns', 'email', 'marketing', 'kampanie'],
|
|
197
|
+
weight: 232,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'open-newsletter-automations',
|
|
201
|
+
labelKey: 'actions.openAutomations.label',
|
|
202
|
+
descriptionKey: 'actions.openAutomations.description',
|
|
203
|
+
icon: 'Inbox',
|
|
204
|
+
targetRoute: '/newsletter/automations',
|
|
205
|
+
requiredPermission: 'newsletter:read',
|
|
206
|
+
keywords: ['newsletter', 'automations', 'workflow', 'automatyzacje'],
|
|
207
|
+
weight: 233,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260629T200954_newsletter_init.d.ts","sourceRoot":"","sources":["../../src/migrations/20260629T200954_newsletter_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAuBlD,qBAAa,sCAAuC,SAAQ,SAAS;IACpD,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAiRnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAerC"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { defaultHeaderTree } from '@endora-commerce/email-components/defaults/default-header';
|
|
4
|
+
import { defaultFooterTree, envelopeFromTree } from '@endora-commerce/email-components/defaults/default-footer';
|
|
5
|
+
/**
|
|
6
|
+
* Feature 048 — Newsletter.
|
|
7
|
+
*
|
|
8
|
+
* Creates the full module schema: subscribers + tags + custom fields +
|
|
9
|
+
* suppressions, reusable email blocks (+ channel bridge), campaigns (+ group
|
|
10
|
+
* bridge), per-recipient send records (with the atomic-claim partial unique
|
|
11
|
+
* indexes that make dispatch idempotent under N≥2 workers — Principle X),
|
|
12
|
+
* engagement events, and automations (+ runs, with the `once` re-entry partial
|
|
13
|
+
* unique index). Seeds the system default header/footer blocks (FR-020/026).
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_LANGUAGES = ['en-US', 'pl-PL'];
|
|
16
|
+
const NEWSLETTER_DEFAULT_HEADER_CODE = 'newsletter_default_header';
|
|
17
|
+
const NEWSLETTER_DEFAULT_FOOTER_CODE = 'newsletter_default_footer';
|
|
18
|
+
function jsonbLiteral(value) {
|
|
19
|
+
return JSON.stringify(value).replace(/'/g, "''");
|
|
20
|
+
}
|
|
21
|
+
export class Migration20260629T200954NewsletterInit extends Migration {
|
|
22
|
+
async up() {
|
|
23
|
+
// --- Subscribers -------------------------------------------------------
|
|
24
|
+
this.addSql(`
|
|
25
|
+
create table "newsletter_subscribers" (
|
|
26
|
+
"id" uuid not null,
|
|
27
|
+
"email" varchar(320) not null,
|
|
28
|
+
"status" varchar(16) not null default 'pending',
|
|
29
|
+
"source" varchar(64) null,
|
|
30
|
+
"sales_channel_id" uuid null,
|
|
31
|
+
"customer_account_id" uuid null,
|
|
32
|
+
"custom_fields" jsonb not null default '{}',
|
|
33
|
+
"consent_at" timestamptz null,
|
|
34
|
+
"confirmed_at" timestamptz null,
|
|
35
|
+
"unsubscribed_at" timestamptz null,
|
|
36
|
+
"unsubscribe_reason" text null,
|
|
37
|
+
"deactivated_at" timestamptz null,
|
|
38
|
+
"version" int not null default 1,
|
|
39
|
+
"created_at" timestamptz not null,
|
|
40
|
+
"updated_at" timestamptz not null,
|
|
41
|
+
constraint "newsletter_subscribers_pkey" primary key ("id")
|
|
42
|
+
);
|
|
43
|
+
`);
|
|
44
|
+
this.addSql(`alter table "newsletter_subscribers" add constraint "newsletter_subscribers_email_unique" unique ("email");`);
|
|
45
|
+
this.addSql(`create index "newsletter_subscribers_status_idx" on "newsletter_subscribers" ("status");`);
|
|
46
|
+
this.addSql(`create index "newsletter_subscribers_channel_idx" on "newsletter_subscribers" ("sales_channel_id");`);
|
|
47
|
+
this.addSql(`create index "newsletter_subscribers_custom_fields_idx" on "newsletter_subscribers" using gin ("custom_fields");`);
|
|
48
|
+
// --- Tags + bridge -----------------------------------------------------
|
|
49
|
+
this.addSql(`
|
|
50
|
+
create table "newsletter_tags" (
|
|
51
|
+
"id" uuid not null,
|
|
52
|
+
"code" varchar(64) not null,
|
|
53
|
+
"name" varchar(160) not null,
|
|
54
|
+
"description" text null,
|
|
55
|
+
"created_at" timestamptz not null,
|
|
56
|
+
"updated_at" timestamptz not null,
|
|
57
|
+
constraint "newsletter_tags_pkey" primary key ("id")
|
|
58
|
+
);
|
|
59
|
+
`);
|
|
60
|
+
this.addSql(`alter table "newsletter_tags" add constraint "newsletter_tags_code_unique" unique ("code");`);
|
|
61
|
+
this.addSql(`
|
|
62
|
+
create table "newsletter_subscriber_tags" (
|
|
63
|
+
"subscriber_id" uuid not null,
|
|
64
|
+
"tag_id" uuid not null,
|
|
65
|
+
constraint "newsletter_subscriber_tags_pkey" primary key ("subscriber_id", "tag_id")
|
|
66
|
+
);
|
|
67
|
+
`);
|
|
68
|
+
this.addSql(`alter table "newsletter_subscriber_tags" add constraint "nst_subscriber_fk" ` +
|
|
69
|
+
`foreign key ("subscriber_id") references "newsletter_subscribers" ("id") on delete cascade;`);
|
|
70
|
+
this.addSql(`alter table "newsletter_subscriber_tags" add constraint "nst_tag_fk" ` +
|
|
71
|
+
`foreign key ("tag_id") references "newsletter_tags" ("id") on delete cascade;`);
|
|
72
|
+
this.addSql(`create index "nst_tag_idx" on "newsletter_subscriber_tags" ("tag_id");`);
|
|
73
|
+
// --- Custom fields -----------------------------------------------------
|
|
74
|
+
this.addSql(`
|
|
75
|
+
create table "newsletter_custom_fields" (
|
|
76
|
+
"id" uuid not null,
|
|
77
|
+
"key" varchar(64) not null,
|
|
78
|
+
"label" varchar(160) not null,
|
|
79
|
+
"type" varchar(16) not null,
|
|
80
|
+
"created_at" timestamptz not null,
|
|
81
|
+
"updated_at" timestamptz not null,
|
|
82
|
+
constraint "newsletter_custom_fields_pkey" primary key ("id")
|
|
83
|
+
);
|
|
84
|
+
`);
|
|
85
|
+
this.addSql(`alter table "newsletter_custom_fields" add constraint "newsletter_custom_fields_key_unique" unique ("key");`);
|
|
86
|
+
// --- Suppressions ------------------------------------------------------
|
|
87
|
+
this.addSql(`
|
|
88
|
+
create table "newsletter_suppressions" (
|
|
89
|
+
"id" uuid not null,
|
|
90
|
+
"email" varchar(320) not null,
|
|
91
|
+
"reason" varchar(16) not null,
|
|
92
|
+
"detail" text null,
|
|
93
|
+
"created_at" timestamptz not null,
|
|
94
|
+
constraint "newsletter_suppressions_pkey" primary key ("id")
|
|
95
|
+
);
|
|
96
|
+
`);
|
|
97
|
+
this.addSql(`alter table "newsletter_suppressions" add constraint "newsletter_suppressions_email_unique" unique ("email");`);
|
|
98
|
+
// --- Email blocks + bridge ---------------------------------------------
|
|
99
|
+
this.addSql(`
|
|
100
|
+
create table "newsletter_email_blocks" (
|
|
101
|
+
"id" uuid not null,
|
|
102
|
+
"code" varchar(180) not null,
|
|
103
|
+
"name" varchar(200) not null,
|
|
104
|
+
"description" text null,
|
|
105
|
+
"active" boolean not null default true,
|
|
106
|
+
"content" jsonb not null default '{}',
|
|
107
|
+
"is_system" boolean not null default false,
|
|
108
|
+
"version" int not null default 1,
|
|
109
|
+
"created_at" timestamptz not null,
|
|
110
|
+
"updated_at" timestamptz not null,
|
|
111
|
+
constraint "newsletter_email_blocks_pkey" primary key ("id")
|
|
112
|
+
);
|
|
113
|
+
`);
|
|
114
|
+
this.addSql(`create index "newsletter_email_blocks_code_idx" on "newsletter_email_blocks" ("code");`);
|
|
115
|
+
this.addSql(`
|
|
116
|
+
create table "newsletter_email_block_sales_channels" (
|
|
117
|
+
"block_id" uuid not null,
|
|
118
|
+
"sales_channel_id" uuid not null,
|
|
119
|
+
"code" varchar(180) not null,
|
|
120
|
+
constraint "newsletter_ebsc_pkey" primary key ("block_id", "sales_channel_id")
|
|
121
|
+
);
|
|
122
|
+
`);
|
|
123
|
+
this.addSql(`alter table "newsletter_email_block_sales_channels" add constraint "newsletter_ebsc_block_fk" ` +
|
|
124
|
+
`foreign key ("block_id") references "newsletter_email_blocks" ("id") on delete cascade;`);
|
|
125
|
+
this.addSql(`create unique index "newsletter_ebsc_channel_code_unique" on "newsletter_email_block_sales_channels" ("sales_channel_id", "code");`);
|
|
126
|
+
// --- Campaigns + group bridge ------------------------------------------
|
|
127
|
+
this.addSql(`
|
|
128
|
+
create table "newsletter_campaigns" (
|
|
129
|
+
"id" uuid not null,
|
|
130
|
+
"name" varchar(200) not null,
|
|
131
|
+
"subject" text not null,
|
|
132
|
+
"content" jsonb not null default '{}',
|
|
133
|
+
"sales_channel_id" uuid null,
|
|
134
|
+
"language" varchar(12) not null,
|
|
135
|
+
"target_type" varchar(16) not null default 'all',
|
|
136
|
+
"target_tag_ids" jsonb not null default '[]',
|
|
137
|
+
"tracking_enabled" boolean not null default true,
|
|
138
|
+
"status" varchar(16) not null default 'draft',
|
|
139
|
+
"scheduled_at" timestamptz null,
|
|
140
|
+
"dispatch_job_id" varchar(128) null,
|
|
141
|
+
"stats" jsonb not null default '{}',
|
|
142
|
+
"version" int not null default 1,
|
|
143
|
+
"created_at" timestamptz not null,
|
|
144
|
+
"updated_at" timestamptz not null,
|
|
145
|
+
constraint "newsletter_campaigns_pkey" primary key ("id")
|
|
146
|
+
);
|
|
147
|
+
`);
|
|
148
|
+
this.addSql(`create index "newsletter_campaigns_status_idx" on "newsletter_campaigns" ("status");`);
|
|
149
|
+
this.addSql(`
|
|
150
|
+
create table "newsletter_campaign_subscribers" (
|
|
151
|
+
"campaign_id" uuid not null,
|
|
152
|
+
"subscriber_id" uuid not null,
|
|
153
|
+
constraint "newsletter_campaign_subscribers_pkey" primary key ("campaign_id", "subscriber_id")
|
|
154
|
+
);
|
|
155
|
+
`);
|
|
156
|
+
this.addSql(`alter table "newsletter_campaign_subscribers" add constraint "ncs_campaign_fk" ` +
|
|
157
|
+
`foreign key ("campaign_id") references "newsletter_campaigns" ("id") on delete cascade;`);
|
|
158
|
+
this.addSql(`alter table "newsletter_campaign_subscribers" add constraint "ncs_subscriber_fk" ` +
|
|
159
|
+
`foreign key ("subscriber_id") references "newsletter_subscribers" ("id") on delete cascade;`);
|
|
160
|
+
// --- Automations + runs ------------------------------------------------
|
|
161
|
+
this.addSql(`
|
|
162
|
+
create table "newsletter_automations" (
|
|
163
|
+
"id" uuid not null,
|
|
164
|
+
"name" varchar(200) not null,
|
|
165
|
+
"status" varchar(16) not null default 'draft',
|
|
166
|
+
"trigger_type" varchar(16) not null default 'all',
|
|
167
|
+
"trigger_tag_ids" jsonb not null default '[]',
|
|
168
|
+
"sales_channel_id" uuid null,
|
|
169
|
+
"language" varchar(12) not null,
|
|
170
|
+
"reentry_policy" varchar(16) not null default 'once',
|
|
171
|
+
"steps" jsonb not null default '[]',
|
|
172
|
+
"version" int not null default 1,
|
|
173
|
+
"created_at" timestamptz not null,
|
|
174
|
+
"updated_at" timestamptz not null,
|
|
175
|
+
constraint "newsletter_automations_pkey" primary key ("id")
|
|
176
|
+
);
|
|
177
|
+
`);
|
|
178
|
+
this.addSql(`
|
|
179
|
+
create table "newsletter_automation_runs" (
|
|
180
|
+
"id" uuid not null,
|
|
181
|
+
"automation_id" uuid not null,
|
|
182
|
+
"subscriber_id" uuid not null,
|
|
183
|
+
"current_step" int not null default 0,
|
|
184
|
+
"status" varchar(16) not null default 'active',
|
|
185
|
+
"next_step_at" timestamptz null,
|
|
186
|
+
"next_step_job_id" varchar(128) null,
|
|
187
|
+
"created_at" timestamptz not null,
|
|
188
|
+
"updated_at" timestamptz not null,
|
|
189
|
+
constraint "newsletter_automation_runs_pkey" primary key ("id")
|
|
190
|
+
);
|
|
191
|
+
`);
|
|
192
|
+
this.addSql(`alter table "newsletter_automation_runs" add constraint "nar_automation_fk" ` +
|
|
193
|
+
`foreign key ("automation_id") references "newsletter_automations" ("id") on delete cascade;`);
|
|
194
|
+
this.addSql(`alter table "newsletter_automation_runs" add constraint "nar_subscriber_fk" ` +
|
|
195
|
+
`foreign key ("subscriber_id") references "newsletter_subscribers" ("id") on delete cascade;`);
|
|
196
|
+
this.addSql(`create index "nar_automation_idx" on "newsletter_automation_runs" ("automation_id");`);
|
|
197
|
+
this.addSql(`create unique index "nar_once_unique" on "newsletter_automation_runs" ("automation_id", "subscriber_id") where "status" <> 'cancelled';`);
|
|
198
|
+
// --- Send records (atomic claim) + engagement events -------------------
|
|
199
|
+
this.addSql(`
|
|
200
|
+
create table "newsletter_send_records" (
|
|
201
|
+
"id" uuid not null,
|
|
202
|
+
"campaign_id" uuid null,
|
|
203
|
+
"automation_run_id" uuid null,
|
|
204
|
+
"step_index" int null,
|
|
205
|
+
"subscriber_id" uuid not null,
|
|
206
|
+
"status" varchar(16) not null default 'queued',
|
|
207
|
+
"provider_message_id" varchar(255) null,
|
|
208
|
+
"error" text null,
|
|
209
|
+
"opened_at" timestamptz null,
|
|
210
|
+
"click_count" int not null default 0,
|
|
211
|
+
"sent_at" timestamptz null,
|
|
212
|
+
"created_at" timestamptz not null,
|
|
213
|
+
constraint "newsletter_send_records_pkey" primary key ("id")
|
|
214
|
+
);
|
|
215
|
+
`);
|
|
216
|
+
this.addSql(`alter table "newsletter_send_records" add constraint "nsr_subscriber_fk" ` +
|
|
217
|
+
`foreign key ("subscriber_id") references "newsletter_subscribers" ("id") on delete cascade;`);
|
|
218
|
+
this.addSql(`create index "nsr_subscriber_idx" on "newsletter_send_records" ("subscriber_id");`);
|
|
219
|
+
this.addSql(`create index "nsr_status_idx" on "newsletter_send_records" ("status");`);
|
|
220
|
+
this.addSql(`create unique index "nsr_campaign_claim_unique" on "newsletter_send_records" ("campaign_id", "subscriber_id") where "campaign_id" is not null;`);
|
|
221
|
+
this.addSql(`create unique index "nsr_automation_claim_unique" on "newsletter_send_records" ("automation_run_id", "step_index", "subscriber_id") where "automation_run_id" is not null;`);
|
|
222
|
+
this.addSql(`
|
|
223
|
+
create table "newsletter_engagement_events" (
|
|
224
|
+
"id" uuid not null,
|
|
225
|
+
"send_record_id" uuid not null,
|
|
226
|
+
"type" varchar(8) not null,
|
|
227
|
+
"link_id" varchar(64) null,
|
|
228
|
+
"url" text null,
|
|
229
|
+
"occurred_at" timestamptz not null,
|
|
230
|
+
constraint "newsletter_engagement_events_pkey" primary key ("id")
|
|
231
|
+
);
|
|
232
|
+
`);
|
|
233
|
+
this.addSql(`alter table "newsletter_engagement_events" add constraint "nee_send_record_fk" ` +
|
|
234
|
+
`foreign key ("send_record_id") references "newsletter_send_records" ("id") on delete cascade;`);
|
|
235
|
+
this.addSql(`create index "nee_send_record_idx" on "newsletter_engagement_events" ("send_record_id");`);
|
|
236
|
+
this.addSql(`create index "nee_type_idx" on "newsletter_engagement_events" ("type");`);
|
|
237
|
+
// --- Seed system default header + footer blocks (FR-020/026) -----------
|
|
238
|
+
const header = envelopeFromTree(defaultHeaderTree(), DEFAULT_LANGUAGES);
|
|
239
|
+
const footer = envelopeFromTree(defaultFooterTree(), DEFAULT_LANGUAGES);
|
|
240
|
+
this.addSql(`insert into "newsletter_email_blocks" ("id", "code", "name", "active", "content", "is_system", "version", "created_at", "updated_at") ` +
|
|
241
|
+
`values ('${randomUUID()}', '${NEWSLETTER_DEFAULT_HEADER_CODE}', 'Newsletter default header', true, '${jsonbLiteral(header)}'::jsonb, true, 1, now(), now());`);
|
|
242
|
+
this.addSql(`insert into "newsletter_email_blocks" ("id", "code", "name", "active", "content", "is_system", "version", "created_at", "updated_at") ` +
|
|
243
|
+
`values ('${randomUUID()}', '${NEWSLETTER_DEFAULT_FOOTER_CODE}', 'Newsletter default footer', true, '${jsonbLiteral(footer)}'::jsonb, true, 1, now(), now());`);
|
|
244
|
+
}
|
|
245
|
+
async down() {
|
|
246
|
+
this.addSql('drop table if exists "newsletter_engagement_events" cascade;');
|
|
247
|
+
this.addSql('drop table if exists "newsletter_send_records" cascade;');
|
|
248
|
+
this.addSql('drop table if exists "newsletter_automation_runs" cascade;');
|
|
249
|
+
this.addSql('drop table if exists "newsletter_automations" cascade;');
|
|
250
|
+
this.addSql('drop table if exists "newsletter_campaign_subscribers" cascade;');
|
|
251
|
+
this.addSql('drop table if exists "newsletter_campaigns" cascade;');
|
|
252
|
+
this.addSql('drop table if exists "newsletter_email_block_sales_channels" cascade;');
|
|
253
|
+
this.addSql('drop table if exists "newsletter_email_blocks" cascade;');
|
|
254
|
+
this.addSql('drop table if exists "newsletter_suppressions" cascade;');
|
|
255
|
+
this.addSql('drop table if exists "newsletter_custom_fields" cascade;');
|
|
256
|
+
this.addSql('drop table if exists "newsletter_subscriber_tags" cascade;');
|
|
257
|
+
this.addSql('drop table if exists "newsletter_tags" cascade;');
|
|
258
|
+
this.addSql('drop table if exists "newsletter_subscribers" cascade;');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=20260629T200954_newsletter_init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260629T200954_newsletter_init.js","sourceRoot":"","sources":["../../src/migrations/20260629T200954_newsletter_init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* D-187 — `newsletter_subscribers` gains its organisation, and a row that names
|
|
4
|
+
* a customer account carries one.
|
|
5
|
+
*
|
|
6
|
+
* Feature 087 Group B, class 4 of 4 and the last of the five
|
|
7
|
+
* (`specs/087-tenant-scope-enforcement/`). `NewsletterSubscriber` is
|
|
8
|
+
* `@CustomerScoped`, and until this migration the `allowed-set` arm of
|
|
9
|
+
* `customerFilterCond` had no column to grant on: a sales representative
|
|
10
|
+
* assigned to the buyer's own organisation was shown **none** of their
|
|
11
|
+
* subscribers. The column is what turns that into an answer.
|
|
12
|
+
*
|
|
13
|
+
* ## The column and the read arrive together, on purpose
|
|
14
|
+
*
|
|
15
|
+
* `customerOrganizationColumn` asks the ORM's own metadata whether the filtered
|
|
16
|
+
* entity carries `organizationId`, **per query**, so the entity property in
|
|
17
|
+
* this same merge request is what switches the grant on — there is no third
|
|
18
|
+
* artefact, no flag and no staging. At the same instant the
|
|
19
|
+
* `ORGANIZATION_ATTRIBUTION_PENDING` disclosure retires itself: it is keyed on
|
|
20
|
+
* the column's *absence*.
|
|
21
|
+
*
|
|
22
|
+
* That pairing is why this migration is not "column and backfill". From the
|
|
23
|
+
* moment the grant is live, a row inserted without an organisation is invisible
|
|
24
|
+
* to the representative who serves that organisation, on a screen that has just
|
|
25
|
+
* stopped explaining itself — and MikroORM applies no filter to `INSERT`
|
|
26
|
+
* (`r1-spike.md` §4, measured), so the filter cannot refuse it. The `CHECK`
|
|
27
|
+
* below is the only refusal an `INSERT` has.
|
|
28
|
+
*
|
|
29
|
+
* ## An implication, not an equivalence
|
|
30
|
+
*
|
|
31
|
+
* `customer_account_id is null or organization_id is not null` is FR-010 and
|
|
32
|
+
* FR-011 together and nothing wider: an **owned** row has an organisation, an
|
|
33
|
+
* **ownerless** row need not. This table is ownerless by construction for the
|
|
34
|
+
* storefront's sign-up form — `POST /api/v1/newsletter/subscribe` passes no
|
|
35
|
+
* account at all, so every subscriber who has never signed in is such a row,
|
|
36
|
+
* and on this table they are the majority rather than the exception. Who they
|
|
37
|
+
* belong to is R-6's open question. The equivalence would answer it in the
|
|
38
|
+
* schema, which D-187 declines to do.
|
|
39
|
+
*
|
|
40
|
+
* The shape the implication leaves open — an **ownerless** row still carrying
|
|
41
|
+
* an organisation — is unreachable here, for `inventory`'s reason rather than
|
|
42
|
+
* `pwa`'s. `subscriber.service.ts` writes `customer_account_id` in exactly two
|
|
43
|
+
* places and both of them *acquire* an owner: the `em.create` on first
|
|
44
|
+
* subscribe, and the adoption line that fills in the account when a subscriber
|
|
45
|
+
* who signed up anonymously later signs in. Nothing in this module ever clears
|
|
46
|
+
* it (grep of `packages/modules/newsletter/src` for `customerAccountId`: two
|
|
47
|
+
* writes, both assignments of a non-null id, the rest reads). So there is no
|
|
48
|
+
* de-association direction for the constraint to be silent about.
|
|
49
|
+
*
|
|
50
|
+
* ## The e-mail address is not a derivation
|
|
51
|
+
*
|
|
52
|
+
* `newsletter_subscribers.email` is globally unique and `customer_accounts`
|
|
53
|
+
* has an `email` too, so the backfill below could have matched them and
|
|
54
|
+
* attributed far more rows. It deliberately does not. That match would claim
|
|
55
|
+
* rows the schema does not link, silently and irreversibly, for whoever
|
|
56
|
+
* happened to sign up with the address their employer later registered — it is
|
|
57
|
+
* R-6 answer (2) guessed rather than decided. The only derivation is
|
|
58
|
+
* `customer_account_id`.
|
|
59
|
+
*
|
|
60
|
+
* ## No foreign key
|
|
61
|
+
*
|
|
62
|
+
* Matching `Cart`, `Comparison`, `PushSubscription` and
|
|
63
|
+
* `AvailabilityNotification`, none of which carries one. D-187 withdraws
|
|
64
|
+
* `r1-spike.md` §8's `on delete set null` recommendation as contradicting
|
|
65
|
+
* FR-011: it produces a row that names an account and no organisation, which is
|
|
66
|
+
* precisely the state this migration makes unreachable, and under the
|
|
67
|
+
* constraint below it would abort an unrelated organisation delete with a
|
|
68
|
+
* message about a table the operator was not touching. If one is ever taken
|
|
69
|
+
* here it must be `restrict`, and it is not this migration's decision.
|
|
70
|
+
*
|
|
71
|
+
* ## Derive, then count, then refuse — never delete
|
|
72
|
+
*
|
|
73
|
+
* The derivation is the owning account's own organisation. **On this table the
|
|
74
|
+
* refusal is a real branch, not a formality**: like `push_subscriptions` and
|
|
75
|
+
* `availability_notifications`, and unlike `comparisons`,
|
|
76
|
+
* `newsletter_subscribers` has **no foreign key** on `customer_account_id` —
|
|
77
|
+
* `20260629T200954_newsletter_init.ts` declares it `uuid null` and constrains
|
|
78
|
+
* nothing — so a row naming an account that is gone is representable here.
|
|
79
|
+
*
|
|
80
|
+
* It is nevertheless empty today, and the reason is worth knowing rather than
|
|
81
|
+
* hoping: a customer account is never hard-deleted in this tree.
|
|
82
|
+
* `customer-account-lifecycle-ports.ts` soft-deletes, restores and
|
|
83
|
+
* **anonymises** — the last one rewrites the e-mail and the name and keeps the
|
|
84
|
+
* row — and every one of those still satisfies `organization_id NOT NULL`
|
|
85
|
+
* (D-178), so it still derives. Change any of that and the count below is the
|
|
86
|
+
* only thing that would notice. The anonymise path is worth a second look on
|
|
87
|
+
* *this* table in particular, because it rewrites the very column an e-mail
|
|
88
|
+
* match would have keyed on; deriving from `customer_account_id` is unaffected
|
|
89
|
+
* by it.
|
|
90
|
+
*
|
|
91
|
+
* It raises with the count and up to twenty ids and deletes nothing (D-184), in
|
|
92
|
+
* the shape `20260825T141659_customer_accounts_organization_required.ts`
|
|
93
|
+
* established and the three Group B migrations before this one repeated.
|
|
94
|
+
* Deleting would be particularly wrong here: a subscriber row is a consent
|
|
95
|
+
* record, and dropping one destroys the evidence that the address may be
|
|
96
|
+
* mailed at all.
|
|
97
|
+
*
|
|
98
|
+
* ## Nothing to declare in the manifest
|
|
99
|
+
*
|
|
100
|
+
* `newsletter` declares `customers` in its manifest `dependencies`, whose
|
|
101
|
+
* transitive closure reaches `customer_accounts` and `organizations`, so the
|
|
102
|
+
* table this reads is created before this runs. No foreign key is added, so
|
|
103
|
+
* `fk-dependency-drift` has nothing to say either, and a migration naming
|
|
104
|
+
* another module's table is outside `check:module-boundary`'s population by
|
|
105
|
+
* that check's own rule.
|
|
106
|
+
*
|
|
107
|
+
* The standing guard is
|
|
108
|
+
* `backend/test/integration/tenancy/customer-scoped-organization-completeness.test.ts`,
|
|
109
|
+
* which derives its population from the ORM's metadata rather than from a list
|
|
110
|
+
* — so this class is covered by gaining the column, with no edit to that file.
|
|
111
|
+
*/
|
|
112
|
+
export declare class Migration20260830T212736NewsletterOrganizationAttribution extends Migration {
|
|
113
|
+
up(): Promise<void>;
|
|
114
|
+
down(): Promise<void>;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=20260830T212736_newsletter_organization_attribution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260830T212736_newsletter_organization_attribution.d.ts","sourceRoot":"","sources":["../../src/migrations/20260830T212736_newsletter_organization_attribution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6GG;AACH,qBAAa,yDAA0D,SAAQ,SAAS;IACvE,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAoEnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAarC"}
|