@endora-commerce/mod-transactional-emails 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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/admin/api/transactional-emails-client.d.ts +43 -0
  4. package/dist/admin/api/transactional-emails-client.d.ts.map +1 -0
  5. package/dist/admin/api/transactional-emails-client.js +47 -0
  6. package/dist/admin/api/transactional-emails-client.js.map +1 -0
  7. package/dist/admin/components/BrandingPanel.d.ts +10 -0
  8. package/dist/admin/components/BrandingPanel.d.ts.map +1 -0
  9. package/dist/admin/components/BrandingPanel.js +58 -0
  10. package/dist/admin/components/BrandingPanel.js.map +1 -0
  11. package/dist/admin/components/EmailEditorPane.d.ts +7 -0
  12. package/dist/admin/components/EmailEditorPane.d.ts.map +1 -0
  13. package/dist/admin/components/EmailEditorPane.js +48 -0
  14. package/dist/admin/components/EmailEditorPane.js.map +1 -0
  15. package/dist/admin/index.d.ts +46 -0
  16. package/dist/admin/index.d.ts.map +1 -0
  17. package/dist/admin/index.js +92 -0
  18. package/dist/admin/index.js.map +1 -0
  19. package/dist/admin/pages/EmailBlockEditorPage.d.ts +16 -0
  20. package/dist/admin/pages/EmailBlockEditorPage.d.ts.map +1 -0
  21. package/dist/admin/pages/EmailBlockEditorPage.js +19 -0
  22. package/dist/admin/pages/EmailBlockEditorPage.js.map +1 -0
  23. package/dist/admin/pages/EmailBlocksPage.d.ts +8 -0
  24. package/dist/admin/pages/EmailBlocksPage.d.ts.map +1 -0
  25. package/dist/admin/pages/EmailBlocksPage.js +55 -0
  26. package/dist/admin/pages/EmailBlocksPage.js.map +1 -0
  27. package/dist/admin/pages/EmailEditor.d.ts +8 -0
  28. package/dist/admin/pages/EmailEditor.d.ts.map +1 -0
  29. package/dist/admin/pages/EmailEditor.js +126 -0
  30. package/dist/admin/pages/EmailEditor.js.map +1 -0
  31. package/dist/admin/pages/EmailFragmentEditor.d.ts +9 -0
  32. package/dist/admin/pages/EmailFragmentEditor.d.ts.map +1 -0
  33. package/dist/admin/pages/EmailFragmentEditor.js +93 -0
  34. package/dist/admin/pages/EmailFragmentEditor.js.map +1 -0
  35. package/dist/admin/pages/EmailTemplateEditorPage.d.ts +12 -0
  36. package/dist/admin/pages/EmailTemplateEditorPage.d.ts.map +1 -0
  37. package/dist/admin/pages/EmailTemplateEditorPage.js +15 -0
  38. package/dist/admin/pages/EmailTemplateEditorPage.js.map +1 -0
  39. package/dist/admin/pages/EmailTemplatesPage.d.ts +8 -0
  40. package/dist/admin/pages/EmailTemplatesPage.d.ts.map +1 -0
  41. package/dist/admin/pages/EmailTemplatesPage.js +55 -0
  42. package/dist/admin/pages/EmailTemplatesPage.js.map +1 -0
  43. package/dist/admin/pages/EmailsList.d.ts +8 -0
  44. package/dist/admin/pages/EmailsList.d.ts.map +1 -0
  45. package/dist/admin/pages/EmailsList.js +85 -0
  46. package/dist/admin/pages/EmailsList.js.map +1 -0
  47. package/dist/backend/commands/email-activation.commands.d.ts +42 -0
  48. package/dist/backend/commands/email-activation.commands.d.ts.map +1 -0
  49. package/dist/backend/commands/email-activation.commands.js +51 -0
  50. package/dist/backend/commands/email-activation.commands.js.map +1 -0
  51. package/dist/backend/entities/email-block-sales-channel.entity.d.ts +11 -0
  52. package/dist/backend/entities/email-block-sales-channel.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/email-block-sales-channel.entity.js +39 -0
  54. package/dist/backend/entities/email-block-sales-channel.entity.js.map +1 -0
  55. package/dist/backend/entities/email-block.entity.d.ts +24 -0
  56. package/dist/backend/entities/email-block.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/email-block.entity.js +85 -0
  58. package/dist/backend/entities/email-block.entity.js.map +1 -0
  59. package/dist/backend/entities/email-template-sales-channel.entity.d.ts +10 -0
  60. package/dist/backend/entities/email-template-sales-channel.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/email-template-sales-channel.entity.js +38 -0
  62. package/dist/backend/entities/email-template-sales-channel.entity.js.map +1 -0
  63. package/dist/backend/entities/email-template.entity.d.ts +19 -0
  64. package/dist/backend/entities/email-template.entity.d.ts.map +1 -0
  65. package/dist/backend/entities/email-template.entity.js +76 -0
  66. package/dist/backend/entities/email-template.entity.js.map +1 -0
  67. package/dist/backend/entities/transactional-email-content.entity.d.ts +25 -0
  68. package/dist/backend/entities/transactional-email-content.entity.d.ts.map +1 -0
  69. package/dist/backend/entities/transactional-email-content.entity.js +77 -0
  70. package/dist/backend/entities/transactional-email-content.entity.js.map +1 -0
  71. package/dist/backend/entities/transactional-email.entity.d.ts +32 -0
  72. package/dist/backend/entities/transactional-email.entity.d.ts.map +1 -0
  73. package/dist/backend/entities/transactional-email.entity.js +100 -0
  74. package/dist/backend/entities/transactional-email.entity.js.map +1 -0
  75. package/dist/backend/index.d.ts +105 -0
  76. package/dist/backend/index.d.ts.map +1 -0
  77. package/dist/backend/index.js +126 -0
  78. package/dist/backend/index.js.map +1 -0
  79. package/dist/backend/plugin.d.ts +49 -0
  80. package/dist/backend/plugin.d.ts.map +1 -0
  81. package/dist/backend/plugin.js +54 -0
  82. package/dist/backend/plugin.js.map +1 -0
  83. package/dist/backend/routes.admin.d.ts +23 -0
  84. package/dist/backend/routes.admin.d.ts.map +1 -0
  85. package/dist/backend/routes.admin.js +133 -0
  86. package/dist/backend/routes.admin.js.map +1 -0
  87. package/dist/backend/services/branding.service.d.ts +63 -0
  88. package/dist/backend/services/branding.service.d.ts.map +1 -0
  89. package/dist/backend/services/branding.service.js +119 -0
  90. package/dist/backend/services/branding.service.js.map +1 -0
  91. package/dist/backend/services/content-resolver.d.ts +28 -0
  92. package/dist/backend/services/content-resolver.d.ts.map +1 -0
  93. package/dist/backend/services/content-resolver.js +64 -0
  94. package/dist/backend/services/content-resolver.js.map +1 -0
  95. package/dist/backend/services/email-block.service.d.ts +25 -0
  96. package/dist/backend/services/email-block.service.d.ts.map +1 -0
  97. package/dist/backend/services/email-block.service.js +162 -0
  98. package/dist/backend/services/email-block.service.js.map +1 -0
  99. package/dist/backend/services/email-builder-registry.d.ts +9 -0
  100. package/dist/backend/services/email-builder-registry.d.ts.map +1 -0
  101. package/dist/backend/services/email-builder-registry.js +14 -0
  102. package/dist/backend/services/email-builder-registry.js.map +1 -0
  103. package/dist/backend/services/email-defaults-registry.d.ts +63 -0
  104. package/dist/backend/services/email-defaults-registry.d.ts.map +1 -0
  105. package/dist/backend/services/email-defaults-registry.js +60 -0
  106. package/dist/backend/services/email-defaults-registry.js.map +1 -0
  107. package/dist/backend/services/email-template.service.d.ts +23 -0
  108. package/dist/backend/services/email-template.service.d.ts.map +1 -0
  109. package/dist/backend/services/email-template.service.js +155 -0
  110. package/dist/backend/services/email-template.service.js.map +1 -0
  111. package/dist/backend/services/embed-resolver.d.ts +21 -0
  112. package/dist/backend/services/embed-resolver.d.ts.map +1 -0
  113. package/dist/backend/services/embed-resolver.js +68 -0
  114. package/dist/backend/services/embed-resolver.js.map +1 -0
  115. package/dist/backend/services/manifest-reconciler.d.ts +28 -0
  116. package/dist/backend/services/manifest-reconciler.d.ts.map +1 -0
  117. package/dist/backend/services/manifest-reconciler.js +98 -0
  118. package/dist/backend/services/manifest-reconciler.js.map +1 -0
  119. package/dist/backend/services/template-email.d.ts +26 -0
  120. package/dist/backend/services/template-email.d.ts.map +1 -0
  121. package/dist/backend/services/template-email.js +69 -0
  122. package/dist/backend/services/template-email.js.map +1 -0
  123. package/dist/backend/services/transactional-email.service.d.ts +138 -0
  124. package/dist/backend/services/transactional-email.service.d.ts.map +1 -0
  125. package/dist/backend/services/transactional-email.service.js +410 -0
  126. package/dist/backend/services/transactional-email.service.js.map +1 -0
  127. package/dist/manifest.d.ts +195 -0
  128. package/dist/manifest.d.ts.map +1 -0
  129. package/dist/manifest.js +367 -0
  130. package/dist/manifest.js.map +1 -0
  131. package/dist/migrations/20260629T113442_transactional_emails_init.d.ts +6 -0
  132. package/dist/migrations/20260629T113442_transactional_emails_init.d.ts.map +1 -0
  133. package/dist/migrations/20260629T113442_transactional_emails_init.js +133 -0
  134. package/dist/migrations/20260629T113442_transactional_emails_init.js.map +1 -0
  135. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.d.ts +6 -0
  136. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.d.ts.map +1 -0
  137. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.js +49 -0
  138. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.js.map +1 -0
  139. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.d.ts +6 -0
  140. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.d.ts.map +1 -0
  141. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.js +60 -0
  142. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.js.map +1 -0
  143. package/dist/migrations/index.d.ts +29 -0
  144. package/dist/migrations/index.d.ts.map +1 -0
  145. package/dist/migrations/index.js +33 -0
  146. package/dist/migrations/index.js.map +1 -0
  147. package/docs/transactional-emails.md +176 -0
  148. package/i18n/en.json +92 -0
  149. package/i18n/pl.json +92 -0
  150. package/package.json +113 -0
  151. package/tailwind.css +14 -0
