@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":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
2
+ import type { NewsletterSelfService } from './services/self.service.js';
3
+ export interface NewsletterSelfDeps {
4
+ self: NewsletterSelfService;
5
+ requireCustomer: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
6
+ resolveCustomerAccountId: (req: FastifyRequest) => string;
7
+ /** Resolve the signed-in customer's email (cross-module lookup, injected). */
8
+ loadCustomerEmail: (customerAccountId: string) => Promise<string | null>;
9
+ }
10
+ /**
11
+ * Authenticated customer newsletter routes (feature 048, US9). Each endpoint is
12
+ * scoped to the signed-in customer's own subscription.
13
+ */
14
+ export declare function registerNewsletterSelfRoutes(app: FastifyInstance, deps: NewsletterSelfDeps): Promise<void>;
15
+ //# sourceMappingURL=routes.self.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.self.d.ts","sourceRoot":"","sources":["../../src/backend/routes.self.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAO7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,eAAe,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,wBAAwB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,MAAM,CAAC;IAC1D,8EAA8E;IAC9E,iBAAiB,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1E;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -0,0 +1,28 @@
1
+ import { ERROR_CODES, selfSubscribeRequestSchema, selfUnsubscribeRequestSchema, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ /**
4
+ * Authenticated customer newsletter routes (feature 048, US9). Each endpoint is
5
+ * scoped to the signed-in customer's own subscription.
6
+ */
7
+ export async function registerNewsletterSelfRoutes(app, deps) {
8
+ const guard = { preHandler: deps.requireCustomer };
9
+ const emailOf = async (req) => {
10
+ const email = await deps.loadCustomerEmail(deps.resolveCustomerAccountId(req));
11
+ if (!email)
12
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Customer not found.');
13
+ return email;
14
+ };
15
+ app.get('/api/v1/me/newsletter', guard, async (req, reply) => reply.send({ data: await deps.self.getStatus(await emailOf(req)) }));
16
+ app.post('/api/v1/me/newsletter/subscribe', guard, async (req, reply) => {
17
+ const body = selfSubscribeRequestSchema.parse(req.body ?? {});
18
+ const accountId = deps.resolveCustomerAccountId(req);
19
+ const email = await emailOf(req);
20
+ const result = await deps.self.subscribe(email, accountId, body.tags, body.customFields);
21
+ return reply.send({ data: result });
22
+ });
23
+ app.post('/api/v1/me/newsletter/unsubscribe', guard, async (req, reply) => {
24
+ const body = selfUnsubscribeRequestSchema.parse(req.body ?? {});
25
+ return reply.send({ data: await deps.self.unsubscribe(await emailOf(req), body.reason) });
26
+ });
27
+ }
28
+ //# sourceMappingURL=routes.self.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.self.js","sourceRoot":"","sources":["../../src/backend/routes.self.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAW3D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,GAAoB,EACpB,IAAwB;IAExB,MAAM,KAAK,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC;IAEnD,MAAM,OAAO,GAAG,KAAK,EAAE,GAAmB,EAAmB,EAAE;QAC7D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;QACnF,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,CAAC,uBAAuB,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,CAC3D,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CACpE,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,iCAAiC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QACtE,MAAM,IAAI,GAAG,0BAA0B,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACzF,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,mCAAmC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QACxE,MAAM,IAAI,GAAG,4BAA4B,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAChE,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { NewsletterSubscriberService } from './services/subscriber.service.js';
3
+ import type { NewsletterOptInService } from './services/opt-in.service.js';
4
+ import type { NewsletterTokenHelper } from './services/token.helper.js';
5
+ import type { NewsletterTrackingService } from './services/tracking.service.js';
6
+ export interface NewsletterStorefrontDeps {
7
+ subscribers: NewsletterSubscriberService;
8
+ optIn: NewsletterOptInService;
9
+ tokens: NewsletterTokenHelper;
10
+ tracking: NewsletterTrackingService;
11
+ /** Resolve a sales-channel code to its id; null when unknown. */
12
+ resolveChannelIdByCode: (code: string) => Promise<string | null>;
13
+ /** The channel a request with no `?channel=` belongs to; null = none. */
14
+ resolveDefaultChannelId: () => Promise<string | null>;
15
+ /** Storefront base used for post-confirm/unsubscribe redirects. */
16
+ storefrontBaseUrl: string;
17
+ }
18
+ /**
19
+ * Public storefront newsletter routes (feature 048, US1/US9). Mutating routes
20
+ * never reveal whether an email already exists (no enumeration). Wrapped by
21
+ * `defineModuleRoutes` in plugin.ts so they 503 when the module is disabled.
22
+ */
23
+ export declare function registerNewsletterStorefrontRoutes(app: FastifyInstance, deps: NewsletterStorefrontDeps): Promise<void>;
24
+ //# sourceMappingURL=routes.storefront.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.d.ts","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAO/C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AACpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAQhF,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,2BAA2B,CAAC;IACzC,KAAK,EAAE,sBAAsB,CAAC;IAC9B,MAAM,EAAE,qBAAqB,CAAC;IAC9B,QAAQ,EAAE,yBAAyB,CAAC;IACpC,iEAAiE;IACjE,sBAAsB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjE,yEAAyE;IACzE,uBAAuB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtD,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,kCAAkC,CACtD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAwEf"}
@@ -0,0 +1,79 @@
1
+ import { newsletterSubscribeRequestSchema, publicUnsubscribeRequestSchema, ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ /** 1×1 transparent GIF for open tracking. */
4
+ const TRACKING_PIXEL = Buffer.from('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64');
5
+ /**
6
+ * Public storefront newsletter routes (feature 048, US1/US9). Mutating routes
7
+ * never reveal whether an email already exists (no enumeration). Wrapped by
8
+ * `defineModuleRoutes` in plugin.ts so they 503 when the module is disabled.
9
+ */
10
+ export async function registerNewsletterStorefrontRoutes(app, deps) {
11
+ app.get('/api/v1/newsletter/status', async (request, reply) => {
12
+ const { channel } = request.query;
13
+ const channelId = channel ? await deps.resolveChannelIdByCode(channel) : null;
14
+ const optInMode = await deps.optIn.resolveMode(channelId ?? (await deps.resolveDefaultChannelId()));
15
+ return reply.send({ data: { enabled: true, optInMode } });
16
+ });
17
+ app.post('/api/v1/newsletter/subscribe', async (request, reply) => {
18
+ const body = newsletterSubscribeRequestSchema.parse(request.body);
19
+ const channelId = await deps.resolveChannelIdByCode(body.channelCode);
20
+ if (!channelId) {
21
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, 'Unknown sales channel.', {
22
+ code: 'unknown_channel',
23
+ });
24
+ }
25
+ const result = await deps.subscribers.subscribe({
26
+ email: body.email,
27
+ salesChannelId: channelId,
28
+ source: body.source ?? 'storefront',
29
+ ...(body.tags ? { tags: body.tags } : {}),
30
+ ...(body.customFields ? { customFields: body.customFields } : {}),
31
+ });
32
+ return reply.send({ data: result });
33
+ });
34
+ app.get('/api/v1/newsletter/confirm', async (request, reply) => {
35
+ const { token } = request.query;
36
+ const claims = token ? deps.tokens.verify(token, 'confirm') : null;
37
+ if (claims)
38
+ await deps.subscribers.confirm(claims.id);
39
+ const status = claims ? 'ok' : 'invalid';
40
+ return reply.redirect(`${deps.storefrontBaseUrl}/newsletter/confirm?status=${status}`);
41
+ });
42
+ app.get('/api/v1/newsletter/unsubscribe', async (request, reply) => {
43
+ const { token } = request.query;
44
+ const valid = token ? deps.tokens.verify(token, 'unsubscribe') !== null : false;
45
+ return reply.redirect(`${deps.storefrontBaseUrl}/newsletter/unsubscribe?token=${encodeURIComponent(token ?? '')}&valid=${valid}`);
46
+ });
47
+ app.post('/api/v1/newsletter/unsubscribe', async (request, reply) => {
48
+ const body = publicUnsubscribeRequestSchema.parse(request.body);
49
+ const claims = deps.tokens.verify(body.token, 'unsubscribe');
50
+ if (claims)
51
+ await deps.subscribers.unsubscribe(claims.id, body.reason);
52
+ // Always neutral — invalid tokens look the same as success.
53
+ return reply.send({ data: { ok: true } });
54
+ });
55
+ // --- Engagement tracking (US6) -----------------------------------------
56
+ app.get('/api/v1/newsletter/o/:token.gif', { config: { streamingResponse: true } }, async (request, reply) => {
57
+ const { token } = request.params;
58
+ const claims = deps.tokens.verify(token, 'open');
59
+ if (claims)
60
+ await deps.tracking.recordOpen(claims.id);
61
+ reply.header('content-type', 'image/gif');
62
+ reply.header('cache-control', 'no-store, no-cache, must-revalidate, private');
63
+ return reply.send(TRACKING_PIXEL);
64
+ });
65
+ app.get('/api/v1/newsletter/c/:token', async (request, reply) => {
66
+ const { token } = request.params;
67
+ const { u } = request.query;
68
+ const claims = deps.tokens.verify(token, 'click');
69
+ // Only redirect to absolute http(s) destinations (no open-redirect to other schemes).
70
+ const dest = u && /^https?:\/\//i.test(u) ? u : null;
71
+ if (claims && dest)
72
+ await deps.tracking.recordClick(claims.id, claims.linkId ?? null, dest);
73
+ if (!dest) {
74
+ return reply.status(410).send({ error: { code: 'GONE', message: 'Invalid link.' } });
75
+ }
76
+ return reply.redirect(dest);
77
+ });
78
+ }
79
+ //# sourceMappingURL=routes.storefront.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.js","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ export interface AudienceTarget {
3
+ type: 'all' | 'group' | 'tag' | 'tag_list';
4
+ tagIds?: string[];
5
+ /** Campaign id, required for `group` targeting. */
6
+ campaignId?: string;
7
+ }
8
+ /**
9
+ * Resolves a campaign/automation target to the set of eligible subscriber ids
10
+ * (feature 048, US2). Eligibility = `status = 'active'` AND not present in
11
+ * `newsletter_suppressions` (the suppression list overrides all targeting —
12
+ * FR-015/035). Pending/unsubscribed/deactivated subscribers are excluded by the
13
+ * status filter.
14
+ */
15
+ export declare class NewsletterAudienceResolver {
16
+ private readonly emFactory;
17
+ constructor(emFactory: () => EntityManager);
18
+ resolve(target: AudienceTarget): Promise<string[]>;
19
+ }
20
+ //# sourceMappingURL=audience-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audience-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/audience-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,CAAC;IAC3C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,qBAAa,0BAA0B;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CA8BzD"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Resolves a campaign/automation target to the set of eligible subscriber ids
3
+ * (feature 048, US2). Eligibility = `status = 'active'` AND not present in
4
+ * `newsletter_suppressions` (the suppression list overrides all targeting —
5
+ * FR-015/035). Pending/unsubscribed/deactivated subscribers are excluded by the
6
+ * status filter.
7
+ */
8
+ export class NewsletterAudienceResolver {
9
+ emFactory;
10
+ constructor(emFactory) {
11
+ this.emFactory = emFactory;
12
+ }
13
+ async resolve(target) {
14
+ const em = this.emFactory();
15
+ const conditions = [
16
+ `s.status = 'active'`,
17
+ `not exists (select 1 from newsletter_suppressions sup where sup.email = s.email)`,
18
+ ];
19
+ const params = [];
20
+ if (target.type === 'tag' || target.type === 'tag_list') {
21
+ const tagIds = target.tagIds ?? [];
22
+ if (tagIds.length === 0)
23
+ return [];
24
+ const placeholders = tagIds.map(() => '?').join(', ');
25
+ params.push(...tagIds);
26
+ conditions.push(`exists (select 1 from newsletter_subscriber_tags st where st.subscriber_id = s.id and st.tag_id in (${placeholders}))`);
27
+ }
28
+ else if (target.type === 'group') {
29
+ if (!target.campaignId)
30
+ return [];
31
+ params.push(target.campaignId);
32
+ conditions.push(`exists (select 1 from newsletter_campaign_subscribers cs where cs.subscriber_id = s.id and cs.campaign_id = ?)`);
33
+ }
34
+ const sql = `select s.id from newsletter_subscribers s where ${conditions.join(' and ')}`;
35
+ const rows = (await em
36
+ .getConnection()
37
+ .execute(sql, params, 'all', em.getTransactionContext()));
38
+ return rows.map((r) => r.id);
39
+ }
40
+ }
41
+ //# sourceMappingURL=audience-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audience-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/audience-resolver.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,MAAM,OAAO,0BAA0B;IACR;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,OAAO,CAAC,MAAsB;QAClC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAa;YAC3B,qBAAqB;YACrB,kFAAkF;SACnF,CAAC;QACF,MAAM,MAAM,GAAc,EAAE,CAAC;QAE7B,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACxD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;YACnC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YACnC,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;YACvB,UAAU,CAAC,IAAI,CACb,uGAAuG,YAAY,IAAI,CACxH,CAAC;QACJ,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,CAAC,UAAU;gBAAE,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAC/B,UAAU,CAAC,IAAI,CACb,gHAAgH,CACjH,CAAC;QACJ,CAAC;QAED,MAAM,GAAG,GAAG,mDAAmD,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1F,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE;aACnB,aAAa,EAAE;aACf,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,qBAAqB,EAAE,CAAC,CAA0B,CAAC;QACrF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,58 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type AutomationDetail, type AutomationStep, type AutomationSummary, type CreateAutomationRequest, type NewsletterSendProvider, type UpdateAutomationRequest } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import type { NewsletterContentService, EmailBrandingResolver } from './content.service.js';
5
+ import type { NewsletterOptInService } from './opt-in.service.js';
6
+ import type { NewsletterLinkBuilder } from './subscriber.service.js';
7
+ export interface AutomationServiceDeps {
8
+ emFactory: () => EntityManager;
9
+ content: NewsletterContentService;
10
+ optIn: NewsletterOptInService;
11
+ links: NewsletterLinkBuilder;
12
+ resolveProvider: () => Promise<NewsletterSendProvider>;
13
+ resolveSender: () => Promise<{
14
+ fromEmail: string;
15
+ fromName: string;
16
+ }>;
17
+ /** Enqueue the next step (production: BullMQ delayed job). */
18
+ enqueueStep: (runId: string, stepIndex: number, delayMs: number) => Promise<void>;
19
+ /** Feature 054 — audits automation lifecycle writes co-transactionally when provided. */
20
+ auditLog?: AuditPort;
21
+ resolveEmailBranding?: EmailBrandingResolver;
22
+ }
23
+ /**
24
+ * Newsletter automations (feature 048, US4). Linear send/wait sequences with a
25
+ * step engine driven by BullMQ delayed jobs. The model is extensible to future
26
+ * conditional/branching node types (research R9).
27
+ */
28
+ export declare class NewsletterAutomationService {
29
+ #private;
30
+ private readonly deps;
31
+ constructor(deps: AutomationServiceDeps);
32
+ /** Validation issues that block activation (FR-022). Empty ⇒ valid. */
33
+ static validate(triggerType: string, triggerTagIds: string[], steps: AutomationStep[]): string[];
34
+ create(input: CreateAutomationRequest): Promise<AutomationDetail>;
35
+ update(id: string, input: UpdateAutomationRequest): Promise<AutomationDetail>;
36
+ get(id: string): Promise<AutomationDetail>;
37
+ list(): Promise<{
38
+ items: AutomationSummary[];
39
+ }>;
40
+ activate(id: string, expectedVersion: number): Promise<AutomationDetail>;
41
+ pause(id: string, expectedVersion: number): Promise<AutomationDetail>;
42
+ /**
43
+ * Enrol a subscriber into an active automation: create the run (the partial
44
+ * unique index enforces the `once` re-entry policy) and enqueue step 0.
45
+ * Returns the run id, or null when already enrolled / not active.
46
+ */
47
+ enrol(automationId: string, subscriberId: string): Promise<string | null>;
48
+ /** Execute one automation step for a run. Idempotent on (run, stepIndex). */
49
+ processStep(runId: string, stepIndex: number): Promise<void>;
50
+ /** Cancel all active runs for a subscriber (on unsubscribe/delete — FR-021). */
51
+ cancelRunsForSubscriber(subscriberId: string): Promise<void>;
52
+ private claimStep;
53
+ private sendStep;
54
+ private load;
55
+ private toSummary;
56
+ private toDetail;
57
+ }
58
+ //# sourceMappingURL=automation.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automation.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/automation.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAKlE,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE5F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAIrE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;IAClC,KAAK,EAAE,sBAAsB,CAAC;IAC9B,KAAK,EAAE,qBAAqB,CAAC;IAC7B,eAAe,EAAE,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACvD,aAAa,EAAE,MAAM,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,8DAA8D;IAC9D,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,yFAAyF;IACzF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,oBAAoB,CAAC,EAAE,qBAAqB,CAAC;CAC9C;AAED;;;;GAIG;AACH,qBAAa,2BAA2B;;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,qBAAqB;IAcxD,uEAAuE;IACvE,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,EAAE;IAkB1F,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiBjE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsB7E,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI1C,IAAI,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,iBAAiB,EAAE,CAAA;KAAE,CAAC;IAM/C,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwBxE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAa3E;;;;OAIG;IACG,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAoB/E,6EAA6E;IACvE,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgDlE,gFAAgF;IAC1E,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAapD,SAAS;YAoBT,QAAQ;YAsDR,IAAI;IAMlB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,QAAQ;CAYjB"}
@@ -0,0 +1,301 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
5
+ import { NewsletterAutomation } from '../entities/newsletter-automation.entity.js';
6
+ import { NewsletterAutomationRun } from '../entities/newsletter-automation-run.entity.js';
7
+ import { NewsletterSubscriber } from '../entities/newsletter-subscriber.entity.js';
8
+ import { NewsletterSendRecord } from '../entities/newsletter-send-record.entity.js';
9
+ import { withEmailBranding } from './content.service.js';
10
+ const DAY_MS = 86_400_000;
11
+ /**
12
+ * Newsletter automations (feature 048, US4). Linear send/wait sequences with a
13
+ * step engine driven by BullMQ delayed jobs. The model is extensible to future
14
+ * conditional/branching node types (research R9).
15
+ */
16
+ export class NewsletterAutomationService {
17
+ deps;
18
+ constructor(deps) {
19
+ this.deps = deps;
20
+ }
21
+ #audit(em, action, objectId, stateAfter) {
22
+ if (this.deps.auditLog) {
23
+ recordAuditFromContext(this.deps.auditLog, em, {
24
+ action,
25
+ objectType: 'newsletter_automation',
26
+ objectId,
27
+ stateBefore: null,
28
+ stateAfter,
29
+ });
30
+ }
31
+ }
32
+ /** Validation issues that block activation (FR-022). Empty ⇒ valid. */
33
+ static validate(triggerType, triggerTagIds, steps) {
34
+ const issues = [];
35
+ if ((triggerType === 'tag' || triggerType === 'tag_list') && triggerTagIds.length === 0) {
36
+ issues.push('Trigger requires at least one tag.');
37
+ }
38
+ if (steps.length === 0)
39
+ issues.push('Automation has no steps.');
40
+ if (!steps.some((s) => s.type === 'send'))
41
+ issues.push('Automation must contain at least one send step.');
42
+ if (steps.length > 0 && steps[steps.length - 1]?.type !== 'send') {
43
+ issues.push('The last step must be a send (no trailing wait).');
44
+ }
45
+ for (const [i, s] of steps.entries()) {
46
+ if (s.type === 'send' && (!s.subject || s.subject.trim().length === 0)) {
47
+ issues.push(`Send step ${i + 1} has an empty subject.`);
48
+ }
49
+ }
50
+ return issues;
51
+ }
52
+ async create(input) {
53
+ const em = this.deps.emFactory();
54
+ const automation = em.create(NewsletterAutomation, {
55
+ name: input.name,
56
+ triggerType: input.triggerType,
57
+ triggerTagIds: input.triggerTagIds ?? [],
58
+ salesChannelId: input.salesChannelId ?? null,
59
+ language: input.language,
60
+ reentryPolicy: input.reentryPolicy,
61
+ steps: input.steps,
62
+ });
63
+ em.persist(automation);
64
+ this.#audit(em, 'newsletter_automation.create', automation.id, { name: automation.name });
65
+ await em.flush();
66
+ return this.toDetail(automation);
67
+ }
68
+ async update(id, input) {
69
+ const em = this.deps.emFactory();
70
+ const a = await this.load(em, id);
71
+ if (a.version !== input.expectedVersion) {
72
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Automation was modified.');
73
+ }
74
+ if (a.status === 'active') {
75
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Pause the automation before editing.');
76
+ }
77
+ if (input.name !== undefined)
78
+ a.name = input.name;
79
+ if (input.triggerType !== undefined)
80
+ a.triggerType = input.triggerType;
81
+ if (input.triggerTagIds !== undefined)
82
+ a.triggerTagIds = input.triggerTagIds;
83
+ if (input.salesChannelId !== undefined)
84
+ a.salesChannelId = input.salesChannelId ?? null;
85
+ if (input.language !== undefined)
86
+ a.language = input.language;
87
+ if (input.reentryPolicy !== undefined)
88
+ a.reentryPolicy = input.reentryPolicy;
89
+ if (input.steps !== undefined)
90
+ a.steps = input.steps;
91
+ a.version += 1;
92
+ this.#audit(em, 'newsletter_automation.update', a.id, { name: a.name });
93
+ await em.persistAndFlush(a);
94
+ return this.toDetail(a);
95
+ }
96
+ async get(id) {
97
+ return this.toDetail(await this.load(this.deps.emFactory(), id));
98
+ }
99
+ async list() {
100
+ const em = this.deps.emFactory();
101
+ const rows = await em.find(NewsletterAutomation, {}, { orderBy: { createdAt: 'desc' } });
102
+ return { items: rows.map((a) => this.toSummary(a)) };
103
+ }
104
+ async activate(id, expectedVersion) {
105
+ const em = this.deps.emFactory();
106
+ const a = await this.load(em, id);
107
+ if (a.version !== expectedVersion) {
108
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Automation was modified.');
109
+ }
110
+ const issues = NewsletterAutomationService.validate(a.triggerType, a.triggerTagIds, a.steps);
111
+ if (issues.length > 0) {
112
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Automation is invalid.', {
113
+ code: 'automation_invalid',
114
+ issues,
115
+ });
116
+ }
117
+ a.status = 'active';
118
+ a.version += 1;
119
+ this.#audit(em, 'newsletter_automation.activate', a.id, { status: 'active' });
120
+ await em.persistAndFlush(a);
121
+ return this.toDetail(a);
122
+ }
123
+ async pause(id, expectedVersion) {
124
+ const em = this.deps.emFactory();
125
+ const a = await this.load(em, id);
126
+ if (a.version !== expectedVersion) {
127
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Automation was modified.');
128
+ }
129
+ a.status = 'paused';
130
+ a.version += 1;
131
+ this.#audit(em, 'newsletter_automation.pause', a.id, { status: 'paused' });
132
+ await em.persistAndFlush(a);
133
+ return this.toDetail(a);
134
+ }
135
+ /**
136
+ * Enrol a subscriber into an active automation: create the run (the partial
137
+ * unique index enforces the `once` re-entry policy) and enqueue step 0.
138
+ * Returns the run id, or null when already enrolled / not active.
139
+ */
140
+ async enrol(automationId, subscriberId) {
141
+ const em = this.deps.emFactory();
142
+ const a = await em.findOne(NewsletterAutomation, { id: automationId });
143
+ if (!a || a.status !== 'active')
144
+ return null;
145
+ const id = randomUUID();
146
+ const rows = (await em.getConnection().execute(`insert into newsletter_automation_runs (id, automation_id, subscriber_id, current_step, status, created_at, updated_at)
147
+ values (?, ?, ?, 0, 'active', now(), now())
148
+ on conflict (automation_id, subscriber_id) where status <> 'cancelled'
149
+ do nothing
150
+ returning id`, [id, automationId, subscriberId], 'all', em.getTransactionContext()));
151
+ if (rows.length === 0)
152
+ return null;
153
+ await this.deps.enqueueStep(id, 0, 0);
154
+ return id;
155
+ }
156
+ /** Execute one automation step for a run. Idempotent on (run, stepIndex). */
157
+ async processStep(runId, stepIndex) {
158
+ // command-coverage-ignore: automation run execution — advances a run to its
159
+ // next step; background workflow execution, not an operator write.
160
+ const em = this.deps.emFactory();
161
+ const run = await em.findOne(NewsletterAutomationRun, { id: runId });
162
+ if (!run || run.status !== 'active')
163
+ return;
164
+ if (run.currentStep !== stepIndex)
165
+ return; // already advanced — idempotent
166
+ // A subscriber who unsubscribed / was deactivated mid-sequence stops here
167
+ // (FR-021): the run self-cancels and no further steps are scheduled.
168
+ const subscriber = await em.findOne(NewsletterSubscriber, { id: run.subscriberId });
169
+ if (!subscriber || subscriber.status !== 'active') {
170
+ run.status = 'cancelled';
171
+ run.nextStepAt = null;
172
+ run.nextStepJobId = null;
173
+ await em.persistAndFlush(run);
174
+ return;
175
+ }
176
+ const automation = await em.findOneOrFail(NewsletterAutomation, { id: run.automationId });
177
+ const steps = automation.steps;
178
+ const step = steps[stepIndex];
179
+ if (!step) {
180
+ run.status = 'completed';
181
+ await em.persistAndFlush(run);
182
+ return;
183
+ }
184
+ if (step.type === 'send') {
185
+ const recordId = await this.claimStep(em, runId, stepIndex, run.subscriberId);
186
+ if (recordId)
187
+ await this.sendStep(em, automation, step, run.subscriberId, recordId);
188
+ }
189
+ const nextIndex = stepIndex + 1;
190
+ run.currentStep = nextIndex;
191
+ if (nextIndex >= steps.length) {
192
+ run.status = 'completed';
193
+ run.nextStepAt = null;
194
+ run.nextStepJobId = null;
195
+ await em.persistAndFlush(run);
196
+ return;
197
+ }
198
+ const delayMs = step.type === 'wait' ? step.days * DAY_MS : 0;
199
+ run.nextStepAt = new Date(Date.now() + delayMs);
200
+ await em.persistAndFlush(run);
201
+ await this.deps.enqueueStep(runId, nextIndex, delayMs);
202
+ }
203
+ /** Cancel all active runs for a subscriber (on unsubscribe/delete — FR-021). */
204
+ async cancelRunsForSubscriber(subscriberId) {
205
+ // command-coverage-ignore: automation run execution — cancels in-flight runs
206
+ // on unsubscribe; background lifecycle bookkeeping.
207
+ const em = this.deps.emFactory();
208
+ const runs = await em.find(NewsletterAutomationRun, { subscriberId, status: 'active' });
209
+ for (const run of runs) {
210
+ run.status = 'cancelled';
211
+ run.nextStepAt = null;
212
+ run.nextStepJobId = null;
213
+ }
214
+ if (runs.length > 0)
215
+ await em.flush();
216
+ }
217
+ async claimStep(em, runId, stepIndex, subscriberId) {
218
+ const id = randomUUID();
219
+ const rows = (await em.getConnection().execute(`insert into newsletter_send_records (id, automation_run_id, step_index, subscriber_id, status, click_count, created_at)
220
+ values (?, ?, ?, ?, 'queued', 0, now())
221
+ on conflict (automation_run_id, step_index, subscriber_id) where automation_run_id is not null
222
+ do nothing
223
+ returning id`, [id, runId, stepIndex, subscriberId], 'all', em.getTransactionContext()));
224
+ return rows.length > 0 ? (rows[0]?.id ?? null) : null;
225
+ }
226
+ async sendStep(em, automation, step,
227
+ // command-coverage-ignore: automation run execution — dispatches a step
228
+ // email + stamps the send record; delivery bookkeeping.
229
+ subscriberId, recordId) {
230
+ const record = await em.findOneOrFail(NewsletterSendRecord, { id: recordId });
231
+ if (record.status === 'sent')
232
+ return;
233
+ const subscriber = await em.findOneOrFail(NewsletterSubscriber, { id: subscriberId });
234
+ const provider = await this.deps.resolveProvider();
235
+ const sender = await this.deps.resolveSender();
236
+ const unsubscribeUrl = this.deps.links.unsubscribe(this.deps.optIn.mintUnsubscribeToken(subscriberId));
237
+ const branded = await withEmailBranding({
238
+ subscriber: { email: subscriber.email },
239
+ customFields: subscriber.customFields,
240
+ channel: { id: automation.salesChannelId },
241
+ }, automation.salesChannelId, this.deps.resolveEmailBranding);
242
+ const rendered = this.deps.content.render({
243
+ subject: step.subject,
244
+ content: step.content,
245
+ ...(branded.accentColor !== undefined ? { accentColor: branded.accentColor } : {}),
246
+ context: {
247
+ variables: branded.variables,
248
+ unsubscribeUrl,
249
+ },
250
+ });
251
+ try {
252
+ const res = await provider.send({
253
+ to: subscriber.email,
254
+ fromEmail: sender.fromEmail || 'noreply@localhost',
255
+ ...(sender.fromName ? { fromName: sender.fromName } : {}),
256
+ subject: rendered.subject,
257
+ html: rendered.html,
258
+ text: rendered.text,
259
+ messageId: record.id,
260
+ headers: { 'List-Unsubscribe': `<${unsubscribeUrl}>` },
261
+ });
262
+ record.status = 'sent';
263
+ record.sentAt = new Date();
264
+ record.providerMessageId = res.providerMessageId ?? null;
265
+ }
266
+ catch (err) {
267
+ record.status = 'failed';
268
+ record.error = err instanceof Error ? err.message : String(err);
269
+ }
270
+ await em.persistAndFlush(record);
271
+ }
272
+ async load(em, id) {
273
+ const a = await em.findOne(NewsletterAutomation, { id });
274
+ if (!a)
275
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Automation not found.');
276
+ return a;
277
+ }
278
+ toSummary(a) {
279
+ return {
280
+ id: a.id,
281
+ name: a.name,
282
+ status: a.status,
283
+ triggerType: a.triggerType,
284
+ stepCount: a.steps.length,
285
+ createdAt: a.createdAt.toISOString(),
286
+ };
287
+ }
288
+ toDetail(a) {
289
+ return {
290
+ ...this.toSummary(a),
291
+ triggerTagIds: a.triggerTagIds,
292
+ salesChannelId: a.salesChannelId,
293
+ language: a.language,
294
+ reentryPolicy: a.reentryPolicy,
295
+ steps: a.steps,
296
+ version: a.version,
297
+ updatedAt: a.updatedAt.toISOString(),
298
+ };
299
+ }
300
+ }
301
+ //# sourceMappingURL=automation.service.js.map