@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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/admin/api/newsletter-client.d.ts +64 -0
  4. package/dist/admin/api/newsletter-client.d.ts.map +1 -0
  5. package/dist/admin/api/newsletter-client.js +71 -0
  6. package/dist/admin/api/newsletter-client.js.map +1 -0
  7. package/dist/admin/email-variables.d.ts +21 -0
  8. package/dist/admin/email-variables.d.ts.map +1 -0
  9. package/dist/admin/email-variables.js +48 -0
  10. package/dist/admin/email-variables.js.map +1 -0
  11. package/dist/admin/index.d.ts +44 -0
  12. package/dist/admin/index.d.ts.map +1 -0
  13. package/dist/admin/index.js +149 -0
  14. package/dist/admin/index.js.map +1 -0
  15. package/dist/admin/pages/AutomationBuilder.d.ts +8 -0
  16. package/dist/admin/pages/AutomationBuilder.d.ts.map +1 -0
  17. package/dist/admin/pages/AutomationBuilder.js +131 -0
  18. package/dist/admin/pages/AutomationBuilder.js.map +1 -0
  19. package/dist/admin/pages/AutomationsPage.d.ts +8 -0
  20. package/dist/admin/pages/AutomationsPage.d.ts.map +1 -0
  21. package/dist/admin/pages/AutomationsPage.js +43 -0
  22. package/dist/admin/pages/AutomationsPage.js.map +1 -0
  23. package/dist/admin/pages/BlocksPage.d.ts +8 -0
  24. package/dist/admin/pages/BlocksPage.d.ts.map +1 -0
  25. package/dist/admin/pages/BlocksPage.js +110 -0
  26. package/dist/admin/pages/BlocksPage.js.map +1 -0
  27. package/dist/admin/pages/CampaignEditor.d.ts +8 -0
  28. package/dist/admin/pages/CampaignEditor.d.ts.map +1 -0
  29. package/dist/admin/pages/CampaignEditor.js +144 -0
  30. package/dist/admin/pages/CampaignEditor.js.map +1 -0
  31. package/dist/admin/pages/CampaignStats.d.ts +8 -0
  32. package/dist/admin/pages/CampaignStats.d.ts.map +1 -0
  33. package/dist/admin/pages/CampaignStats.js +45 -0
  34. package/dist/admin/pages/CampaignStats.js.map +1 -0
  35. package/dist/admin/pages/CampaignsPage.d.ts +8 -0
  36. package/dist/admin/pages/CampaignsPage.d.ts.map +1 -0
  37. package/dist/admin/pages/CampaignsPage.js +28 -0
  38. package/dist/admin/pages/CampaignsPage.js.map +1 -0
  39. package/dist/admin/pages/ProviderSettingsPage.d.ts +8 -0
  40. package/dist/admin/pages/ProviderSettingsPage.d.ts.map +1 -0
  41. package/dist/admin/pages/ProviderSettingsPage.js +64 -0
  42. package/dist/admin/pages/ProviderSettingsPage.js.map +1 -0
  43. package/dist/admin/pages/SubscribersPage.d.ts +8 -0
  44. package/dist/admin/pages/SubscribersPage.d.ts.map +1 -0
  45. package/dist/admin/pages/SubscribersPage.js +55 -0
  46. package/dist/admin/pages/SubscribersPage.js.map +1 -0
  47. package/dist/admin/pages/TagsPage.d.ts +8 -0
  48. package/dist/admin/pages/TagsPage.d.ts.map +1 -0
  49. package/dist/admin/pages/TagsPage.js +114 -0
  50. package/dist/admin/pages/TagsPage.js.map +1 -0
  51. package/dist/backend/entities/newsletter-automation-run.entity.d.ts +19 -0
  52. package/dist/backend/entities/newsletter-automation-run.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/newsletter-automation-run.entity.js +72 -0
  54. package/dist/backend/entities/newsletter-automation-run.entity.js.map +1 -0
  55. package/dist/backend/entities/newsletter-automation.entity.d.ts +22 -0
  56. package/dist/backend/entities/newsletter-automation.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/newsletter-automation.entity.js +86 -0
  58. package/dist/backend/entities/newsletter-automation.entity.js.map +1 -0
  59. package/dist/backend/entities/newsletter-campaign-subscriber.entity.d.ts +6 -0
  60. package/dist/backend/entities/newsletter-campaign-subscriber.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/newsletter-campaign-subscriber.entity.js +30 -0
  62. package/dist/backend/entities/newsletter-campaign-subscriber.entity.js.map +1 -0
  63. package/dist/backend/entities/newsletter-campaign.entity.d.ts +22 -0
  64. package/dist/backend/entities/newsletter-campaign.entity.d.ts.map +1 -0
  65. package/dist/backend/entities/newsletter-campaign.entity.js +102 -0
  66. package/dist/backend/entities/newsletter-campaign.entity.js.map +1 -0
  67. package/dist/backend/entities/newsletter-custom-field.entity.d.ts +12 -0
  68. package/dist/backend/entities/newsletter-custom-field.entity.d.ts.map +1 -0
  69. package/dist/backend/entities/newsletter-custom-field.entity.js +53 -0
  70. package/dist/backend/entities/newsletter-custom-field.entity.js.map +1 -0
  71. package/dist/backend/entities/newsletter-email-block-sales-channel.entity.d.ts +7 -0
  72. package/dist/backend/entities/newsletter-email-block-sales-channel.entity.d.ts.map +1 -0
  73. package/dist/backend/entities/newsletter-email-block-sales-channel.entity.js +35 -0
  74. package/dist/backend/entities/newsletter-email-block-sales-channel.entity.js.map +1 -0
  75. package/dist/backend/entities/newsletter-email-block.entity.d.ts +21 -0
  76. package/dist/backend/entities/newsletter-email-block.entity.d.ts.map +1 -0
  77. package/dist/backend/entities/newsletter-email-block.entity.js +78 -0
  78. package/dist/backend/entities/newsletter-email-block.entity.js.map +1 -0
  79. package/dist/backend/entities/newsletter-engagement-event.entity.d.ts +12 -0
  80. package/dist/backend/entities/newsletter-engagement-event.entity.d.ts.map +1 -0
  81. package/dist/backend/entities/newsletter-engagement-event.entity.js +54 -0
  82. package/dist/backend/entities/newsletter-engagement-event.entity.js.map +1 -0
  83. package/dist/backend/entities/newsletter-send-record.entity.d.ts +23 -0
  84. package/dist/backend/entities/newsletter-send-record.entity.d.ts.map +1 -0
  85. package/dist/backend/entities/newsletter-send-record.entity.js +89 -0
  86. package/dist/backend/entities/newsletter-send-record.entity.js.map +1 -0
  87. package/dist/backend/entities/newsletter-subscriber-tag.entity.d.ts +6 -0
  88. package/dist/backend/entities/newsletter-subscriber-tag.entity.d.ts.map +1 -0
  89. package/dist/backend/entities/newsletter-subscriber-tag.entity.js +31 -0
  90. package/dist/backend/entities/newsletter-subscriber-tag.entity.js.map +1 -0
  91. package/dist/backend/entities/newsletter-subscriber.entity.d.ts +63 -0
  92. package/dist/backend/entities/newsletter-subscriber.entity.d.ts.map +1 -0
  93. package/dist/backend/entities/newsletter-subscriber.entity.js +147 -0
  94. package/dist/backend/entities/newsletter-subscriber.entity.js.map +1 -0
  95. package/dist/backend/entities/newsletter-suppression.entity.d.ts +14 -0
  96. package/dist/backend/entities/newsletter-suppression.entity.d.ts.map +1 -0
  97. package/dist/backend/entities/newsletter-suppression.entity.js +51 -0
  98. package/dist/backend/entities/newsletter-suppression.entity.js.map +1 -0
  99. package/dist/backend/entities/newsletter-tag.entity.d.ts +12 -0
  100. package/dist/backend/entities/newsletter-tag.entity.d.ts.map +1 -0
  101. package/dist/backend/entities/newsletter-tag.entity.js +53 -0
  102. package/dist/backend/entities/newsletter-tag.entity.js.map +1 -0
  103. package/dist/backend/index.d.ts +120 -0
  104. package/dist/backend/index.d.ts.map +1 -0
  105. package/dist/backend/index.js +139 -0
  106. package/dist/backend/index.js.map +1 -0
  107. package/dist/backend/plugin.d.ts +114 -0
  108. package/dist/backend/plugin.d.ts.map +1 -0
  109. package/dist/backend/plugin.js +171 -0
  110. package/dist/backend/plugin.js.map +1 -0
  111. package/dist/backend/routes.admin.d.ts +27 -0
  112. package/dist/backend/routes.admin.d.ts.map +1 -0
  113. package/dist/backend/routes.admin.js +118 -0
  114. package/dist/backend/routes.admin.js.map +1 -0
  115. package/dist/backend/routes.self.d.ts +15 -0
  116. package/dist/backend/routes.self.d.ts.map +1 -0
  117. package/dist/backend/routes.self.js +28 -0
  118. package/dist/backend/routes.self.js.map +1 -0
  119. package/dist/backend/routes.storefront.d.ts +24 -0
  120. package/dist/backend/routes.storefront.d.ts.map +1 -0
  121. package/dist/backend/routes.storefront.js +79 -0
  122. package/dist/backend/routes.storefront.js.map +1 -0
  123. package/dist/backend/services/audience-resolver.d.ts +20 -0
  124. package/dist/backend/services/audience-resolver.d.ts.map +1 -0
  125. package/dist/backend/services/audience-resolver.js +41 -0
  126. package/dist/backend/services/audience-resolver.js.map +1 -0
  127. package/dist/backend/services/automation.service.d.ts +58 -0
  128. package/dist/backend/services/automation.service.d.ts.map +1 -0
  129. package/dist/backend/services/automation.service.js +301 -0
  130. package/dist/backend/services/automation.service.js.map +1 -0
  131. package/dist/backend/services/campaign-dispatch.service.d.ts +50 -0
  132. package/dist/backend/services/campaign-dispatch.service.d.ts.map +1 -0
  133. package/dist/backend/services/campaign-dispatch.service.js +130 -0
  134. package/dist/backend/services/campaign-dispatch.service.js.map +1 -0
  135. package/dist/backend/services/campaign.service.d.ts +40 -0
  136. package/dist/backend/services/campaign.service.d.ts.map +1 -0
  137. package/dist/backend/services/campaign.service.js +195 -0
  138. package/dist/backend/services/campaign.service.js.map +1 -0
  139. package/dist/backend/services/consent-block-seeder.d.ts +30 -0
  140. package/dist/backend/services/consent-block-seeder.d.ts.map +1 -0
  141. package/dist/backend/services/consent-block-seeder.js +52 -0
  142. package/dist/backend/services/consent-block-seeder.js.map +1 -0
  143. package/dist/backend/services/content.service.d.ts +52 -0
  144. package/dist/backend/services/content.service.d.ts.map +1 -0
  145. package/dist/backend/services/content.service.js +48 -0
  146. package/dist/backend/services/content.service.js.map +1 -0
  147. package/dist/backend/services/custom-field.service.d.ts +16 -0
  148. package/dist/backend/services/custom-field.service.d.ts.map +1 -0
  149. package/dist/backend/services/custom-field.service.js +67 -0
  150. package/dist/backend/services/custom-field.service.js.map +1 -0
  151. package/dist/backend/services/email-block.service.d.ts +26 -0
  152. package/dist/backend/services/email-block.service.d.ts.map +1 -0
  153. package/dist/backend/services/email-block.service.js +120 -0
  154. package/dist/backend/services/email-block.service.js.map +1 -0
  155. package/dist/backend/services/opt-in.service.d.ts +25 -0
  156. package/dist/backend/services/opt-in.service.d.ts.map +1 -0
  157. package/dist/backend/services/opt-in.service.js +46 -0
  158. package/dist/backend/services/opt-in.service.js.map +1 -0
  159. package/dist/backend/services/provider/console-provider.d.ts +32 -0
  160. package/dist/backend/services/provider/console-provider.d.ts.map +1 -0
  161. package/dist/backend/services/provider/console-provider.js +31 -0
  162. package/dist/backend/services/provider/console-provider.js.map +1 -0
  163. package/dist/backend/services/provider/provider-registry.d.ts +51 -0
  164. package/dist/backend/services/provider/provider-registry.d.ts.map +1 -0
  165. package/dist/backend/services/provider/provider-registry.js +84 -0
  166. package/dist/backend/services/provider/provider-registry.js.map +1 -0
  167. package/dist/backend/services/provider/smtp-provider.d.ts +30 -0
  168. package/dist/backend/services/provider/smtp-provider.d.ts.map +1 -0
  169. package/dist/backend/services/provider/smtp-provider.js +42 -0
  170. package/dist/backend/services/provider/smtp-provider.js.map +1 -0
  171. package/dist/backend/services/provider-admin.service.d.ts +39 -0
  172. package/dist/backend/services/provider-admin.service.d.ts.map +1 -0
  173. package/dist/backend/services/provider-admin.service.js +59 -0
  174. package/dist/backend/services/provider-admin.service.js.map +1 -0
  175. package/dist/backend/services/provider-admin.types.d.ts +6 -0
  176. package/dist/backend/services/provider-admin.types.d.ts.map +1 -0
  177. package/dist/backend/services/provider-admin.types.js +2 -0
  178. package/dist/backend/services/provider-admin.types.js.map +1 -0
  179. package/dist/backend/services/queues/newsletter-queues.d.ts +34 -0
  180. package/dist/backend/services/queues/newsletter-queues.d.ts.map +1 -0
  181. package/dist/backend/services/queues/newsletter-queues.js +58 -0
  182. package/dist/backend/services/queues/newsletter-queues.js.map +1 -0
  183. package/dist/backend/services/self.service.d.ts +17 -0
  184. package/dist/backend/services/self.service.d.ts.map +1 -0
  185. package/dist/backend/services/self.service.js +44 -0
  186. package/dist/backend/services/self.service.js.map +1 -0
  187. package/dist/backend/services/stats.service.d.ts +10 -0
  188. package/dist/backend/services/stats.service.d.ts.map +1 -0
  189. package/dist/backend/services/stats.service.js +45 -0
  190. package/dist/backend/services/stats.service.js.map +1 -0
  191. package/dist/backend/services/subscriber-admin.service.d.ts +26 -0
  192. package/dist/backend/services/subscriber-admin.service.d.ts.map +1 -0
  193. package/dist/backend/services/subscriber-admin.service.js +163 -0
  194. package/dist/backend/services/subscriber-admin.service.js.map +1 -0
  195. package/dist/backend/services/subscriber.service.d.ts +91 -0
  196. package/dist/backend/services/subscriber.service.d.ts.map +1 -0
  197. package/dist/backend/services/subscriber.service.js +280 -0
  198. package/dist/backend/services/subscriber.service.js.map +1 -0
  199. package/dist/backend/services/tag.service.d.ts +16 -0
  200. package/dist/backend/services/tag.service.d.ts.map +1 -0
  201. package/dist/backend/services/tag.service.js +79 -0
  202. package/dist/backend/services/tag.service.js.map +1 -0
  203. package/dist/backend/services/token.helper.d.ts +32 -0
  204. package/dist/backend/services/token.helper.d.ts.map +1 -0
  205. package/dist/backend/services/token.helper.js +76 -0
  206. package/dist/backend/services/token.helper.js.map +1 -0
  207. package/dist/backend/services/tracking.service.d.ts +14 -0
  208. package/dist/backend/services/tracking.service.d.ts.map +1 -0
  209. package/dist/backend/services/tracking.service.js +45 -0
  210. package/dist/backend/services/tracking.service.js.map +1 -0
  211. package/dist/manifest.d.ts +192 -0
  212. package/dist/manifest.d.ts.map +1 -0
  213. package/dist/manifest.js +211 -0
  214. package/dist/manifest.js.map +1 -0
  215. package/dist/migrations/20260629T200954_newsletter_init.d.ts +6 -0
  216. package/dist/migrations/20260629T200954_newsletter_init.d.ts.map +1 -0
  217. package/dist/migrations/20260629T200954_newsletter_init.js +261 -0
  218. package/dist/migrations/20260629T200954_newsletter_init.js.map +1 -0
  219. package/dist/migrations/20260830T212736_newsletter_organization_attribution.d.ts +116 -0
  220. package/dist/migrations/20260830T212736_newsletter_organization_attribution.d.ts.map +1 -0
  221. package/dist/migrations/20260830T212736_newsletter_organization_attribution.js +186 -0
  222. package/dist/migrations/20260830T212736_newsletter_organization_attribution.js.map +1 -0
  223. package/dist/migrations/20260903T101752_newsletter_namespace_block_names.d.ts +6 -0
  224. package/dist/migrations/20260903T101752_newsletter_namespace_block_names.d.ts.map +1 -0
  225. package/dist/migrations/20260903T101752_newsletter_namespace_block_names.js +58 -0
  226. package/dist/migrations/20260903T101752_newsletter_namespace_block_names.js.map +1 -0
  227. package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.d.ts +23 -0
  228. package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.d.ts.map +1 -0
  229. package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.js +27 -0
  230. package/dist/migrations/20260912T125702_newsletter_subscriber_tenant_scope_index.js.map +1 -0
  231. package/dist/migrations/index.d.ts +30 -0
  232. package/dist/migrations/index.d.ts.map +1 -0
  233. package/dist/migrations/index.js +35 -0
  234. package/dist/migrations/index.js.map +1 -0
  235. package/docs/newsletter.md +94 -0
  236. package/i18n/en.json +27 -0
  237. package/i18n/pl.json +27 -0
  238. package/package.json +119 -0
  239. package/tailwind.css +14 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { lazyPort, resolvePublicApiBaseUrl } from '@endora-commerce/platform/kernel';
