@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,79 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { NewsletterTag } from '../entities/newsletter-tag.entity.js';
4
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
5
+ /** Newsletter tag CRUD (feature 048, US3). */
6
+ export class NewsletterTagService {
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_tag',
18
+ objectId,
19
+ stateBefore: null,
20
+ stateAfter,
21
+ });
22
+ }
23
+ }
24
+ async list() {
25
+ const em = this.emFactory();
26
+ const rows = await em.find(NewsletterTag, {}, { orderBy: { code: 'asc' } });
27
+ return rows.map((t) => this.toDto(t));
28
+ }
29
+ async create(input) {
30
+ const em = this.emFactory();
31
+ const existing = await em.findOne(NewsletterTag, { code: input.code });
32
+ if (existing)
33
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'Tag code already exists.');
34
+ const tag = em.create(NewsletterTag, {
35
+ code: input.code,
36
+ name: input.name,
37
+ description: input.description ?? null,
38
+ });
39
+ em.persist(tag);
40
+ this.#audit(em, 'newsletter_tag.create', tag.id, { code: tag.code });
41
+ await em.flush();
42
+ return this.toDto(tag);
43
+ }
44
+ async update(id, input) {
45
+ const em = this.emFactory();
46
+ const tag = await em.findOne(NewsletterTag, { id });
47
+ if (!tag)
48
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Tag not found.');
49
+ if (input.name !== undefined)
50
+ tag.name = input.name;
51
+ if (input.description !== undefined)
52
+ tag.description = input.description;
53
+ this.#audit(em, 'newsletter_tag.update', tag.id, { name: tag.name });
54
+ await em.persistAndFlush(tag);
55
+ return this.toDto(tag);
56
+ }
57
+ async remove(id) {
58
+ const em = this.emFactory();
59
+ const tag = await em.findOne(NewsletterTag, { id });
60
+ if (!tag)
61
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Tag not found.');
62
+ // Guard: block delete if referenced by a campaign target or automation trigger.
63
+ const refs = (await em.getConnection().execute(`select 1 from newsletter_campaigns where target_tag_ids @> ?::jsonb
64
+ union all
65
+ select 1 from newsletter_automations where trigger_tag_ids @> ?::jsonb limit 1`, [JSON.stringify([id]), JSON.stringify([id])], 'all', em.getTransactionContext()));
66
+ if (refs.length > 0) {
67
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'Tag is used by a campaign or automation.', {
68
+ code: 'tag_in_use',
69
+ });
70
+ }
71
+ // Subscriber-tag bridge rows cascade via FK.
72
+ this.#audit(em, 'newsletter_tag.delete', tag.id, null);
73
+ await em.removeAndFlush(tag);
74
+ }
75
+ toDto(t) {
76
+ return { id: t.id, code: t.code, name: t.name, description: t.description };
77
+ }
78
+ }
79
+ //# sourceMappingURL=tag.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.service.js","sourceRoot":"","sources":["../../../src/backend/services/tag.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Signed, TTL-bounded tokens for the newsletter public surface (feature 048,
3
+ * research R5/R10). One helper mints/verifies all four token purposes so the
4
+ * confirm/unsubscribe/open/click links share a single HMAC secret and format.
5
+ *
6
+ * Token format: `base64url(payloadJson).base64url(hmacSha256(payloadJson))`.
7
+ * The payload always carries `{ p: purpose, exp: epochSeconds, ...claims }`.
8
+ */
9
+ export type NewsletterTokenPurpose = 'confirm' | 'unsubscribe' | 'open' | 'click';
10
+ export interface NewsletterTokenClaims {
11
+ /** Subscriber id (confirm/unsubscribe) or send-record id (open/click). */
12
+ id: string;
13
+ /** Optional link id for click tracking. */
14
+ linkId?: string;
15
+ }
16
+ export declare class NewsletterTokenHelper {
17
+ private readonly secret;
18
+ constructor(secret: string);
19
+ private sign;
20
+ /**
21
+ * Mint a token. `ttlSeconds` bounds validity; `nowSeconds` is injectable for
22
+ * deterministic tests (defaults to the current time).
23
+ */
24
+ mint(purpose: NewsletterTokenPurpose, claims: NewsletterTokenClaims, ttlSeconds: number, nowSeconds?: number): string;
25
+ /**
26
+ * Verify a token against the expected purpose. Returns the claims on success,
27
+ * or `null` when the signature is invalid, the purpose mismatches, the token
28
+ * is malformed, or it has expired.
29
+ */
30
+ verify(token: string, purpose: NewsletterTokenPurpose, nowSeconds?: number): NewsletterTokenClaims | null;
31
+ }
32
+ //# sourceMappingURL=token.helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.helper.d.ts","sourceRoot":"","sources":["../../../src/backend/services/token.helper.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAElF,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAgBD,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAM3C,OAAO,CAAC,IAAI;IAIZ;;;OAGG;IACH,IAAI,CACF,OAAO,EAAE,sBAAsB,EAC/B,MAAM,EAAE,qBAAqB,EAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,GAAE,MAAsC,GACjD,MAAM;IAYT;;;;OAIG;IACH,MAAM,CACJ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,sBAAsB,EAC/B,UAAU,GAAE,MAAsC,GACjD,qBAAqB,GAAG,IAAI;CAgChC"}
@@ -0,0 +1,76 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ function b64urlEncode(buf) {
3
+ return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
4
+ }
5
+ function b64urlDecode(s) {
6
+ const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));
7
+ return Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/') + pad, 'base64');
8
+ }
9
+ export class NewsletterTokenHelper {
10
+ secret;
11
+ constructor(secret) {
12
+ this.secret = secret;
13
+ if (!secret) {
14
+ throw new Error('[newsletter] token secret must be a non-empty string');
15
+ }
16
+ }
17
+ sign(payloadJson) {
18
+ return b64urlEncode(createHmac('sha256', this.secret).update(payloadJson).digest());
19
+ }
20
+ /**
21
+ * Mint a token. `ttlSeconds` bounds validity; `nowSeconds` is injectable for
22
+ * deterministic tests (defaults to the current time).
23
+ */
24
+ mint(purpose, claims, ttlSeconds, nowSeconds = Math.floor(Date.now() / 1000)) {
25
+ const payload = {
26
+ p: purpose,
27
+ exp: nowSeconds + ttlSeconds,
28
+ id: claims.id,
29
+ ...(claims.linkId !== undefined ? { linkId: claims.linkId } : {}),
30
+ };
31
+ const payloadJson = JSON.stringify(payload);
32
+ const body = b64urlEncode(Buffer.from(payloadJson, 'utf8'));
33
+ return `${body}.${this.sign(payloadJson)}`;
34
+ }
35
+ /**
36
+ * Verify a token against the expected purpose. Returns the claims on success,
37
+ * or `null` when the signature is invalid, the purpose mismatches, the token
38
+ * is malformed, or it has expired.
39
+ */
40
+ verify(token, purpose, nowSeconds = Math.floor(Date.now() / 1000)) {
41
+ const dot = token.indexOf('.');
42
+ if (dot <= 0)
43
+ return null;
44
+ const body = token.slice(0, dot);
45
+ const sig = token.slice(dot + 1);
46
+ let payloadJson;
47
+ try {
48
+ payloadJson = b64urlDecode(body).toString('utf8');
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ const expectedSig = this.sign(payloadJson);
54
+ const a = Buffer.from(sig);
55
+ const b = Buffer.from(expectedSig);
56
+ if (a.length !== b.length || !timingSafeEqual(a, b))
57
+ return null;
58
+ let payload;
59
+ try {
60
+ payload = JSON.parse(payloadJson);
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ if (payload.p !== purpose)
66
+ return null;
67
+ if (typeof payload.exp !== 'number' || payload.exp < nowSeconds)
68
+ return null;
69
+ if (typeof payload.id !== 'string' || payload.id.length === 0)
70
+ return null;
71
+ return payload.linkId !== undefined
72
+ ? { id: payload.id, linkId: payload.linkId }
73
+ : { id: payload.id };
74
+ }
75
+ }
76
+ //# sourceMappingURL=token.helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.helper.js","sourceRoot":"","sources":["../../../src/backend/services/token.helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ /**
3
+ * Engagement tracking (feature 048, US6). Records open + click events against a
4
+ * send record and maintains the denormalized counters the stats service reads.
5
+ * Token minting/verification lives in NewsletterTokenHelper; the routes verify
6
+ * the signed token then call these methods with the resolved send-record id.
7
+ */
8
+ export declare class NewsletterTrackingService {
9
+ private readonly emFactory;
10
+ constructor(emFactory: () => EntityManager);
11
+ recordOpen(sendRecordId: string): Promise<void>;
12
+ recordClick(sendRecordId: string, linkId: string | null, url: string | null): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=tracking.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracking.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/tracking.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D;;;;;GAKG;AACH,qBAAa,yBAAyB;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/C,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBlG"}
@@ -0,0 +1,45 @@
1
+ import { NewsletterSendRecord } from '../entities/newsletter-send-record.entity.js';
2
+ import { NewsletterEngagementEvent } from '../entities/newsletter-engagement-event.entity.js';
3
+ /**
4
+ * Engagement tracking (feature 048, US6). Records open + click events against a
5
+ * send record and maintains the denormalized counters the stats service reads.
6
+ * Token minting/verification lives in NewsletterTokenHelper; the routes verify
7
+ * the signed token then call these methods with the resolved send-record id.
8
+ */
9
+ export class NewsletterTrackingService {
10
+ emFactory;
11
+ constructor(emFactory) {
12
+ this.emFactory = emFactory;
13
+ }
14
+ async recordOpen(sendRecordId) {
15
+ // command-coverage-ignore: engagement telemetry — records an email open,
16
+ // high-volume delivery tracking, not an audited domain-state mutation.
17
+ const em = this.emFactory();
18
+ const record = await em.findOne(NewsletterSendRecord, { id: sendRecordId });
19
+ if (!record)
20
+ return;
21
+ em.create(NewsletterEngagementEvent, { sendRecordId, type: 'open' });
22
+ if (record.openedAt === null)
23
+ record.openedAt = new Date();
24
+ await em.flush();
25
+ }
26
+ async recordClick(sendRecordId, linkId, url) {
27
+ // command-coverage-ignore: engagement telemetry — records a link click,
28
+ // high-volume delivery tracking, not an audited domain-state mutation.
29
+ const em = this.emFactory();
30
+ const record = await em.findOne(NewsletterSendRecord, { id: sendRecordId });
31
+ if (!record)
32
+ return;
33
+ em.create(NewsletterEngagementEvent, {
34
+ sendRecordId,
35
+ type: 'click',
36
+ linkId,
37
+ url,
38
+ });
39
+ record.clickCount += 1;
40
+ if (record.openedAt === null)
41
+ record.openedAt = new Date();
42
+ await em.flush();
43
+ }
44
+ }
45
+ //# sourceMappingURL=tracking.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracking.service.js","sourceRoot":"","sources":["../../../src/backend/services/tracking.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AACpF,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAE9F;;;;;GAKG;AACH,MAAM,OAAO,yBAAyB;IACP;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,UAAU,CAAC,YAAoB;QACnC,yEAAyE;QACzE,uEAAuE;QACvE,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,EAAE,CAAC,MAAM,CAAC,yBAAyB,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACrE,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;YAAE,MAAM,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC;QAC3D,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,YAAoB,EAAE,MAAqB,EAAE,GAAkB;QAC/E,wEAAwE;QACxE,uEAAuE;QACvE,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,EAAE,CAAC,MAAM,CAAC,yBAAyB,EAAE;YACnC,YAAY;YACZ,IAAI,EAAE,OAAO;YACb,MAAM;YACN,GAAG;SACJ,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;QACvB,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;YAAE,MAAM,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC;QAC3D,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Newsletter module (feature 048). Owns the subscriber list, tags, custom
3
+ * fields, campaigns, linear automations, reusable email-safe blocks, the
4
+ * bulk-sending provider (SMTP / Amazon SES via nodemailer), and engagement
5
+ * tracking. Bulk delivery is independent of the transactional-email transport.
6
+ * The SMTP connection + credentials come from a reusable `email_adapter`
7
+ * credential configuration referenced by `newsletter.email_credentials`
8
+ * (feature 058); when unset, dispatch uses the console (dev) sink.
9
+ */
10
+ export declare const newsletterSettingsManifest: {
11
+ moduleCode: string;
12
+ groups: {
13
+ code: string;
14
+ name: string;
15
+ salesChannelCodes?: string[] | undefined;
16
+ isSystemProtected?: boolean | undefined;
17
+ }[];
18
+ settings: {
19
+ code: string;
20
+ name: string;
21
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
22
+ defaultValue: unknown;
23
+ description?: string | undefined;
24
+ groupCode?: string | undefined;
25
+ previousDefaultValues?: unknown[] | undefined;
26
+ salesChannelCodes?: string[] | undefined;
27
+ enumOptions?: string[] | undefined;
28
+ configurationType?: string | undefined;
29
+ hidden?: boolean | undefined;
30
+ }[];
31
+ };
32
+ export declare const manifest: {
33
+ id: string;
34
+ name: string;
35
+ version: string;
36
+ dependencies: string[];
37
+ description?: string | undefined;
38
+ acknowledgedDependencies?: {
39
+ moduleId: string;
40
+ port: string;
41
+ reason: string;
42
+ }[] | undefined;
43
+ nonBindingDependencies?: {
44
+ moduleId: string;
45
+ name: string;
46
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
47
+ reason: string;
48
+ whenAbsent?: string | undefined;
49
+ }[] | undefined;
50
+ activation?: {
51
+ settingCode: string;
52
+ default: boolean;
53
+ } | {
54
+ nonDeactivatable: true;
55
+ reason: string;
56
+ } | undefined;
57
+ settings?: {
58
+ moduleCode: string;
59
+ groups: {
60
+ code: string;
61
+ name: string;
62
+ salesChannelCodes?: string[] | undefined;
63
+ isSystemProtected?: boolean | undefined;
64
+ }[];
65
+ settings: {
66
+ code: string;
67
+ name: string;
68
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
69
+ defaultValue: unknown;
70
+ description?: string | undefined;
71
+ groupCode?: string | undefined;
72
+ previousDefaultValues?: unknown[] | undefined;
73
+ salesChannelCodes?: string[] | undefined;
74
+ enumOptions?: string[] | undefined;
75
+ configurationType?: string | undefined;
76
+ hidden?: boolean | undefined;
77
+ }[];
78
+ } | undefined;
79
+ i18n?: {
80
+ bundlesDir: string;
81
+ } | undefined;
82
+ docs?: false | {
83
+ dir: string;
84
+ } | undefined;
85
+ demo?: false | {
86
+ summary: string;
87
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
88
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
89
+ after?: readonly string[] | undefined;
90
+ package?: string | undefined;
91
+ } | undefined;
92
+ actions?: {
93
+ id: string;
94
+ labelKey: string;
95
+ icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
96
+ targetRoute: string;
97
+ keywords: string[];
98
+ weight: number;
99
+ descriptionKey?: string | undefined;
100
+ requiredPermission?: string | undefined;
101
+ }[] | undefined;
102
+ permissions?: {
103
+ code: string;
104
+ label: string;
105
+ module?: string | undefined;
106
+ description?: string | undefined;
107
+ requires?: string[] | undefined;
108
+ }[] | undefined;
109
+ transactionalEmails?: {
110
+ code: string;
111
+ name: string;
112
+ variables: {
113
+ key: string;
114
+ label: string;
115
+ sampleValue?: string | undefined;
116
+ description?: string | undefined;
117
+ }[];
118
+ description?: string | undefined;
119
+ group?: string | undefined;
120
+ }[] | undefined;
121
+ capabilities?: string[] | undefined;
122
+ exclusiveCapabilities?: {
123
+ key: string;
124
+ errorCode: string;
125
+ }[] | undefined;
126
+ errorCodes?: {
127
+ code: string;
128
+ tokens?: string[] | undefined;
129
+ }[] | undefined;
130
+ blocks?: {
131
+ name: string;
132
+ labelKey: string;
133
+ category: string;
134
+ contexts: ("newsletter" | "cms" | "email" | "invoice")[];
135
+ fields: Record<string, {
136
+ type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
137
+ label?: string | undefined;
138
+ required?: boolean | undefined;
139
+ options?: {
140
+ label: string;
141
+ value: string | number;
142
+ }[] | undefined;
143
+ refKind?: string | undefined;
144
+ }>;
145
+ descriptionKey?: string | undefined;
146
+ defaultProps?: Record<string, unknown> | undefined;
147
+ responsiveFields?: string[] | undefined;
148
+ previewIcon?: string | undefined;
149
+ weight?: number | undefined;
150
+ }[] | undefined;
151
+ blockCategories?: {
152
+ key: string;
153
+ titleKey: string;
154
+ contexts: ("newsletter" | "cms" | "email" | "invoice")[];
155
+ weight?: number | undefined;
156
+ visible?: boolean | undefined;
157
+ }[] | undefined;
158
+ env?: {
159
+ name: string;
160
+ describes: {
161
+ en: string;
162
+ pl: string;
163
+ };
164
+ requirement: {
165
+ kind: "required";
166
+ } | {
167
+ kind: "requiredWhen";
168
+ input: string;
169
+ equals: string;
170
+ } | {
171
+ kind: "optional";
172
+ without: {
173
+ en: string;
174
+ pl: string;
175
+ };
176
+ };
177
+ secret: boolean;
178
+ generable: boolean;
179
+ owner: {
180
+ kind: "platform";
181
+ } | {
182
+ kind: "application";
183
+ application: "admin" | "backend" | "storefront";
184
+ } | {
185
+ kind: "module";
186
+ moduleId: string;
187
+ };
188
+ consumers: ("admin" | "backend" | "storefront")[];
189
+ addressOf: "admin" | "backend" | "storefront" | null;
190
+ }[] | undefined;
191
+ };
192
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;CAqErC,CAAC;AAEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmInB,CAAC"}