@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,48 @@
1
+ import { renderEmailHtml } from '@endora-commerce/email-components/render/render-email-html';
2
+ import { renderEmailText } from '@endora-commerce/email-components/render/render-email-text';
3
+ import { renderDirectives } from '@endora-commerce/email-components/directives/directive-engine';
4
+ const EMPTY_EMBEDS = { blocks: {}, templates: {} };
5
+ /** Merge channel branding into the directive variable map (logo + accent). */
6
+ export async function withEmailBranding(variables, salesChannelId, resolve) {
7
+ if (!resolve)
8
+ return { variables };
9
+ const branding = await resolve(salesChannelId);
10
+ return {
11
+ variables: {
12
+ ...variables,
13
+ branding: { logoUrl: branding.logoUrl, accentColor: branding.accentColor },
14
+ },
15
+ accentColor: branding.accentColor,
16
+ };
17
+ }
18
+ /**
19
+ * Pure render: tree → email-safe HTML + plain-text, then resolve `{{var}}` /
20
+ * `{{if}}` / `{{for}}` directives against the recipient context. HTML values
21
+ * are escaped; text values are raw. Missing variables resolve to empty strings
22
+ * (the engine never throws), so no literal `{{...}}` ever ships (FR-028).
23
+ */
24
+ export function renderNewsletterEmail(input) {
25
+ const embeds = input.embeds ?? EMPTY_EMBEDS;
26
+ const directiveCtx = {
27
+ ...input.context.variables,
28
+ unsubscribeUrl: input.context.unsubscribeUrl,
29
+ ...(input.context.webviewUrl !== undefined ? { webviewUrl: input.context.webviewUrl } : {}),
30
+ };
31
+ const htmlOpts = input.accentColor !== undefined
32
+ ? { embeds, accentColor: input.accentColor }
33
+ : { embeds };
34
+ const rawHtml = renderEmailHtml(input.content, htmlOpts);
35
+ const rawText = renderEmailText(input.content, { embeds });
36
+ return {
37
+ subject: renderDirectives(input.subject, directiveCtx),
38
+ html: renderDirectives(rawHtml, directiveCtx, { escape: true }),
39
+ text: renderDirectives(rawText, directiveCtx),
40
+ };
41
+ }
42
+ export class NewsletterContentService {
43
+ /** Render a campaign/automation email for one recipient. */
44
+ render(input) {
45
+ return renderNewsletterEmail(input);
46
+ }
47
+ }
48
+ //# sourceMappingURL=content.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.service.js","sourceRoot":"","sources":["../../../src/backend/services/content.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,4DAA4D,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,4DAA4D,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,MAAM,+DAA+D,CAAC;AAsCjG,MAAM,YAAY,GAAgB,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AAMhE,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAkC,EAClC,cAA6B,EAC7B,OAA+B;IAE/B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/C,OAAO;QACL,SAAS,EAAE;YACT,GAAG,SAAS;YACZ,QAAQ,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE;SAC3E;QACD,WAAW,EAAE,QAAQ,CAAC,WAAW;KAClC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAkB;IACtD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,YAAY,CAAC;IAC5C,MAAM,YAAY,GAA4B;QAC5C,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS;QAC1B,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,cAAc;QAC5C,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5F,CAAC;IAEF,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,KAAK,SAAS;QAC9C,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE;QAC5C,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAE3D,OAAO;QACL,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,OAAO,EAAE,YAAY,CAAC;QACtD,IAAI,EAAE,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC/D,IAAI,EAAE,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,wBAAwB;IACnC,4DAA4D;IAC5D,MAAM,CAAC,KAAkB;QACvB,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CreateNewsletterCustomFieldRequest, type NewsletterCustomField as NewsletterCustomFieldDto, type UpdateNewsletterCustomFieldRequest } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ /** Newsletter custom-field definition CRUD (feature 048, US3). */
5
+ export declare class NewsletterCustomFieldService {
6
+ #private;
7
+ private readonly emFactory;
8
+ private readonly auditLog?;
9
+ constructor(emFactory: () => EntityManager, auditLog?: AuditPort | undefined);
10
+ list(): Promise<NewsletterCustomFieldDto[]>;
11
+ create(input: CreateNewsletterCustomFieldRequest): Promise<NewsletterCustomFieldDto>;
12
+ update(id: string, input: UpdateNewsletterCustomFieldRequest): Promise<NewsletterCustomFieldDto>;
13
+ remove(id: string): Promise<void>;
14
+ private toDto;
15
+ }
16
+ //# sourceMappingURL=custom-field.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-field.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/custom-field.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,kCAAkC,EACvC,KAAK,qBAAqB,IAAI,wBAAwB,EACtD,KAAK,kCAAkC,EACxC,MAAM,4BAA4B,CAAC;AAIpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,kEAAkE;AAClE,qBAAa,4BAA4B;;IAErC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,CAAC,EAAE,SAAS,YAAA;IAejC,IAAI,IAAI,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAM3C,MAAM,CAAC,KAAK,EAAE,kCAAkC,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAepF,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kCAAkC,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAUhG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQvC,OAAO,CAAC,KAAK;CAGd"}
@@ -0,0 +1,67 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { NewsletterCustomField } from '../entities/newsletter-custom-field.entity.js';
4
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
5
+ /** Newsletter custom-field definition CRUD (feature 048, US3). */
6
+ export class NewsletterCustomFieldService {
7
+ emFactory;
8
+ auditLog;
9
+ constructor(emFactory, auditLog) {
10
+ this.emFactory = emFactory;
11
+ this.auditLog = auditLog;
12
+ }
13
+ #audit(em, action, objectId, stateAfter) {
14
+ if (this.auditLog) {
15
+ recordAuditFromContext(this.auditLog, em, {
16
+ action,
17
+ objectType: 'newsletter_custom_field',
18
+ objectId,
19
+ stateBefore: null,
20
+ stateAfter,
21
+ });
22
+ }
23
+ }
24
+ async list() {
25
+ const em = this.emFactory();
26
+ const rows = await em.find(NewsletterCustomField, {}, { orderBy: { key: 'asc' } });
27
+ return rows.map((f) => this.toDto(f));
28
+ }
29
+ async create(input) {
30
+ const em = this.emFactory();
31
+ const existing = await em.findOne(NewsletterCustomField, { key: input.key });
32
+ if (existing)
33
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'Field key already exists.');
34
+ const field = em.create(NewsletterCustomField, {
35
+ key: input.key,
36
+ label: input.label,
37
+ type: input.type,
38
+ });
39
+ em.persist(field);
40
+ this.#audit(em, 'newsletter_custom_field.create', field.id, { key: field.key });
41
+ await em.flush();
42
+ return this.toDto(field);
43
+ }
44
+ async update(id, input) {
45
+ const em = this.emFactory();
46
+ const field = await em.findOne(NewsletterCustomField, { id });
47
+ if (!field)
48
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Field not found.');
49
+ if (input.label !== undefined)
50
+ field.label = input.label;
51
+ this.#audit(em, 'newsletter_custom_field.update', field.id, { label: field.label });
52
+ await em.persistAndFlush(field);
53
+ return this.toDto(field);
54
+ }
55
+ async remove(id) {
56
+ const em = this.emFactory();
57
+ const field = await em.findOne(NewsletterCustomField, { id });
58
+ if (!field)
59
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Field not found.');
60
+ this.#audit(em, 'newsletter_custom_field.delete', field.id, null);
61
+ await em.removeAndFlush(field);
62
+ }
63
+ toDto(f) {
64
+ return { id: f.id, key: f.key, label: f.label, type: f.type };
65
+ }
66
+ }
67
+ //# sourceMappingURL=custom-field.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-field.service.js","sourceRoot":"","sources":["../../../src/backend/services/custom-field.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CreateNewsletterEmailBlockRequest, type NewsletterEmailBlockDetail, type NewsletterEmailBlockSummary, type UpdateNewsletterEmailBlockRequest } from '@endora-commerce/contracts';
3
+ import type { EmailEmbeds } from './content.service.js';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * Reusable email-safe blocks (feature 048, US5). Global blocks embedded into
7
+ * campaign/automation content via the shared renderer's embeds map. System
8
+ * blocks (seeded header/footer) cannot be deleted.
9
+ */
10
+ export declare class NewsletterEmailBlockService {
11
+ #private;
12
+ private readonly emFactory;
13
+ private readonly auditLog?;
14
+ constructor(emFactory: () => EntityManager, auditLog?: AuditPort | undefined);
15
+ list(): Promise<NewsletterEmailBlockSummary[]>;
16
+ get(id: string): Promise<NewsletterEmailBlockDetail>;
17
+ create(input: CreateNewsletterEmailBlockRequest): Promise<NewsletterEmailBlockDetail>;
18
+ update(id: string, input: UpdateNewsletterEmailBlockRequest): Promise<NewsletterEmailBlockDetail>;
19
+ remove(id: string): Promise<void>;
20
+ /** Build the renderer embeds map from active blocks for the given language. */
21
+ resolveEmbeds(language: string): Promise<EmailEmbeds>;
22
+ private load;
23
+ private toSummary;
24
+ private toDetail;
25
+ }
26
+ //# sourceMappingURL=email-block.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-block.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/email-block.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,iCAAiC,EACtC,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,iCAAiC,EACvC,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAmBlE;;;;GAIG;AACH,qBAAa,2BAA2B;;IAEpC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,CAAC,EAAE,SAAS,YAAA;IAejC,IAAI,IAAI,OAAO,CAAC,2BAA2B,EAAE,CAAC;IAM9C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAIpD,MAAM,CAAC,KAAK,EAAE,iCAAiC,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAgBrF,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iCAAiC,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAgBjG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvC,+EAA+E;IACzE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YAQ7C,IAAI;IAMlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,QAAQ;CASjB"}
@@ -0,0 +1,120 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { NewsletterEmailBlock } from '../entities/newsletter-email-block.entity.js';
4
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
5
+ function wrap(tree) {
6
+ return { schema_version: 1, languages: { default: tree } };
7
+ }
8
+ function unwrap(content, language) {
9
+ const env = content;
10
+ if (!env.languages)
11
+ return content;
12
+ if (language && env.languages[language])
13
+ return env.languages[language];
14
+ const first = Object.values(env.languages)[0];
15
+ return first ?? {};
16
+ }
17
+ /**
18
+ * Reusable email-safe blocks (feature 048, US5). Global blocks embedded into
19
+ * campaign/automation content via the shared renderer's embeds map. System
20
+ * blocks (seeded header/footer) cannot be deleted.
21
+ */
22
+ export class NewsletterEmailBlockService {
23
+ emFactory;
24
+ auditLog;
25
+ constructor(emFactory, auditLog) {
26
+ this.emFactory = emFactory;
27
+ this.auditLog = auditLog;
28
+ }
29
+ #audit(em, action, objectId, stateAfter) {
30
+ if (this.auditLog) {
31
+ recordAuditFromContext(this.auditLog, em, {
32
+ action,
33
+ objectType: 'newsletter_email_block',
34
+ objectId,
35
+ stateBefore: null,
36
+ stateAfter,
37
+ });
38
+ }
39
+ }
40
+ async list() {
41
+ const em = this.emFactory();
42
+ const rows = await em.find(NewsletterEmailBlock, {}, { orderBy: { code: 'asc' } });
43
+ return rows.map((b) => this.toSummary(b));
44
+ }
45
+ async get(id) {
46
+ return this.toDetail(await this.load(this.emFactory(), id));
47
+ }
48
+ async create(input) {
49
+ const em = this.emFactory();
50
+ const existing = await em.findOne(NewsletterEmailBlock, { code: input.code });
51
+ if (existing)
52
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'Block code already exists.');
53
+ const block = em.create(NewsletterEmailBlock, {
54
+ code: input.code,
55
+ name: input.name,
56
+ description: input.description ?? null,
57
+ content: wrap(input.content),
58
+ });
59
+ em.persist(block);
60
+ this.#audit(em, 'newsletter_email_block.create', block.id, { code: block.code });
61
+ await em.flush();
62
+ return this.toDetail(block);
63
+ }
64
+ async update(id, input) {
65
+ const em = this.emFactory();
66
+ const block = await this.load(em, id);
67
+ if (block.version !== input.expectedVersion) {
68
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Block was modified.');
69
+ }
70
+ if (input.name !== undefined)
71
+ block.name = input.name;
72
+ if (input.description !== undefined)
73
+ block.description = input.description;
74
+ if (input.active !== undefined)
75
+ block.active = input.active;
76
+ if (input.content !== undefined)
77
+ block.content = wrap(input.content);
78
+ block.version += 1;
79
+ this.#audit(em, 'newsletter_email_block.update', block.id, { name: block.name });
80
+ await em.persistAndFlush(block);
81
+ return this.toDetail(block);
82
+ }
83
+ async remove(id) {
84
+ const em = this.emFactory();
85
+ const block = await this.load(em, id);
86
+ if (block.isSystem) {
87
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'System blocks cannot be deleted.');
88
+ }
89
+ this.#audit(em, 'newsletter_email_block.delete', block.id, null);
90
+ await em.removeAndFlush(block);
91
+ }
92
+ /** Build the renderer embeds map from active blocks for the given language. */
93
+ async resolveEmbeds(language) {
94
+ const em = this.emFactory();
95
+ const rows = await em.find(NewsletterEmailBlock, { active: true });
96
+ const blocks = {};
97
+ for (const b of rows)
98
+ blocks[b.code] = unwrap(b.content, language);
99
+ return { blocks, templates: {} };
100
+ }
101
+ async load(em, id) {
102
+ const block = await em.findOne(NewsletterEmailBlock, { id });
103
+ if (!block)
104
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Block not found.');
105
+ return block;
106
+ }
107
+ toSummary(b) {
108
+ return { id: b.id, code: b.code, name: b.name, active: b.active, isSystem: b.isSystem };
109
+ }
110
+ toDetail(b) {
111
+ return {
112
+ ...this.toSummary(b),
113
+ description: b.description,
114
+ content: unwrap(b.content),
115
+ salesChannelId: null,
116
+ version: b.version,
117
+ };
118
+ }
119
+ }
120
+ //# sourceMappingURL=email-block.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-block.service.js","sourceRoot":"","sources":["../../../src/backend/services/email-block.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { NewsletterOptInMode } from '@endora-commerce/contracts';
2
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
3
+ import type { NewsletterTokenHelper } from './token.helper.js';
4
+ /**
5
+ * Per-sales-channel opt-in policy (feature 048, research R5). Resolves the
6
+ * single/double mode and confirmation TTL from Settings, and mints the signed
7
+ * confirm/unsubscribe tokens.
8
+ */
9
+ export declare class NewsletterOptInService {
10
+ private readonly settings;
11
+ private readonly tokens;
12
+ constructor(settings: SettingsReadPort, tokens: NewsletterTokenHelper);
13
+ /**
14
+ * `null` = no channel for this subscriber, so the platform-wide value applies
15
+ * (feature 072, D-41). Both are per-storefront settings, which is why the
16
+ * caller resolves the *system-default channel* first and only reaches `null`
17
+ * when the deployment has none.
18
+ */
19
+ resolveMode(salesChannelId: string | null): Promise<NewsletterOptInMode>;
20
+ confirmTtlSeconds(salesChannelId: string | null): Promise<number>;
21
+ mintConfirmToken(subscriberId: string, salesChannelId: string | null): Promise<string>;
22
+ /** Unsubscribe tokens are long-lived (1 year) so links in old mail keep working. */
23
+ mintUnsubscribeToken(subscriberId: string): string;
24
+ }
25
+ //# sourceMappingURL=opt-in.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opt-in.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/opt-in.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/D;;;;GAIG;AACH,qBAAa,sBAAsB;IAE/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,qBAAqB;IAGhD;;;;;OAKG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAYxE,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAajE,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5F,oFAAoF;IACpF,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;CAGnD"}
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ import { NEWSLETTER_SETTING_CODES, newsletterOptInModeSchema } from '@endora-commerce/contracts';
3
+ /**
4
+ * Per-sales-channel opt-in policy (feature 048, research R5). Resolves the
5
+ * single/double mode and confirmation TTL from Settings, and mints the signed
6
+ * confirm/unsubscribe tokens.
7
+ */
8
+ export class NewsletterOptInService {
9
+ settings;
10
+ tokens;
11
+ constructor(settings, tokens) {
12
+ this.settings = settings;
13
+ this.tokens = tokens;
14
+ }
15
+ /**
16
+ * `null` = no channel for this subscriber, so the platform-wide value applies
17
+ * (feature 072, D-41). Both are per-storefront settings, which is why the
18
+ * caller resolves the *system-default channel* first and only reaches `null`
19
+ * when the deployment has none.
20
+ */
21
+ async resolveMode(salesChannelId) {
22
+ try {
23
+ return await this.settings.get(NEWSLETTER_SETTING_CODES.OPT_IN_MODE, salesChannelId, newsletterOptInModeSchema);
24
+ }
25
+ catch {
26
+ return 'double';
27
+ }
28
+ }
29
+ async confirmTtlSeconds(salesChannelId) {
30
+ try {
31
+ const hours = await this.settings.get(NEWSLETTER_SETTING_CODES.CONFIRM_TTL_HOURS, salesChannelId, z.number());
32
+ return Math.max(1, Math.floor(hours)) * 3600;
33
+ }
34
+ catch {
35
+ return 168 * 3600;
36
+ }
37
+ }
38
+ async mintConfirmToken(subscriberId, salesChannelId) {
39
+ return this.tokens.mint('confirm', { id: subscriberId }, await this.confirmTtlSeconds(salesChannelId));
40
+ }
41
+ /** Unsubscribe tokens are long-lived (1 year) so links in old mail keep working. */
42
+ mintUnsubscribeToken(subscriberId) {
43
+ return this.tokens.mint('unsubscribe', { id: subscriberId }, 365 * 24 * 3600);
44
+ }
45
+ }
46
+ //# sourceMappingURL=opt-in.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opt-in.service.js","sourceRoot":"","sources":["../../../src/backend/services/opt-in.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAKjG;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IAEd;IACA;IAFnB,YACmB,QAA0B,EAC1B,MAA6B;QAD7B,aAAQ,GAAR,QAAQ,CAAkB;QAC1B,WAAM,GAAN,MAAM,CAAuB;IAC7C,CAAC;IAEJ;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,cAA6B;QAC7C,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAC5B,wBAAwB,CAAC,WAAW,EACpC,cAAc,EACd,yBAAyB,CAC1B,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,cAA6B;QACnD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CACnC,wBAAwB,CAAC,iBAAiB,EAC1C,cAAc,EACd,CAAC,CAAC,MAAM,EAAE,CACX,CAAC;YACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,YAAoB,EAAE,cAA6B;QACxE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,MAAM,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC,CAAC;IACzG,CAAC;IAED,oFAAoF;IACpF,oBAAoB,CAAC,YAAoB;QACvC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAChF,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ import type { NewsletterSendMessage, NewsletterSendProvider } from '@endora-commerce/contracts';
2
+ /**
3
+ * Default newsletter provider for dev/tests (feature 048). Logs the message and
4
+ * is idempotent on `messageId`, mirroring the transactional ConsoleMailer. Real
5
+ * bulk delivery uses the SmtpProvider configured from Settings.
6
+ */
7
+ export declare class ConsoleNewsletterProvider implements NewsletterSendProvider {
8
+ private readonly seen;
9
+ send(msg: NewsletterSendMessage): Promise<{
10
+ providerMessageId?: string;
11
+ }>;
12
+ verify(): Promise<{
13
+ ok: true;
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ }>;
18
+ }
19
+ /** In-memory provider for tests — captures every dispatched message. */
20
+ export declare class InMemoryNewsletterProvider implements NewsletterSendProvider {
21
+ readonly sent: NewsletterSendMessage[];
22
+ send(msg: NewsletterSendMessage): Promise<{
23
+ providerMessageId?: string;
24
+ }>;
25
+ verify(): Promise<{
26
+ ok: true;
27
+ } | {
28
+ ok: false;
29
+ error: string;
30
+ }>;
31
+ }
32
+ //# sourceMappingURL=console-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-provider.d.ts","sourceRoot":"","sources":["../../../../src/backend/services/provider/console-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEhG;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,sBAAsB;IACtE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAEpC,IAAI,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAUzE,MAAM,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAGrE;AAED,wEAAwE;AACxE,qBAAa,0BAA2B,YAAW,sBAAsB;IACvE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,EAAE,CAAM;IAEtC,IAAI,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAKzE,MAAM,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAGrE"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Default newsletter provider for dev/tests (feature 048). Logs the message and
3
+ * is idempotent on `messageId`, mirroring the transactional ConsoleMailer. Real
4
+ * bulk delivery uses the SmtpProvider configured from Settings.
5
+ */
6
+ export class ConsoleNewsletterProvider {
7
+ seen = new Set();
8
+ async send(msg) {
9
+ if (this.seen.has(msg.messageId))
10
+ return { providerMessageId: msg.messageId };
11
+ this.seen.add(msg.messageId);
12
+ // eslint-disable-next-line no-console
13
+ console.log(JSON.stringify({ msg: 'console-newsletter', to: msg.to, subject: msg.subject }));
14
+ return { providerMessageId: msg.messageId };
15
+ }
16
+ async verify() {
17
+ return { ok: true };
18
+ }
19
+ }
20
+ /** In-memory provider for tests — captures every dispatched message. */
21
+ export class InMemoryNewsletterProvider {
22
+ sent = [];
23
+ async send(msg) {
24
+ this.sent.push(msg);
25
+ return { providerMessageId: msg.messageId };
26
+ }
27
+ async verify() {
28
+ return { ok: true };
29
+ }
30
+ }
31
+ //# sourceMappingURL=console-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-provider.js","sourceRoot":"","sources":["../../../../src/backend/services/provider/console-provider.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,yBAAyB;IACnB,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,KAAK,CAAC,IAAI,CAAC,GAA0B;QACnC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;QAC9E,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7B,sCAAsC;QACtC,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,oBAAoB,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAChF,CAAC;QACF,OAAO,EAAE,iBAAiB,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;CACF;AAED,wEAAwE;AACxE,MAAM,OAAO,0BAA0B;IAC5B,IAAI,GAA4B,EAAE,CAAC;IAE5C,KAAK,CAAC,IAAI,CAAC,GAA0B;QACnC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,OAAO,EAAE,iBAAiB,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,51 @@
1
+ import type { NewsletterSendProvider, ResolveResult } from '@endora-commerce/contracts';
2
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
3
+ export interface ResolvedSender {
4
+ fromEmail: string;
5
+ fromName: string;
6
+ }
7
+ /**
8
+ * Narrow port over CredentialsService.resolve (feature 058). The newsletter
9
+ * module reaches credentials ONLY through this shape (Principle I).
10
+ */
11
+ export interface CredentialResolvePort {
12
+ resolve(configurationCode: string): Promise<ResolveResult>;
13
+ }
14
+ /**
15
+ * Selects and builds the active newsletter sending provider (feature 048,
16
+ * research R7; feature 058 — the credential reference is the single source).
17
+ *
18
+ * The `newsletter.email_credentials` reference (a reusable `email_adapter`
19
+ * credential configuration) supplies the SMTP transport. When it is unset or
20
+ * unresolvable, dispatch fails closed to the console (dev) sink — the pre-058
21
+ * default. Secrets are decrypted in-memory by `CredentialsService.resolve`.
22
+ */
23
+ export declare class NewsletterProviderRegistry {
24
+ private readonly settings;
25
+ private readonly credentials?;
26
+ constructor(settings: SettingsReadPort, credentials?: CredentialResolvePort | undefined);
27
+ /**
28
+ * Platform-wide (feature 072, D-41). The sending provider is one SMTP
29
+ * transport for the deployment — `NewsletterProviderAdminService` writes it
30
+ * with `setValueForAllChannels` — so a read scoped to a channel was always
31
+ * asking the wrong question. It used to take a `platformChannelId` the root
32
+ * resolved to the system-default channel, falling back to the `'default'`
33
+ * sentinel; the name is gone rather than corrected, because a DI name
34
+ * carrying a sentinel is one no static check can see.
35
+ */
36
+ private getString;
37
+ /**
38
+ * Resolve the referenced SMTP config, or `null` when no usable reference is
39
+ * configured (caller then uses the console dev sink).
40
+ */
41
+ private resolveCredentialSmtp;
42
+ resolveProvider(): Promise<NewsletterSendProvider>;
43
+ /**
44
+ * Gate for campaign sends (FR-017). A transport is always available — a
45
+ * resolvable `email_adapter` credential gives a real SMTP transport, otherwise
46
+ * the console dev sink (the pre-058 default) — so sends are never blocked here.
47
+ */
48
+ isConfigured(): Promise<boolean>;
49
+ resolveSender(): Promise<ResolvedSender>;
50
+ }
51
+ //# sourceMappingURL=provider-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-registry.d.ts","sourceRoot":"","sources":["../../../../src/backend/services/provider/provider-registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAIzE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC5D;AAED;;;;;;;;GAQG;AACH,qBAAa,0BAA0B;IAEnC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBADZ,QAAQ,EAAE,gBAAgB,EAC1B,WAAW,CAAC,EAAE,qBAAqB,YAAA;IAGtD;;;;;;;;OAQG;YACW,SAAS;IAIvB;;;OAGG;YACW,qBAAqB;IA0B7B,eAAe,IAAI,OAAO,CAAC,sBAAsB,CAAC;IASxD;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC,aAAa,IAAI,OAAO,CAAC,cAAc,CAAC;CAO/C"}
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod';
2
+ import { NEWSLETTER_SETTING_CODES } from '@endora-commerce/contracts';
3
+ import { ConsoleNewsletterProvider } from './console-provider.js';
4
+ import { SmtpProvider } from './smtp-provider.js';
5
+ /**
6
+ * Selects and builds the active newsletter sending provider (feature 048,
7
+ * research R7; feature 058 — the credential reference is the single source).
8
+ *
9
+ * The `newsletter.email_credentials` reference (a reusable `email_adapter`
10
+ * credential configuration) supplies the SMTP transport. When it is unset or
11
+ * unresolvable, dispatch fails closed to the console (dev) sink — the pre-058
12
+ * default. Secrets are decrypted in-memory by `CredentialsService.resolve`.
13
+ */
14
+ export class NewsletterProviderRegistry {
15
+ settings;
16
+ credentials;
17
+ constructor(settings, credentials) {
18
+ this.settings = settings;
19
+ this.credentials = credentials;
20
+ }
21
+ /**
22
+ * Platform-wide (feature 072, D-41). The sending provider is one SMTP
23
+ * transport for the deployment — `NewsletterProviderAdminService` writes it
24
+ * with `setValueForAllChannels` — so a read scoped to a channel was always
25
+ * asking the wrong question. It used to take a `platformChannelId` the root
26
+ * resolved to the system-default channel, falling back to the `'default'`
27
+ * sentinel; the name is gone rather than corrected, because a DI name
28
+ * carrying a sentinel is one no static check can see.
29
+ */
30
+ async getString(code) {
31
+ return this.settings.get(code, null, z.string());
32
+ }
33
+ /**
34
+ * Resolve the referenced SMTP config, or `null` when no usable reference is
35
+ * configured (caller then uses the console dev sink).
36
+ */
37
+ async resolveCredentialSmtp() {
38
+ if (!this.credentials)
39
+ return null;
40
+ const ref = await this.getString(NEWSLETTER_SETTING_CODES.EMAIL_CREDENTIALS).catch(() => '');
41
+ if (!ref)
42
+ return null;
43
+ const resolved = await this.credentials.resolve(ref);
44
+ // Only the `smtp` provider variant has a wired transport today; any other
45
+ // provider (or an unavailable/inert reference) falls back to the dev sink.
46
+ if (resolved.status !== 'ok' || resolved.providerCode !== 'smtp')
47
+ return null;
48
+ const v = resolved.values;
49
+ const host = typeof v['host'] === 'string' ? v['host'] : '';
50
+ if (!host)
51
+ return null;
52
+ return {
53
+ host,
54
+ port: typeof v['port'] === 'number' ? v['port'] : 587,
55
+ secure: typeof v['secure'] === 'boolean' ? v['secure'] : false,
56
+ username: typeof v['username'] === 'string' ? v['username'] : '',
57
+ password: typeof v['password'] === 'string' ? v['password'] : '',
58
+ };
59
+ }
60
+ async resolveProvider() {
61
+ const credentialSmtp = await this.resolveCredentialSmtp();
62
+ if (credentialSmtp) {
63
+ return new SmtpProvider(credentialSmtp);
64
+ }
65
+ // Fail closed to the console dev sink when no email credential is set.
66
+ return new ConsoleNewsletterProvider();
67
+ }
68
+ /**
69
+ * Gate for campaign sends (FR-017). A transport is always available — a
70
+ * resolvable `email_adapter` credential gives a real SMTP transport, otherwise
71
+ * the console dev sink (the pre-058 default) — so sends are never blocked here.
72
+ */
73
+ async isConfigured() {
74
+ return true;
75
+ }
76
+ async resolveSender() {
77
+ const [fromEmail, fromName] = await Promise.all([
78
+ this.getString(NEWSLETTER_SETTING_CODES.SENDER_FROM_EMAIL),
79
+ this.getString(NEWSLETTER_SETTING_CODES.SENDER_FROM_NAME),
80
+ ]);
81
+ return { fromEmail, fromName };
82
+ }
83
+ }
84
+ //# sourceMappingURL=provider-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-registry.js","sourceRoot":"","sources":["../../../../src/backend/services/provider/provider-registry.ts"],"names":[],"mappings":"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"}