3
+ import { newsletterModule } from './plugin.js';
4
+ import { ensureNewsletterConsentBlock } from './services/consent-block-seeder.js';
5
+ import { NewsletterAutomationRun } from './entities/newsletter-automation-run.entity.js';
6
+ import { NewsletterAutomation } from './entities/newsletter-automation.entity.js';
7
+ import { NewsletterCampaignSubscriber } from './entities/newsletter-campaign-subscriber.entity.js';
8
+ import { NewsletterCampaign } from './entities/newsletter-campaign.entity.js';
9
+ import { NewsletterCustomField } from './entities/newsletter-custom-field.entity.js';
10
+ import { NewsletterEmailBlockSalesChannel } from './entities/newsletter-email-block-sales-channel.entity.js';
11
+ import { NewsletterEmailBlock } from './entities/newsletter-email-block.entity.js';
12
+ import { NewsletterEngagementEvent } from './entities/newsletter-engagement-event.entity.js';
13
+ import { NewsletterSendRecord } from './entities/newsletter-send-record.entity.js';
14
+ import { NewsletterSubscriberTag } from './entities/newsletter-subscriber-tag.entity.js';
15
+ import { NewsletterSubscriber } from './entities/newsletter-subscriber.entity.js';
16
+ import { NewsletterSuppression } from './entities/newsletter-suppression.entity.js';
17
+ import { NewsletterTag } from './entities/newsletter-tag.entity.js';
18
+ export function registerModule(ctx) {
19
+ ctx.di.register({
20
+ // Contribution point, absent by default: no branding source, neutral email.
21
+ newsletterEmailBranding: ctx
22
+ .asFunction(() => undefined)
23
+ .singleton(),
24
+ newsletter: ctx
25
+ .asFunction(({ emFactory, auditLogService, moduleQueueRedis, newsletterTokenSecret, storefrontBaseUrl, eventBus, }) => {
26
+ const cradle = () => ctx.cradle();
27
+ return newsletterModule({
28
+ emFactory,
29
+ auditLog: auditLogService,
30
+ settings: lazyPort(ctx, 'settingsReadPort'),
31
+ settingsWrite: lazyPort(ctx, 'settingsAdminService'),
32
+ credentials: lazyPort(ctx, 'credentialsService'),
33
+ // Feature 087 Group B / D-187 — the organisation an owned subscriber
34
+ // carries. A new edge for this module, and it needs no manifest
35
+ // change: `check:port-dependencies` uses the transitive closure of
36
+ // `dependencies`, and `customers` (declared) reaches
37
+ // `customer_accounts`. No deactivation-consequence entry either —
38
+ // `customer_accounts` declares `nonDeactivatable`, so it has no
39
+ // absent state for a consequence to describe.
40
+ customerAccounts: lazyPort(ctx, 'customerAccountReadPort'),
41
+ requireAdmin: (permission) => async (req, reply) => ctx.cradle().requireAdmin(permission)(req, reply),
42
+ requireCustomer: (req, reply) => ctx.cradle().requireCustomer(req, reply),
43
+ resolveAuditContext: (req) => ctx.cradle().adminAuditActorResolver(req),
44
+ // The nine former `newsletterBridge` members, each read where it
45
+ // belongs. A composition that wants a different answer contributes
46
+ // over the name it disagrees with; the harness pins the secret and
47
+ // the two origins, and gets a capturing mailer through `emailMailer`.
48
+ tokenSecret: newsletterTokenSecret,
49
+ publicBaseUrl: resolvePublicApiBaseUrl(),
50
+ storefrontBaseUrl,
51
+ // `lazyPort`, not `cradle().emailMailer`: this factory body runs once,
52
+ // and a cradle read written into it resolves the name there — the
53
+ // captured-registration shape `check:port-dependencies` refuses.
54
+ mailer: lazyPort(ctx, 'emailMailer'),
55
+ // An accessor, so nothing is awaited while this factory runs. The
56
+ // opt-in policy is the only reader and it asks per subscribe.
57
+ resolveDefaultChannelId: async () => (await cradle().salesChannelResolutionPort.getSystemDefault()).id,
58
+ resolveChannelIdByCode: (code) => cradle().salesChannelCodeIdPort.idByCode(code),
59
+ resolveCustomerAccountId: (req) => cradle().customerAccountIdResolver(req),
60
+ loadCustomerEmail: async (customerAccountId) => (await lazyPort(ctx, 'customerAccountReadPort').findById(customerAccountId))?.email ?? null,
61
+ emitEvent: (name, payload) => eventBus.emit(name, {
62
+ eventId: randomUUID(),
63
+ occurredAt: new Date().toISOString(),
64
+ ...payload,
65
+ }),
66
+ // Queue-backed dispatch when the composition has Redis; the module
67
+ // falls back to inline sending when it does not (Principle X).
68
+ ...(moduleQueueRedis === undefined ? {} : { redis: moduleQueueRedis }),
69
+ runWorkers: process.env['BACKEND_ROLE'] !== 'api',
70
+ // The lifecycle gate, threaded in rather than imported. The module
71
+ // builds its consumers inside the attach function — two need
72
+ // `app.log`, one an awaited settings read — so it has no
73
+ // `ModuleContext` where the workers exist; it called
74
+ // `defineModuleWorker('newsletter', …)` for itself until T051, which
75
+ // `contracts/host-package.md` §1.4c classifies **A**. Same gate,
76
+ // through the seam a composed module is meant to use.
77
+ registerWorker: (worker, workerOptions) => {
78
+ ctx.worker(worker, workerOptions);
79
+ },
80
+ // Read per call, so a root may contribute branding at any point in
81
+ // its own ordering.
82
+ resolveEmailBranding: async (salesChannelId) => {
83
+ const resolve = ctx.cradle().newsletterEmailBranding;
84
+ return resolve === undefined
85
+ ? { logoUrl: '', accentColor: '#1f2937' }
86
+ : resolve(salesChannelId);
87
+ },
88
+ });
89
+ })
90
+ .singleton(),
91
+ });
92
+ ctx.routes(async (app) => {
93
+ // Seed the predefined consent CMS block (idempotent; runs after channels
94
+ // exist). It sat inside `plugin.ts`'s route registrar until feature 075,
95
+ // where it wrote `cms`' two tables in raw SQL under a `try/catch` that
96
+ // downgraded every failure to a warning so a CMS-less platform could still
97
+ // boot (D-87). `cms` owns the write now, behind `cmsBlockSeedPort`, and the
98
+ // seeder decides that module's presence before it calls — so the absence is
99
+ // an answer logged here rather than an exception a `catch` has to be
100
+ // trusted to tell apart from a real one. The call moves up to `backend.ts`
101
+ // with the port, because a `ModuleContext` is what resolves one; relative
102
+ // order is unchanged, since the plugin's own registrar runs below.
103
+ //
104
+ // `lazyPort` built in a `ctx.routes` body resolves nothing: it defers to
105
+ // the method call, which happens only after the presence decision.
106
+ const seeded = await ensureNewsletterConsentBlock(lazyPort(ctx, 'cmsBlockSeedPort'));
107
+ if (seeded === 'cms-absent') {
108
+ app.log.info('[newsletter] consent CMS block seed skipped — cms is not present');
109
+ }
110
+ await ctx.cradle().newsletter(app);
111
+ });
112
+ }
113
+ /**
114
+ * The module's persisted entity classes, on the `./backend` subpath, as one
115
+ * array and **no named class export** (D-168).
116
+ *
117
+ * This is the shape the platform reads when the package is *installed*: the
118
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
119
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
120
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
121
+ * the package pass of `check-entity-tenant-classification`. A missing array is
122
+ * answered with `[]` — zero entities registered, no error anywhere.
123
+ */
124
+ export const entities = [
125
+ NewsletterAutomationRun,
126
+ NewsletterAutomation,
127
+ NewsletterCampaignSubscriber,
128
+ NewsletterCampaign,
129
+ NewsletterCustomField,
130
+ NewsletterEmailBlockSalesChannel,
131
+ NewsletterEmailBlock,
132
+ NewsletterEngagementEvent,
133
+ NewsletterSendRecord,
134
+ NewsletterSubscriberTag,
135
+ NewsletterSubscriber,
136
+ NewsletterSuppression,
137
+ NewsletterTag,
138
+ ];
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ import type { Worker } from 'bullmq';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { Redis } from 'ioredis';
4
+ import { type CustomerAccountReadPort, type EmailMailerPort } from '@endora-commerce/contracts';
5
+ import type { PlatformLogger } from '@endora-commerce/platform/kernel';
6
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
7
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
8
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
9
+ import { type SettingsWriter } from './services/provider-admin.service.js';
10
+ import type { AdminAuditContext } from './services/provider-admin.types.js';
11
+ import { type CredentialResolvePort } from './services/provider/provider-registry.js';
12
+ /**
13
+ * The attach function this module hands its composition root.
14
+ *
15
+ * Typed on `fastify`'s own `FastifyInstance` rather than on the platform's
16
+ * `ModulePlugin`, which `contracts/host-package.md` §1.4g classifies **A**: the
17
+ * host does not publish it, so a packaged module cannot name it. The
18
+ * already-packaged `quote_requests` types its attach function the same way.
19
+ */
20
+ type ModuleAttach = (app: FastifyInstance) => Promise<void>;
21
+ export interface NewsletterModuleOptions {
22
+ emFactory: () => EntityManager;
23
+ settings: SettingsReadPort;
24
+ tokenSecret: string;
25
+ /**
26
+ * The channel a subscriber with no origin channel belongs to — the
27
+ * deployment's system-default one, or `null` when it has none. Only the
28
+ * opt-in policy uses it: mode and confirmation TTL are per-storefront
29
+ * settings, so "the system-default channel's value" is the right answer for a
30
+ * subscriber with no channel and the platform-wide value is not. The provider
31
+ * configuration, which *is* platform-wide, reads `null` directly and no
32
+ * longer takes this at all (feature 072, D-41).
33
+ *
34
+ * An **accessor** since `specs/117-instance-bring-up/` Phase 6: it was an
35
+ * awaited value, which is the one member of the old `newsletterBridge` that
36
+ * only a composition root could produce. Both readers are already `async`.
37
+ */
38
+ resolveDefaultChannelId: () => Promise<string | null>;
39
+ resolveChannelIdByCode: (code: string) => Promise<string | null>;
40
+ publicBaseUrl: string;
41
+ storefrontBaseUrl: string;
42
+ requireAdmin: (permission?: string) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
43
+ /** Settings admin write surface (provider config + secret). */
44
+ settingsWrite: SettingsWriter;
45
+ /** Resolve the admin audit context from a request (for secret writes). */
46
+ resolveAuditContext: (req: FastifyRequest) => AdminAuditContext;
47
+ /** Customer session guard (self routes). */
48
+ requireCustomer: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
49
+ /** Resolve the signed-in customer's account id (org-less). */
50
+ resolveCustomerAccountId: (req: FastifyRequest) => string;
51
+ /** Resolve a customer account's email (cross-module lookup). */
52
+ loadCustomerEmail: (customerAccountId: string) => Promise<string | null>;
53
+ /**
54
+ * `customerAccountReadPort`, owned by `customer_accounts` — the organisation
55
+ * a subscriber's account belongs to (feature 087 Group B, D-187).
56
+ *
57
+ * Required, like the two settings ports above it and unlike `mailer`:
58
+ * `newsletter_subscribers_organization_attribution_chk` refuses an owned row
59
+ * with no organisation, so a composition that cannot answer this question
60
+ * cannot write the table at all. Typed as the published contract rather than
61
+ * by naming `customer_accounts`' own service class (Principle I).
62
+ */
63
+ customerAccounts: CustomerAccountReadPort;
64
+ /**
65
+ * `emailMailer`, owned by `email` — the container's registration, contributed
66
+ * here by a root. Typed as the published contract (feature 075, Phase C)
67
+ * rather than by naming `email`'s own `Mailer` alias: this module described
68
+ * the transport it needs by reaching into the module that owns it, which is
69
+ * the compile-time coupling Principle I forbids even when nothing is called.
70
+ */
71
+ mailer?: EmailMailerPort;
72
+ auditLog: AuditPort;
73
+ /** Optional observability emitter (wraps the in-process EventBus). */
74
+ emitEvent?: (name: string, payload: Record<string, unknown>) => void;
75
+ /** Redis connection — when present, dispatch is queue-backed (Principle X). */
76
+ redis?: Redis;
77
+ /** Whether this process runs queue consumers (BACKEND_ROLE != api). */
78
+ runWorkers?: boolean;
79
+ /**
80
+ * The lifecycle gate for a constructed BullMQ worker — `ctx.worker`, threaded
81
+ * in by `backend.ts`.
82
+ *
83
+ * This module builds its three consumers inside the attach function, because
84
+ * two of them need `app.log` and one needs an awaited settings read, so it
85
+ * has no `ModuleContext` in scope where the workers exist. It used to call
86
+ * `defineModuleWorker('newsletter', …)` directly, which
87
+ * `contracts/host-package.md` §1.4c classifies **A**: publishing that wrapper
88
+ * would re-open by bare specifier the seam `check:subscribe-seam` closed by
89
+ * relative path. Taking the seam as an argument is the same gate reached the
90
+ * sanctioned way.
91
+ */
92
+ registerWorker: (worker: Worker, options?: {
93
+ logger?: PlatformLogger;
94
+ }) => void;
95
+ /**
96
+ * Feature 058 — resolves the `newsletter.email_credentials` reference into a
97
+ * usable SMTP transport. Injected as a narrow port (Principle I); when absent
98
+ * the registry uses the legacy `newsletter.smtp.*` settings.
99
+ */
100
+ credentials?: CredentialResolvePort;
101
+ /** Resolve transactional email branding (logo + accent) for a sales channel. */
102
+ resolveEmailBranding?: (salesChannelId: string | null) => Promise<{
103
+ logoUrl: string;
104
+ accentColor: string;
105
+ }>;
106
+ }
107
+ /**
108
+ * Composition root for the newsletter module (feature 048). Builds the service
109
+ * graph, the (optional) BullMQ dispatch queues + workers, and registers the
110
+ * storefront + admin routes, all gated on the module's enabled state.
111
+ */
112
+ export declare function newsletterModule(options: NewsletterModuleOptions): ModuleAttach;
113
+ export {};
114
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,171 @@
1
+ import { z } from 'zod';
2
+ import { NEWSLETTER_SETTING_CODES, } from '@endora-commerce/contracts';
3
+ import { NewsletterTokenHelper } from './services/token.helper.js';
4
+ import { NewsletterOptInService } from './services/opt-in.service.js';
5
+ import { NewsletterSubscriberService } from './services/subscriber.service.js';
6
+ import { NewsletterContentService } from './services/content.service.js';
7
+ import { NewsletterAudienceResolver } from './services/audience-resolver.js';
8
+ import { NewsletterCampaignDispatchService } from './services/campaign-dispatch.service.js';
9
+ import { NewsletterCampaignService } from './services/campaign.service.js';
10
+ import { NewsletterSubscriberAdminService } from './services/subscriber-admin.service.js';
11
+ import { NewsletterTagService } from './services/tag.service.js';
12
+ import { NewsletterCustomFieldService } from './services/custom-field.service.js';
13
+ import { NewsletterAutomationService } from './services/automation.service.js';
14
+ import { NewsletterTrackingService } from './services/tracking.service.js';
15
+ import { NewsletterStatsService } from './services/stats.service.js';
16
+ import { NewsletterEmailBlockService } from './services/email-block.service.js';
17
+ import { NewsletterProviderAdminService } from './services/provider-admin.service.js';
18
+ import { NewsletterProviderRegistry, } from './services/provider/provider-registry.js';
19
+ import { createCampaignPlanQueue, createSendQueue, createAutomationStepQueue, createCampaignPlanWorker, createSendWorker, createAutomationStepWorker, } from './services/queues/newsletter-queues.js';
20
+ import { NewsletterSelfService } from './services/self.service.js';
21
+ import { registerNewsletterStorefrontRoutes } from './routes.storefront.js';
22
+ import { registerNewsletterAdminRoutes } from './routes.admin.js';
23
+ import { registerNewsletterSelfRoutes } from './routes.self.js';
24
+ /**
25
+ * Composition root for the newsletter module (feature 048). Builds the service
26
+ * graph, the (optional) BullMQ dispatch queues + workers, and registers the
27
+ * storefront + admin routes, all gated on the module's enabled state.
28
+ */
29
+ export function newsletterModule(options) {
30
+ const tokens = new NewsletterTokenHelper(options.tokenSecret);
31
+ const optIn = new NewsletterOptInService(options.settings, tokens);
32
+ const content = new NewsletterContentService();
33
+ const audience = new NewsletterAudienceResolver(options.emFactory);
34
+ const providers = new NewsletterProviderRegistry(options.settings, options.credentials);
35
+ const links = {
36
+ confirm: (t) => `${options.publicBaseUrl}/api/v1/newsletter/confirm?token=${encodeURIComponent(t)}`,
37
+ unsubscribe: (t) => `${options.publicBaseUrl}/api/v1/newsletter/unsubscribe?token=${encodeURIComponent(t)}`,
38
+ };
39
+ const subscribers = new NewsletterSubscriberService({
40
+ emFactory: options.emFactory,
41
+ optIn,
42
+ resolveDefaultChannelId: options.resolveDefaultChannelId,
43
+ links,
44
+ customerAccounts: options.customerAccounts,
45
+ ...(options.mailer ? { mailer: options.mailer } : {}),
46
+ ...(options.auditLog ? { auditLog: options.auditLog } : {}),
47
+ ...(options.emitEvent ? { emitEvent: options.emitEvent } : {}),
48
+ });
49
+ const subscriberAdmin = new NewsletterSubscriberAdminService(options.emFactory, options.auditLog);
50
+ const tags = new NewsletterTagService(options.emFactory, options.auditLog);
51
+ const customFields = new NewsletterCustomFieldService(options.emFactory, options.auditLog);
52
+ const tracking = new NewsletterTrackingService(options.emFactory);
53
+ const stats = new NewsletterStatsService(options.emFactory);
54
+ const blocks = new NewsletterEmailBlockService(options.emFactory, options.auditLog);
55
+ const providerAdmin = new NewsletterProviderAdminService(options.settings, options.settingsWrite, providers);
56
+ const self = new NewsletterSelfService(options.emFactory, subscribers);
57
+ const dispatch = new NewsletterCampaignDispatchService({
58
+ emFactory: options.emFactory,
59
+ audience,
60
+ content,
61
+ optIn,
62
+ links,
63
+ resolveProvider: () => providers.resolveProvider(),
64
+ resolveSender: () => providers.resolveSender(),
65
+ ...(options.resolveEmailBranding
66
+ ? { resolveEmailBranding: options.resolveEmailBranding }
67
+ : {}),
68
+ });
69
+ // Producer-side queues (needed by the API to enqueue, regardless of worker role).
70
+ const planQueue = options.redis ? createCampaignPlanQueue(options.redis) : undefined;
71
+ const sendQueue = options.redis ? createSendQueue(options.redis) : undefined;
72
+ const automationStepQueue = options.redis ? createAutomationStepQueue(options.redis) : undefined;
73
+ const automations = new NewsletterAutomationService({
74
+ emFactory: options.emFactory,
75
+ content,
76
+ optIn,
77
+ links,
78
+ resolveProvider: () => providers.resolveProvider(),
79
+ resolveSender: () => providers.resolveSender(),
80
+ enqueueStep: async (runId, stepIndex, delayMs) => {
81
+ if (automationStepQueue) {
82
+ await automationStepQueue.add('step', { runId, stepIndex }, delayMs ? { delay: delayMs } : {});
83
+ }
84
+ },
85
+ ...(options.auditLog ? { auditLog: options.auditLog } : {}),
86
+ ...(options.resolveEmailBranding
87
+ ? { resolveEmailBranding: options.resolveEmailBranding }
88
+ : {}),
89
+ });
90
+ const campaigns = new NewsletterCampaignService({
91
+ emFactory: options.emFactory,
92
+ dispatch,
93
+ content,
94
+ isProviderConfigured: () => providers.isConfigured(),
95
+ ...(options.auditLog ? { auditLog: options.auditLog } : {}),
96
+ ...(options.resolveEmailBranding
97
+ ? { resolveEmailBranding: options.resolveEmailBranding }
98
+ : {}),
99
+ ...(planQueue
100
+ ? {
101
+ enqueuePlan: async (campaignId, delayMs) => {
102
+ const job = await planQueue.add('plan', { campaignId }, delayMs ? { delay: delayMs } : {});
103
+ return job.id;
104
+ },
105
+ }
106
+ : {}),
107
+ });
108
+ return async (app) => {
109
+ // Queue consumers (Principle X): separable, pause on disable.
110
+ if (options.runWorkers && options.redis && planQueue && sendQueue) {
111
+ let rate = 14;
112
+ try {
113
+ // Platform-wide: one send-rate cap for the deployment's one SMTP
114
+ // transport (feature 072, D-41).
115
+ rate = await options.settings.get(NEWSLETTER_SETTING_CODES.RATE_LIMIT_PER_SECOND, null, z.number());
116
+ }
117
+ catch {
118
+ // default
119
+ }
120
+ options.registerWorker(createCampaignPlanWorker(options.redis, async (job) => {
121
+ const recordIds = await dispatch.planCampaign(job.data.campaignId);
122
+ for (const recordId of recordIds)
123
+ await sendQueue.add('send', { recordId });
124
+ }), { logger: app.log });
125
+ options.registerWorker(createSendWorker(options.redis, async (job) => {
126
+ await dispatch.sendRecord(job.data.recordId);
127
+ }, rate), { logger: app.log });
128
+ if (automationStepQueue) {
129
+ options.registerWorker(createAutomationStepWorker(options.redis, async (job) => {
130
+ await automations.processStep(job.data.runId, job.data.stepIndex);
131
+ }), { logger: app.log });
132
+ }
133
+ }
134
+ // Encapsulated, not gated. `backend.ts` registers this through `ctx.routes`,
135
+ // which already applies `defineModuleRoutes('newsletter', …)` — gating here
136
+ // too would add a second, identical `onRequest` check to every route the
137
+ // module owns (feature 072). The `register` call stays: it is what keeps
138
+ // this module's hooks and decorators out of the rest of the app.
139
+ await app.register(async (scoped) => {
140
+ await registerNewsletterStorefrontRoutes(scoped, {
141
+ subscribers,
142
+ optIn,
143
+ tokens,
144
+ tracking,
145
+ resolveChannelIdByCode: options.resolveChannelIdByCode,
146
+ resolveDefaultChannelId: options.resolveDefaultChannelId,
147
+ storefrontBaseUrl: options.storefrontBaseUrl,
148
+ });
149
+ await registerNewsletterAdminRoutes(scoped, {
150
+ campaigns,
151
+ subscribers,
152
+ subscriberAdmin,
153
+ tags,
154
+ customFields,
155
+ automations,
156
+ stats,
157
+ providerAdmin,
158
+ blocks,
159
+ resolveAuditContext: options.resolveAuditContext,
160
+ requireAdmin: options.requireAdmin,
161
+ });
162
+ await registerNewsletterSelfRoutes(scoped, {
163
+ self,
164
+ requireCustomer: options.requireCustomer,
165
+ resolveCustomerAccountId: options.resolveCustomerAccountId,
166
+ loadCustomerEmail: options.loadCustomerEmail,
167
+ });
168
+ });
169
+ };
170
+ }
171
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
2
+ import type { NewsletterCampaignService } from './services/campaign.service.js';
3
+ import type { NewsletterSubscriberService } from './services/subscriber.service.js';
4
+ import type { NewsletterSubscriberAdminService } from './services/subscriber-admin.service.js';
5
+ import type { NewsletterTagService } from './services/tag.service.js';
6
+ import type { NewsletterCustomFieldService } from './services/custom-field.service.js';
7
+ import type { NewsletterAutomationService } from './services/automation.service.js';
8
+ import type { NewsletterStatsService } from './services/stats.service.js';
9
+ import type { NewsletterProviderAdminService } from './services/provider-admin.service.js';
10
+ import type { AdminAuditContext } from './services/provider-admin.types.js';
11
+ import type { NewsletterEmailBlockService } from './services/email-block.service.js';
12
+ export interface NewsletterAdminDeps {
13
+ campaigns: NewsletterCampaignService;
14
+ subscribers: NewsletterSubscriberService;
15
+ subscriberAdmin: NewsletterSubscriberAdminService;
16
+ tags: NewsletterTagService;
17
+ customFields: NewsletterCustomFieldService;
18
+ automations: NewsletterAutomationService;
19
+ stats: NewsletterStatsService;
20
+ providerAdmin: NewsletterProviderAdminService;
21
+ blocks: NewsletterEmailBlockService;
22
+ resolveAuditContext: (req: FastifyRequest) => AdminAuditContext;
23
+ requireAdmin: (permission?: string) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
24
+ }
25
+ /** Admin newsletter routes (feature 048). All under /api/v1/admin/newsletter. */
26
+ export declare function registerNewsletterAdminRoutes(app: FastifyInstance, deps: NewsletterAdminDeps): Promise<void>;
27
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAsB7E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AACpF,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,wCAAwC,CAAC;AAC/F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,oCAAoC,CAAC;AACvF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AACpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,sCAAsC,CAAC;AAC3F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AAErF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,yBAAyB,CAAC;IACrC,WAAW,EAAE,2BAA2B,CAAC;IACzC,eAAe,EAAE,gCAAgC,CAAC;IAClD,IAAI,EAAE,oBAAoB,CAAC;IAC3B,YAAY,EAAE,4BAA4B,CAAC;IAC3C,WAAW,EAAE,2BAA2B,CAAC;IACzC,KAAK,EAAE,sBAAsB,CAAC;IAC9B,aAAa,EAAE,8BAA8B,CAAC;IAC9C,MAAM,EAAE,2BAA2B,CAAC;IACpC,mBAAmB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,iBAAiB,CAAC;IAChE,YAAY,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpG;AAKD,iFAAiF;AACjF,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CAoKf"}
@@ -0,0 +1,118 @@
1
+ import { createCampaignRequestSchema, updateCampaignRequestSchema, setCampaignGroupRequestSchema, sendCampaignRequestSchema, previewCampaignRequestSchema, createSubscriberRequestSchema, subscriberListQuerySchema, unsubscribeSubscriberRequestSchema, createNewsletterTagRequestSchema, updateNewsletterTagRequestSchema, createNewsletterCustomFieldRequestSchema, updateNewsletterCustomFieldRequestSchema, createAutomationRequestSchema, updateAutomationRequestSchema, automationActionRequestSchema, putProviderRequestSchema, createNewsletterEmailBlockRequestSchema, updateNewsletterEmailBlockRequestSchema, } from '@endora-commerce/contracts';
2
+ import { z } from 'zod';
3
+ const READ = 'newsletter:read';
4
+ const WRITE = 'newsletter:write';
5
+ /** Admin newsletter routes (feature 048). All under /api/v1/admin/newsletter. */
6
+ export async function registerNewsletterAdminRoutes(app, deps) {
7
+ const base = '/api/v1/admin/newsletter';
8
+ const read = { preHandler: deps.requireAdmin(READ) };
9
+ const write = { preHandler: deps.requireAdmin(WRITE) };
10
+ const idOf = (req) => req.params.id;
11
+ // --- Campaigns ---------------------------------------------------------
12
+ app.get(`${base}/campaigns`, read, async (_req, reply) => reply.send({ data: await deps.campaigns.list() }));
13
+ app.get(`${base}/campaigns/:id`, read, async (req, reply) => reply.send({ data: await deps.campaigns.get(idOf(req)) }));
14
+ app.post(`${base}/campaigns`, write, async (req, reply) => reply.status(201).send({ data: await deps.campaigns.create(createCampaignRequestSchema.parse(req.body)) }));
15
+ app.put(`${base}/campaigns/:id`, write, async (req, reply) => reply.send({ data: await deps.campaigns.update(idOf(req), updateCampaignRequestSchema.parse(req.body)) }));
16
+ app.put(`${base}/campaigns/:id/group`, write, async (req, reply) => reply.send({
17
+ data: await deps.campaigns.setGroup(idOf(req), setCampaignGroupRequestSchema.parse(req.body).subscriberIds),
18
+ }));
19
+ app.post(`${base}/campaigns/:id/preview`, read, async (req, reply) => {
20
+ const body = previewCampaignRequestSchema.parse(req.body ?? {});
21
+ return reply.send({ data: await deps.campaigns.preview(idOf(req), body.subscriberId) });
22
+ });
23
+ app.post(`${base}/campaigns/:id/send`, write, async (req, reply) => {
24
+ const body = sendCampaignRequestSchema.parse(req.body);
25
+ const scheduledAt = body.scheduledAt ? new Date(body.scheduledAt) : undefined;
26
+ return reply.send({ data: await deps.campaigns.send(idOf(req), body.expectedVersion, scheduledAt) });
27
+ });
28
+ app.post(`${base}/campaigns/:id/cancel`, write, async (req, reply) => {
29
+ const body = sendCampaignRequestSchema.pick({ expectedVersion: true }).parse(req.body);
30
+ return reply.send({ data: await deps.campaigns.cancel(idOf(req), body.expectedVersion) });
31
+ });
32
+ app.get(`${base}/campaigns/:id/stats`, read, async (req, reply) => reply.send({ data: await deps.stats.campaignStats(idOf(req)) }));
33
+ // --- Subscribers -------------------------------------------------------
34
+ app.get(`${base}/subscribers`, read, async (req, reply) => {
35
+ const query = subscriberListQuerySchema.parse(req.query);
36
+ return reply.send({ data: await deps.subscriberAdmin.list(query) });
37
+ });
38
+ app.get(`${base}/subscribers/export`, { ...read, config: { streamingResponse: true } }, async (req, reply) => {
39
+ const query = subscriberListQuerySchema.partial().parse(req.query);
40
+ const csv = await deps.subscriberAdmin.exportCsv(query);
41
+ reply.header('content-type', 'text/csv; charset=utf-8');
42
+ reply.header('content-disposition', 'attachment; filename="newsletter-subscribers.csv"');
43
+ return reply.send(csv);
44
+ });
45
+ app.get(`${base}/subscribers/:id`, read, async (req, reply) => reply.send({ data: await deps.subscriberAdmin.getDetail(idOf(req)) }));
46
+ app.post(`${base}/subscribers`, write, async (req, reply) => {
47
+ const body = createSubscriberRequestSchema.parse(req.body);
48
+ const result = await deps.subscribers.subscribe({
49
+ email: body.email,
50
+ salesChannelId: body.salesChannelId ?? null,
51
+ source: body.source ?? 'api',
52
+ ...(body.tags ? { tags: body.tags } : {}),
53
+ ...(body.customFields ? { customFields: body.customFields } : {}),
54
+ });
55
+ return reply.status(201).send({ data: result });
56
+ });
57
+ app.post(`${base}/subscribers/:id/unsubscribe`, write, async (req, reply) => {
58
+ const body = unsubscribeSubscriberRequestSchema.parse(req.body);
59
+ await deps.subscribers.unsubscribe(idOf(req), body.reason);
60
+ return reply.send({ data: await deps.subscriberAdmin.getDetail(idOf(req)) });
61
+ });
62
+ app.post(`${base}/subscribers/:id/deactivate`, write, async (req, reply) => {
63
+ const body = z.object({ expectedVersion: z.number().int() }).parse(req.body);
64
+ return reply.send({ data: await deps.subscriberAdmin.deactivate(idOf(req), body.expectedVersion) });
65
+ });
66
+ app.delete(`${base}/subscribers/:id`, write, async (req, reply) => {
67
+ await deps.subscriberAdmin.remove(idOf(req));
68
+ return reply.status(204).send();
69
+ });
70
+ // --- Tags --------------------------------------------------------------
71
+ app.get(`${base}/tags`, read, async (_req, reply) => reply.send({ data: { items: await deps.tags.list() } }));
72
+ app.post(`${base}/tags`, write, async (req, reply) => reply.status(201).send({ data: await deps.tags.create(createNewsletterTagRequestSchema.parse(req.body)) }));
73
+ app.patch(`${base}/tags/:id`, write, async (req, reply) => reply.send({ data: await deps.tags.update(idOf(req), updateNewsletterTagRequestSchema.parse(req.body)) }));
74
+ app.delete(`${base}/tags/:id`, write, async (req, reply) => {
75
+ await deps.tags.remove(idOf(req));
76
+ return reply.status(204).send();
77
+ });
78
+ // --- Custom fields -----------------------------------------------------
79
+ app.get(`${base}/custom-fields`, read, async (_req, reply) => reply.send({ data: { items: await deps.customFields.list() } }));
80
+ app.post(`${base}/custom-fields`, write, async (req, reply) => reply
81
+ .status(201)
82
+ .send({ data: await deps.customFields.create(createNewsletterCustomFieldRequestSchema.parse(req.body)) }));
83
+ app.patch(`${base}/custom-fields/:id`, write, async (req, reply) => reply.send({
84
+ data: await deps.customFields.update(idOf(req), updateNewsletterCustomFieldRequestSchema.parse(req.body)),
85
+ }));
86
+ app.delete(`${base}/custom-fields/:id`, write, async (req, reply) => {
87
+ await deps.customFields.remove(idOf(req));
88
+ return reply.status(204).send();
89
+ });
90
+ // --- Automations -------------------------------------------------------
91
+ app.get(`${base}/automations`, read, async (_req, reply) => reply.send({ data: await deps.automations.list() }));
92
+ app.get(`${base}/automations/:id`, read, async (req, reply) => reply.send({ data: await deps.automations.get(idOf(req)) }));
93
+ app.post(`${base}/automations`, write, async (req, reply) => reply.status(201).send({ data: await deps.automations.create(createAutomationRequestSchema.parse(req.body)) }));
94
+ app.put(`${base}/automations/:id`, write, async (req, reply) => reply.send({ data: await deps.automations.update(idOf(req), updateAutomationRequestSchema.parse(req.body)) }));
95
+ app.post(`${base}/automations/:id/activate`, write, async (req, reply) => reply.send({
96
+ data: await deps.automations.activate(idOf(req), automationActionRequestSchema.parse(req.body).expectedVersion),
97
+ }));
98
+ app.post(`${base}/automations/:id/pause`, write, async (req, reply) => reply.send({
99
+ data: await deps.automations.pause(idOf(req), automationActionRequestSchema.parse(req.body).expectedVersion),
100
+ }));
101
+ // --- Sending provider --------------------------------------------------
102
+ app.get(`${base}/provider`, read, async (_req, reply) => reply.send({ data: await deps.providerAdmin.getConfig() }));
103
+ app.put(`${base}/provider`, write, async (req, reply) => {
104
+ const body = putProviderRequestSchema.parse(req.body);
105
+ return reply.send({ data: await deps.providerAdmin.putConfig(body, deps.resolveAuditContext(req)) });
106
+ });
107
+ app.post(`${base}/provider/test`, write, async (_req, reply) => reply.send({ data: await deps.providerAdmin.test() }));
108
+ // --- Reusable email blocks ---------------------------------------------
109
+ app.get(`${base}/blocks`, read, async (_req, reply) => reply.send({ data: { items: await deps.blocks.list() } }));
110
+ app.get(`${base}/blocks/:id`, read, async (req, reply) => reply.send({ data: await deps.blocks.get(idOf(req)) }));
111
+ app.post(`${base}/blocks`, write, async (req, reply) => reply.status(201).send({ data: await deps.blocks.create(createNewsletterEmailBlockRequestSchema.parse(req.body)) }));
112
+ app.put(`${base}/blocks/:id`, write, async (req, reply) => reply.send({ data: await deps.blocks.update(idOf(req), updateNewsletterEmailBlockRequestSchema.parse(req.body)) }));
113
+ app.delete(`${base}/blocks/:id`, write, async (req, reply) => {
114
+ await deps.blocks.remove(idOf(req));
115
+ return reply.status(204).send();
116
+ });
117
+ }
118
+ //# sourceMappingURL=routes.admin.js.map