@@ -0,0 +1,105 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { FastifyRequest } from 'fastify';
3
+ import type { ModuleManifest, TransactionalEmailSender } from '@endora-commerce/contracts';
4
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
5
+ import type { CommandBus } from '@endora-commerce/platform/commands';
6
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
7
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
8
+ import type { SalesChannelResolutionPort } from '@endora-commerce/platform/kernel';
9
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
10
+ import { transactionalEmailsModule, type TransactionalEmailsModuleOptions } from './plugin.js';
11
+ import type { BrandingService, AssetUrlResolver } from './services/branding.service.js';
12
+ import { type TemplateEmail } from './services/template-email.js';
13
+ import { EmailDefaultsRegistry } from './services/email-defaults-registry.js';
14
+ import { EmailBlockSalesChannel } from './entities/email-block-sales-channel.entity.js';
15
+ import { EmailBlock } from './entities/email-block.entity.js';
16
+ import { EmailTemplateSalesChannel } from './entities/email-template-sales-channel.entity.js';
17
+ import { EmailTemplate } from './entities/email-template.entity.js';
18
+ import { TransactionalEmailContent } from './entities/transactional-email-content.entity.js';
19
+ import { TransactionalEmail } from './entities/transactional-email.entity.js';
20
+ /**
21
+ * `transactional_emails` — the last of the 65 (feature 072, T120).
22
+ *
23
+ * It sat in wave 2 and was skipped, which is why `composition.ts` still called
24
+ * a module factory after every other module had stopped needing one. Both roots
25
+ * held `let transactionalEmailSender` and `let emailBrandingService`, filled in
26
+ * by this module's two `expose…` callbacks, and **six** consumers read the
27
+ * sender back out of that variable.
28
+ *
29
+ * Both sinks become accessor ports, the seam `orders` got in T141: the services
30
+ * are constructed inside the plugin body, so they do not exist until route
31
+ * registration, and an accessor resolved before then answers `null` honestly.
32
+ *
33
+ * **`templateEmailPort` is the substantive part.** `inventory` and
34
+ * `organizations` both send template-routed mail against the system-default
35
+ * channel, and both got there through `makeOrgTemplateEmail` — a 59-line
36
+ * adapter that lived in `organizations` and that each root imported and passed
37
+ * down to `inventory`. Every line of it is about the transactional sender and
38
+ * the channel an unscoped email resolves against, so it moved here (see
39
+ * `services/template-email.ts`) and both modules resolve one port. That removes
40
+ * a converted module importing another converted module's helper, which is what
41
+ * the root indirection had been hiding.
42
+ *
43
+ * The harness passed neither `resolveAssetUrl` nor `exposeBranding`, so email
44
+ * asset URLs and branding resolution ran in no test. Both are wired for both
45
+ * compositions now.
46
+ */
47
+ /** What `transactional_emails` resolves from the container, and the names it owns. */
48
+ export interface TransactionalEmailsCradle {
49
+ readonly emFactory: () => EntityManager;
50
+ readonly auditLogService: AuditPort;
51
+ readonly commandBus: CommandBus;
52
+ readonly settingsReadPort: SettingsReadPort;
53
+ readonly salesChannelResolutionPort: SalesChannelResolutionPort;
54
+ readonly requireAdmin: RequireAdminFactory;
55
+ readonly adminContextResolver: (req: FastifyRequest) => {
56
+ adminUserId: string | null;
57
+ };
58
+ readonly resolvedModuleRegistry: ReadonlyArray<{
59
+ manifest: ModuleManifest;
60
+ }>;
61
+ readonly emailMailer: NonNullable<TransactionalEmailsModuleOptions['mailer']>;
62
+ readonly emailDeliveryRecorder: NonNullable<TransactionalEmailsModuleOptions['deliveryRecorder']>;
63
+ readonly settingsAdminService: NonNullable<TransactionalEmailsModuleOptions['settingsAdmin']>;
64
+ /**
65
+ * Contributed: how an asset id becomes a public URL in an email. It reaches
66
+ * `assets_library`, which this module must not read through directly.
67
+ */
68
+ readonly transactionalEmailAssetUrl: AssetUrlResolver;
69
+ readonly transactionalEmailSenderAccessor: () => TransactionalEmailSender | undefined;
70
+ readonly emailBrandingAccessor: () => BrandingService | undefined;
71
+ readonly templateEmailPort: TemplateEmail;
72
+ /**
73
+ * Where a module declaring a transactional email puts its default subject and
74
+ * content (T143a).
75
+ *
76
+ * The registry is read once, by the boot reconciler in this module's plugin
77
+ * body, so a contributor pushes from `ctx.onBoot` — boot hooks run during
78
+ * composition and plugin bodies only when the Fastify app is built, so the
79
+ * ordering holds by construction rather than by luck. This is the "push at
80
+ * boot" shape: a module contributing a *descriptor* to a registry the host
81
+ * enumerates, as opposed to the pull shape, where a dependent resolves an
82
+ * answer.
83
+ *
84
+ * **Ungated** — `ctx.di.register`, not `ctx.di.providePort` (D-39). See the
85
+ * registration below for why the distinction is the difference between a
86
+ * degraded feature and a dead deployment.
87
+ */
88
+ readonly emailDefaultsPort: EmailDefaultsRegistry;
89
+ readonly emailDefaultsRegistryInstance: EmailDefaultsRegistry;
90
+ readonly transactionalEmails: ReturnType<typeof transactionalEmailsModule>;
91
+ }
92
+ export declare function registerModule(ctx: ModuleContext): void;
93
+ /**
94
+ * The module's persisted entity classes, on the `./backend` subpath, as one
95
+ * array and **no named class export** (D-168).
96
+ *
97
+ * This is the shape the platform reads when the package is *installed*: the
98
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
99
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
100
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
101
+ * the package pass of `check-entity-tenant-classification`. A missing array is
102
+ * answered with `[]` — zero entities registered, no error anywhere.
103
+ */
104
+ export declare const entities: (typeof TransactionalEmail | typeof TransactionalEmailContent | typeof EmailBlock | typeof EmailTemplate | typeof EmailBlockSalesChannel | typeof EmailTemplateSalesChannel)[];
105
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAqB,cAAc,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAC9G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,KAAK,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAExF,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,kDAAkD,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sFAAsF;AACtF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,0BAA0B,EAAE,0BAA0B,CAAC;IAChE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACvF,QAAQ,CAAC,sBAAsB,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;IAC7E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,gCAAgC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,qBAAqB,EAAE,WAAW,CAAC,gCAAgC,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAClG,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,gCAAgC,CAAC,eAAe,CAAC,CAAC,CAAC;IAC9F;;;OAGG;IACH,QAAQ,CAAC,0BAA0B,EAAE,gBAAgB,CAAC;IACtD,QAAQ,CAAC,gCAAgC,EAAE,MAAM,wBAAwB,GAAG,SAAS,CAAC;IACtF,QAAQ,CAAC,qBAAqB,EAAE,MAAM,eAAe,GAAG,SAAS,CAAC;IAClE,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC;IAC1C;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC;IAClD,QAAQ,CAAC,6BAA6B,EAAE,qBAAqB,CAAC;IAC9D,QAAQ,CAAC,mBAAmB,EAAE,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC;CAC5E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAwIvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,gLAOpB,CAAC"}
@@ -0,0 +1,126 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { transactionalEmailsModule } from './plugin.js';
3
+ import { makeTemplateEmail } from './services/template-email.js';
4
+ import { EmailDefaultsRegistry } from './services/email-defaults-registry.js';
5
+ import { EmailBlockSalesChannel } from './entities/email-block-sales-channel.entity.js';
6
+ import { EmailBlock } from './entities/email-block.entity.js';
7
+ import { EmailTemplateSalesChannel } from './entities/email-template-sales-channel.entity.js';
8
+ import { EmailTemplate } from './entities/email-template.entity.js';
9
+ import { TransactionalEmailContent } from './entities/transactional-email-content.entity.js';
10
+ import { TransactionalEmail } from './entities/transactional-email.entity.js';
11
+ export function registerModule(ctx) {
12
+ const cradle = () => ctx.cradle();
13
+ /** Filled in at route registration by the module's two `expose…` callbacks. */
14
+ const exposed = { sender: null, branding: null };
15
+ ctx.di.register({
16
+ // One registry per composition. It used to be a module-level singleton
17
+ // shared by every composition in the process.
18
+ emailDefaultsRegistryInstance: ctx
19
+ .asFunction(() => new EmailDefaultsRegistry())
20
+ .singleton(),
21
+ // Contribution point: a composition that cannot resolve asset URLs sends
22
+ // emails without them rather than failing to send.
23
+ transactionalEmailAssetUrl: ctx
24
+ .asFunction(() => async () => null)
25
+ .singleton(),
26
+ transactionalEmails: ctx
27
+ .asFunction(({ emFactory, auditLogService, commandBus, resolvedModuleRegistry }) => transactionalEmailsModule({
28
+ emFactory,
29
+ auditLog: auditLogService,
30
+ commandBus,
31
+ manifests: resolvedModuleRegistry.map((entry) => entry.manifest),
32
+ defaultsRegistry: cradle().emailDefaultsRegistryInstance,
33
+ settingsService: lazyPort(ctx, 'settingsReadPort'),
34
+ settingsAdmin: lazyPort(ctx, 'settingsAdminService'),
35
+ mailer: lazyPort(ctx, 'emailMailer'),
36
+ // D-59 — the three outcomes this module decides before the
37
+ // transport is reached leave no row unless it writes one, and one
38
+ // of them is the operator's own "off".
39
+ deliveryRecorder: lazyPort(ctx, 'emailDeliveryRecorder'),
40
+ resolveAssetUrl: (assetId) => cradle().transactionalEmailAssetUrl(assetId),
41
+ requireAdmin: (permission) => async (req, reply) => cradle().requireAdmin(permission ?? '')(req, reply),
42
+ resolveAdminUserId: (req) => cradle().adminContextResolver(req).adminUserId,
43
+ exposeSender: (sender) => {
44
+ exposed.sender = sender;
45
+ },
46
+ exposeBranding: (branding) => {
47
+ exposed.branding = branding;
48
+ },
49
+ }))
50
+ .singleton(),
51
+ });
52
+ ctx.di.providePort('transactionalEmailSenderAccessor', ctx
53
+ .asFunction(() => () => exposed.sender ?? undefined)
54
+ .singleton());
55
+ ctx.di.providePort('emailBrandingAccessor', ctx
56
+ .asFunction(() => () => exposed.branding ?? undefined)
57
+ .singleton());
58
+ // One template-email adapter for every module that sends unscoped,
59
+ // template-routed mail. `organizations` and `inventory` each used to get their
60
+ // own, built by a root from a helper the first of them owned.
61
+ // Feature 075, Phase P — the type parameter is the compile-time proof that
62
+ // this adapter still satisfies `TemplateEmailPort`, the shape `organizations`
63
+ // reaches. It is still a gated port and must stay one: `trySend` renders and
64
+ // sends, which is what the gate is for.
65
+ ctx.di.providePort('templateEmailPort', ctx
66
+ .asFunction(() => makeTemplateEmail({
67
+ getSender: () => exposed.sender ?? undefined,
68
+ // D-48 — the system-default channel, which always exists. It was
69
+ // `?? null`, which read template-email branding and language
70
+ // platform-wide on a branch that cannot be taken.
71
+ resolveScopeSalesChannelId: async () => (await cradle().salesChannelResolutionPort.getSystemDefault()).id,
72
+ resolveLanguage: async (salesChannelId) => (await cradle().salesChannelResolutionPort.getById(salesChannelId))?.defaultLanguage ??
73
+ 'en-US',
74
+ }))
75
+ .singleton());
76
+ /**
77
+ * The contribution seam, deliberately **ungated** (feature 072, D-39).
78
+ *
79
+ * Seven modules push their email defaults into this registry from
80
+ * `ctx.onBoot`, and `runBootHooks()` does not consult module presence. A
81
+ * `providePort` here is a transient gate that throws `MODULE_DISABLED` on
82
+ * resolution, so every one of those seven hooks would have thrown during
83
+ * composition the moment an operator switched `transactional_emails` off —
84
+ * `index.ts` turns that into `process.exit(1)`, and with the API down the
85
+ * `/platform/modules` screen the operator would undo their own change from is
86
+ * unreachable. "Off is non-destructive and reversible" (Constitution XVII) is
87
+ * not satisfied by a platform that will not start.
88
+ *
89
+ * Nothing leaks by leaving it ungated: an entry is inert data — a subject and
90
+ * a content envelope — and every behavioural seam in this module is still a
91
+ * port. `templateEmailPort` sends, `transactionalEmailSenderAccessor` hands
92
+ * out the sender, and both fail closed.
93
+ *
94
+ * Whether an entry is *honoured* while its contributor is absent is the host's
95
+ * question, answered at enumeration and keyed on the owner the registry
96
+ * records — see `services/email-defaults-registry.ts`.
97
+ */
98
+ ctx.di.register({
99
+ emailDefaultsPort: ctx
100
+ .asFunction(({ emailDefaultsRegistryInstance }) => emailDefaultsRegistryInstance)
101
+ .singleton(),
102
+ });
103
+ ctx.routes(async (app) => {
104
+ await cradle().transactionalEmails(app);
105
+ });
106
+ }
107
+ /**
108
+ * The module's persisted entity classes, on the `./backend` subpath, as one
109
+ * array and **no named class export** (D-168).
110
+ *
111
+ * This is the shape the platform reads when the package is *installed*: the
112
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
113
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
114
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
115
+ * the package pass of `check-entity-tenant-classification`. A missing array is
116
+ * answered with `[]` — zero entities registered, no error anywhere.
117
+ */
118
+ export const entities = [
119
+ EmailBlockSalesChannel,
120
+ EmailBlock,
121
+ EmailTemplateSalesChannel,
122
+ EmailTemplate,
123
+ TransactionalEmailContent,
124
+ TransactionalEmail,
125
+ ];
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { ModuleManifest } from '@endora-commerce/contracts';
4
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
5
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
6
+ import type { CommandBus } from '@endora-commerce/platform/commands';
7
+ import type { EmailDeliveryRecorder, EmailMailerPort, SettingsAdminPort } from '@endora-commerce/contracts';
8
+ import { BrandingService, type AssetUrlResolver } from './services/branding.service.js';
9
+ import { TransactionalEmailService } from './services/transactional-email.service.js';
10
+ import type { EmailDefaultsRegistry } from './services/email-defaults-registry.js';
11
+ export interface TransactionalEmailsModuleOptions {
12
+ emFactory: () => EntityManager;
13
+ settingsService: SettingsReadPort;
14
+ requireAdmin: (permission?: string) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
15
+ resolveAdminUserId: (req: FastifyRequest) => string | null;
16
+ /** All registered module manifests — drives boot reconciliation of definitions. */
17
+ manifests: ReadonlyArray<ModuleManifest>;
18
+ /**
19
+ * The registry the owning modules pushed their defaults into (T143a).
20
+ *
21
+ * Supplied rather than imported, so it is **one per composition**. The
22
+ * module-level singleton this replaced was shared by every composition in the
23
+ * process.
24
+ */
25
+ defaultsRegistry: EmailDefaultsRegistry;
26
+ /** Audits the per-email activation flip (issue #89, Principle XIII). */
27
+ commandBus: CommandBus;
28
+ mailer?: EmailMailerPort;
29
+ /**
30
+ * Where a message this module suppresses — or cannot render — is recorded
31
+ * (D-59). The transport records the ones it is handed; these never reach it.
32
+ */
33
+ deliveryRecorder?: EmailDeliveryRecorder;
34
+ auditLog?: AuditPort;
35
+ resolveAssetUrl?: AssetUrlResolver;
36
+ /** Settings admin service used to persist branding values (US2). */
37
+ settingsAdmin?: SettingsAdminPort;
38
+ /** Exposes the sender back to composition so owning modules can send. */
39
+ exposeSender?: (sender: TransactionalEmailService) => void;
40
+ /** Exposes branding so newsletter (and others) can inject logoUrl/accent. */
41
+ exposeBranding?: (branding: BrandingService) => void;
42
+ }
43
+ /**
44
+ * Composition root for the transactional_emails module (feature 047). Builds the
45
+ * resolver/branding/embed/render services, reconciles email definitions from
46
+ * manifests at boot, registers the admin routes, and exposes the sender port.
47
+ */
48
+ export declare function transactionalEmailsModule(options: TransactionalEmailsModuleOptions): (app: FastifyInstance) => Promise<void>;
49
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EACV,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAExF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAItF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAGnF,MAAM,WAAW,gCAAgC;IAC/C,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,eAAe,EAAE,gBAAgB,CAAC;IAClC,YAAY,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnG,kBAAkB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,MAAM,GAAG,IAAI,CAAC;IAC3D,mFAAmF;IACnF,SAAS,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACzC;;;;;;OAMG;IACH,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,wEAAwE;IACxE,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,oEAAoE;IACpE,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,IAAI,CAAC;IAC3D,6EAA6E;IAC7E,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAkDzC"}
@@ -0,0 +1,54 @@
1
+ import { ContentResolver } from './services/content-resolver.js';
2
+ import { BrandingService } from './services/branding.service.js';
3
+ import { EmbedResolver } from './services/embed-resolver.js';
4
+ import { TransactionalEmailService } from './services/transactional-email.service.js';
5
+ import { EmailBlockService } from './services/email-block.service.js';
6
+ import { EmailTemplateService } from './services/email-template.service.js';
7
+ import { TransactionalEmailReconciler } from './services/manifest-reconciler.js';
8
+ import { registerTransactionalEmailsAdminRoutes } from './routes.admin.js';
9
+ /**
10
+ * Composition root for the transactional_emails module (feature 047). Builds the
11
+ * resolver/branding/embed/render services, reconciles email definitions from
12
+ * manifests at boot, registers the admin routes, and exposes the sender port.
13
+ */
14
+ export function transactionalEmailsModule(options) {
15
+ return async (app) => {
16
+ const contentResolver = new ContentResolver();
17
+ const branding = new BrandingService(options.settingsService, options.resolveAssetUrl, options.settingsAdmin ? { admin: options.settingsAdmin, emFactory: options.emFactory } : undefined);
18
+ const embeds = new EmbedResolver();
19
+ const service = new TransactionalEmailService({
20
+ emFactory: options.emFactory,
21
+ contentResolver,
22
+ branding,
23
+ embeds,
24
+ defaults: options.defaultsRegistry,
25
+ ...(options.mailer ? { mailer: options.mailer } : {}),
26
+ ...(options.deliveryRecorder ? { deliveryRecorder: options.deliveryRecorder } : {}),
27
+ ...(options.auditLog ? { auditLog: options.auditLog } : {}),
28
+ });
29
+ const blocks = new EmailBlockService(options.emFactory, options.auditLog);
30
+ const templates = new EmailTemplateService(options.emFactory, options.auditLog);
31
+ // Boot reconciliation: upsert definitions from manifests + registered defaults.
32
+ const reconciler = new TransactionalEmailReconciler(options.emFactory, options.defaultsRegistry);
33
+ try {
34
+ await reconciler.reconcile(options.manifests);
35
+ }
36
+ catch (err) {
37
+ app.log.error({ err }, '[transactional_emails] manifest reconciliation failed');
38
+ throw err;
39
+ }
40
+ options.exposeSender?.(service);
41
+ options.exposeBranding?.(branding);
42
+ await registerTransactionalEmailsAdminRoutes(app, {
43
+ service,
44
+ branding,
45
+ blocks,
46
+ templates,
47
+ requireAdmin: options.requireAdmin,
48
+ resolveAdminUserId: options.resolveAdminUserId,
49
+ commandBus: options.commandBus,
50
+ defaults: options.defaultsRegistry,
51
+ });
52
+ };
53
+ }
54
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,eAAe,EAAyB,MAAM,gCAAgC,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAEjF,OAAO,EAAE,sCAAsC,EAAE,MAAM,mBAAmB,CAAC;AAmC3E;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAAyC;IAEzC,OAAO,KAAK,EAAE,GAAoB,EAAiB,EAAE;QACnD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,eAAe,CAClC,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CACnG,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAEnC,MAAM,OAAO,GAAG,IAAI,yBAAyB,CAAC;YAC5C,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,eAAe;YACf,QAAQ;YACR,MAAM;YACN,QAAQ,EAAE,OAAO,CAAC,gBAAgB;YAClC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnF,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1E,MAAM,SAAS,GAAG,IAAI,oBAAoB,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEhF,gFAAgF;QAChF,MAAM,UAAU,GAAG,IAAI,4BAA4B,CACjD,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,gBAAgB,CACzB,CAAC;QACF,IAAI,CAAC;YACH,MAAM,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,uDAAuD,CAAC,CAAC;YAChF,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,OAAO,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,CAAC;QAEnC,MAAM,sCAAsC,CAAC,GAAG,EAAE;YAChD,OAAO;YACP,QAAQ;YACR,MAAM;YACN,SAAS;YACT,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,QAAQ,EAAE,OAAO,CAAC,gBAAgB;SACnC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
2
+ import type { CommandBus } from '@endora-commerce/platform/commands';
3
+ import type { EmailDefaultsRegistry } from './services/email-defaults-registry.js';
4
+ import type { TransactionalEmailService } from './services/transactional-email.service.js';
5
+ import type { BrandingService } from './services/branding.service.js';
6
+ import type { EmailBlockService } from './services/email-block.service.js';
7
+ import type { EmailTemplateService } from './services/email-template.service.js';
8
+ type RequireAdmin = (permission?: string) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
9
+ export interface TransactionalEmailsAdminRoutesDeps {
10
+ service: TransactionalEmailService;
11
+ branding: BrandingService;
12
+ blocks: EmailBlockService;
13
+ templates: EmailTemplateService;
14
+ requireAdmin: RequireAdmin;
15
+ resolveAdminUserId: (req: FastifyRequest) => string | null;
16
+ /** The per-email activation flip (issue #89) — Principle XIII. */
17
+ commandBus: CommandBus;
18
+ /** Where the owning modules declared which emails may not be switched off. */
19
+ defaults: EmailDefaultsRegistry;
20
+ }
21
+ export declare function registerTransactionalEmailsAdminRoutes(app: FastifyInstance, deps: TransactionalEmailsAdminRoutesDeps): Promise<void>;
22
+ export {};
23
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAgB7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAErE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAEnF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,KAAK,YAAY,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzG,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,yBAAyB,CAAC;IACnC,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,oBAAoB,CAAC;IAChC,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,MAAM,GAAG,IAAI,CAAC;IAC3D,kEAAkE;IAClE,UAAU,EAAE,UAAU,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAKD,wBAAsB,sCAAsC,CAC1D,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,kCAAkC,GACvC,OAAO,CAAC,IAAI,CAAC,CAoJf"}
@@ -0,0 +1,133 @@
1
+ import { createEmailBlockRequestSchema, createEmailTemplateRequestSchema, emailScopeQuerySchema, patchEmailBlockRequestSchema, patchEmailTemplateRequestSchema, previewEmailRequestSchema, putEmailBrandingRequestSchema, putEmailBlockContentRequestSchema, putEmailContentQuerySchema, putEmailContentRequestSchema, setTransactionalEmailActiveRequestSchema, transactionalEmailDetailQuerySchema, } from '@endora-commerce/contracts';
2
+ import { makeSetTransactionalEmailActiveCommand } from './commands/email-activation.commands.js';
3
+ import { describeEmailBuilder } from './services/email-builder-registry.js';
4
+ const READ = 'transactional_emails:read';
5
+ const WRITE = 'transactional_emails:write';
6
+ export async function registerTransactionalEmailsAdminRoutes(app, deps) {
7
+ const { service, branding, blocks, templates, requireAdmin, resolveAdminUserId, commandBus, defaults } = deps;
8
+ const base = '/api/v1/admin/transactional-emails';
9
+ // --- Page-builder descriptor (email-safe palette) ----------------------
10
+ app.get(`${base}/page-builder/config`, { preHandler: requireAdmin(READ) }, async () => ({
11
+ data: describeEmailBuilder(),
12
+ }));
13
+ // --- Reusable blocks ----------------------------------------------------
14
+ app.get(`${base}/blocks`, { preHandler: requireAdmin(READ) }, async (request) => {
15
+ const q = emailScopeQuerySchema.parse(request.query);
16
+ return { data: { items: await blocks.list(q.salesChannelId) } };
17
+ });
18
+ app.post(`${base}/blocks`, { preHandler: requireAdmin(WRITE) }, async (request) => {
19
+ const body = createEmailBlockRequestSchema.parse(request.body);
20
+ return { data: await blocks.create(body) };
21
+ });
22
+ app.get(`${base}/blocks/:id`, { preHandler: requireAdmin(READ) }, async (request) => {
23
+ const { id } = request.params;
24
+ return { data: await blocks.get(id) };
25
+ });
26
+ app.patch(`${base}/blocks/:id`, { preHandler: requireAdmin(WRITE) }, async (request) => {
27
+ const { id } = request.params;
28
+ const body = patchEmailBlockRequestSchema.parse(request.body);
29
+ return { data: await blocks.patch(id, body) };
30
+ });
31
+ app.put(`${base}/blocks/:id/content/:language`, { preHandler: requireAdmin(WRITE) }, async (request) => {
32
+ const { id, language } = request.params;
33
+ const body = putEmailBlockContentRequestSchema.parse(request.body);
34
+ return { data: await blocks.setContent(id, language, body) };
35
+ });
36
+ app.delete(`${base}/blocks/:id`, { preHandler: requireAdmin(WRITE) }, async (request, reply) => {
37
+ const { id } = request.params;
38
+ await blocks.delete(id);
39
+ return reply.code(204).send();
40
+ });
41
+ // --- Reusable templates -------------------------------------------------
42
+ app.get(`${base}/templates`, { preHandler: requireAdmin(READ) }, async (request) => {
43
+ const q = emailScopeQuerySchema.parse(request.query);
44
+ return { data: { items: await templates.list(q.salesChannelId) } };
45
+ });
46
+ app.post(`${base}/templates`, { preHandler: requireAdmin(WRITE) }, async (request) => {
47
+ const body = createEmailTemplateRequestSchema.parse(request.body);
48
+ return { data: await templates.create(body) };
49
+ });
50
+ app.get(`${base}/templates/:id`, { preHandler: requireAdmin(READ) }, async (request) => {
51
+ const { id } = request.params;
52
+ return { data: await templates.get(id) };
53
+ });
54
+ app.patch(`${base}/templates/:id`, { preHandler: requireAdmin(WRITE) }, async (request) => {
55
+ const { id } = request.params;
56
+ const body = patchEmailTemplateRequestSchema.parse(request.body);
57
+ return { data: await templates.patch(id, body) };
58
+ });
59
+ app.put(`${base}/templates/:id/content/:language`, { preHandler: requireAdmin(WRITE) }, async (request) => {
60
+ const { id, language } = request.params;
61
+ const body = putEmailBlockContentRequestSchema.parse(request.body);
62
+ return { data: await templates.setContent(id, language, body) };
63
+ });
64
+ app.delete(`${base}/templates/:id`, { preHandler: requireAdmin(WRITE) }, async (request, reply) => {
65
+ const { id } = request.params;
66
+ await templates.delete(id);
67
+ return reply.code(204).send();
68
+ });
69
+ // --- Branding (read + write) -------------------------------------------
70
+ app.get(`${base}/branding`, { preHandler: requireAdmin(READ) }, async (request) => {
71
+ const q = emailScopeQuerySchema.parse(request.query);
72
+ return { data: await branding.resolve(q.salesChannelId ?? null) };
73
+ });
74
+ app.put(`${base}/branding`, { preHandler: requireAdmin(WRITE) }, async (request) => {
75
+ const q = emailScopeQuerySchema.parse(request.query);
76
+ const body = putEmailBrandingRequestSchema.parse(request.body);
77
+ const data = await branding.update(q.salesChannelId ?? null, body, {
78
+ actorAdminUserId: resolveAdminUserId(request),
79
+ });
80
+ return { data };
81
+ });
82
+ // --- Email definitions --------------------------------------------------
83
+ app.get(base, { preHandler: requireAdmin(READ) }, async () => ({
84
+ data: { items: await service.list() },
85
+ }));
86
+ app.get(`${base}/:code`, { preHandler: requireAdmin(READ) }, async (request) => {
87
+ const { code } = request.params;
88
+ const q = transactionalEmailDetailQuerySchema.parse(request.query);
89
+ return { data: await service.getDetail(code, q.salesChannelId ?? null, q.language) };
90
+ });
91
+ app.put(`${base}/:code/content`, { preHandler: requireAdmin(WRITE) }, async (request) => {
92
+ const { code } = request.params;
93
+ const q = putEmailContentQuerySchema.parse(request.query);
94
+ const body = putEmailContentRequestSchema.parse(request.body);
95
+ const data = await service.saveContent(code, { salesChannelId: q.salesChannelId ?? null, language: q.language }, { subject: body.subject, content: body.content, ...(body.expectedVersion !== undefined ? { expectedVersion: body.expectedVersion } : {}) }, { adminUserId: resolveAdminUserId(request) });
96
+ return { data };
97
+ });
98
+ app.delete(`${base}/:code/content`, { preHandler: requireAdmin(WRITE) }, async (request) => {
99
+ const { code } = request.params;
100
+ const q = putEmailContentQuerySchema.parse(request.query);
101
+ const data = await service.resetContent(code, { salesChannelId: q.salesChannelId ?? null, language: q.language }, { adminUserId: resolveAdminUserId(request) });
102
+ return { data };
103
+ });
104
+ /**
105
+ * Issue #89 — switch one email on or off.
106
+ *
107
+ * `WRITE` rather than a new permission code: it is the same "manage
108
+ * transactional emails" authority that already lets an operator rewrite the
109
+ * body of this very email, and silencing it is the lesser of the two.
110
+ *
111
+ * The refusal for a protected code happens in the Command factory, before a
112
+ * transaction opens, so a refused flip leaves no audit row — matching the
113
+ * module-level door exactly.
114
+ */
115
+ app.post(`${base}/:code/activation`, { preHandler: requireAdmin(WRITE) }, async (request) => {
116
+ const { code } = request.params;
117
+ const body = setTransactionalEmailActiveRequestSchema.parse(request.body);
118
+ const result = await commandBus.run(makeSetTransactionalEmailActiveCommand({ code, active: body.active }, defaults));
119
+ return { data: await service.summary(result.code) };
120
+ });
121
+ app.post(`${base}/:code/preview`, { preHandler: requireAdmin(READ) }, async (request) => {
122
+ const { code } = request.params;
123
+ const body = previewEmailRequestSchema.parse(request.body);
124
+ const data = await service.preview(code, {
125
+ salesChannelId: body.salesChannelId ?? null,
126
+ ...(body.language !== undefined ? { language: body.language } : {}),
127
+ ...(body.draftSubject !== undefined ? { draftSubject: body.draftSubject } : {}),
128
+ ...(body.draftContent !== undefined ? { draftContent: body.draftContent } : {}),
129
+ });
130
+ return { data };
131
+ });
132
+ }
133
+ //# sourceMappingURL=routes.admin.js.map
@@ -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,63 @@
1
+ /**
2
+ * BrandingService — feature 047 (R5/US2).
3
+ *
4
+ * Resolves email branding (header logo, accent, default header/footer block
5
+ * codes) globally and per sales channel via the Settings module, and exposes the
6
+ * resolved logo URL as the `branding.logoUrl` variable for rendering.
7
+ */
8
+ import type { EntityManager } from '@mikro-orm/postgresql';
9
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
10
+ import type { SettingsAdminAuditContext, SettingsAdminPort } from '@endora-commerce/contracts';
11
+ export interface ResolvedBranding {
12
+ salesChannelId: string | null;
13
+ logoAssetId: string;
14
+ logoUrl: string;
15
+ accentColor: string;
16
+ headerBlockCode: string;
17
+ footerBlockCode: string;
18
+ source: 'channel' | 'global' | 'default';
19
+ }
20
+ /** Resolves an asset id to a servable URL (wired from assets_library). */
21
+ export type AssetUrlResolver = (assetId: string) => Promise<string | null>;
22
+ export interface BrandingWriteDeps {
23
+ /**
24
+ * `settingsAdminService`, owned by `settings` and named by its published
25
+ * contract since feature 075's Phase C. Two writes rather than one with an
26
+ * optional argument, because a value set for all channels and a value set
27
+ * for a named subset are different operations (Constitution XII).
28
+ */
29
+ admin: SettingsAdminPort;
30
+ emFactory: () => EntityManager;
31
+ }
32
+ export interface BrandingPatch {
33
+ logoAssetId?: string | undefined;
34
+ accentColor?: string | undefined;
35
+ headerBlockCode?: string | undefined;
36
+ footerBlockCode?: string | undefined;
37
+ }
38
+ export declare class BrandingService {
39
+ private readonly settings;
40
+ private readonly resolveAssetUrl?;
41
+ private readonly writeDeps?;
42
+ constructor(settings: SettingsReadPort, resolveAssetUrl?: AssetUrlResolver | undefined, writeDeps?: BrandingWriteDeps | undefined);
43
+ /**
44
+ * Two conditions degrade, and both to the caller's compiled-in default:
45
+ * the setting is not registered (the module has not reconciled its manifest
46
+ * yet), and the setting is scoped to a subset of channels that this read is
47
+ * not inside. Everything else propagates — the bare `catch` this replaces
48
+ * would have swallowed a `MODULE_DISABLED` envelope with equal enthusiasm,
49
+ * which is fail-open dressed as robustness (composition rule 7, D-43).
50
+ */
51
+ private readString;
52
+ /**
53
+ * `salesChannelId === null` is the platform-wide read, and it is passed
54
+ * through as `null` (D-41). It used to become the nil UUID: well-formed
55
+ * enough to pass the seam guard, matching no row, so the right tier by
56
+ * accident — and cached under a fake channel key that no invalidation ever
57
+ * targeted, instead of the reserved `__global__` segment.
58
+ */
59
+ resolve(salesChannelId: string | null): Promise<ResolvedBranding>;
60
+ /** Persist branding values at the given scope (global when salesChannelId is null). */
61
+ update(salesChannelId: string | null, patch: BrandingPatch, actor: SettingsAdminAuditContext): Promise<ResolvedBranding>;
62
+ }
63
+ //# sourceMappingURL=branding.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branding.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/branding.service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAI/F,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC1C;AAED,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE3E,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,aAAa,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAcD,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAFV,QAAQ,EAAE,gBAAgB,EAC1B,eAAe,CAAC,EAAE,gBAAgB,YAAA,EAClC,SAAS,CAAC,EAAE,iBAAiB,YAAA;IAGhD;;;;;;;OAOG;YACW,UAAU;IAsBxB;;;;;;OAMG;IACG,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmCvE,uFAAuF;IACjF,MAAM,CACV,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,gBAAgB,CAAC;CA0B7B"}