@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.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/admin/api/transactional-emails-client.d.ts +43 -0
- package/dist/admin/api/transactional-emails-client.d.ts.map +1 -0
- package/dist/admin/api/transactional-emails-client.js +47 -0
- package/dist/admin/api/transactional-emails-client.js.map +1 -0
- package/dist/admin/components/BrandingPanel.d.ts +10 -0
- package/dist/admin/components/BrandingPanel.d.ts.map +1 -0
- package/dist/admin/components/BrandingPanel.js +58 -0
- package/dist/admin/components/BrandingPanel.js.map +1 -0
- package/dist/admin/components/EmailEditorPane.d.ts +7 -0
- package/dist/admin/components/EmailEditorPane.d.ts.map +1 -0
- package/dist/admin/components/EmailEditorPane.js +48 -0
- package/dist/admin/components/EmailEditorPane.js.map +1 -0
- package/dist/admin/index.d.ts +46 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +92 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/EmailBlockEditorPage.d.ts +16 -0
- package/dist/admin/pages/EmailBlockEditorPage.d.ts.map +1 -0
- package/dist/admin/pages/EmailBlockEditorPage.js +19 -0
- package/dist/admin/pages/EmailBlockEditorPage.js.map +1 -0
- package/dist/admin/pages/EmailBlocksPage.d.ts +8 -0
- package/dist/admin/pages/EmailBlocksPage.d.ts.map +1 -0
- package/dist/admin/pages/EmailBlocksPage.js +55 -0
- package/dist/admin/pages/EmailBlocksPage.js.map +1 -0
- package/dist/admin/pages/EmailEditor.d.ts +8 -0
- package/dist/admin/pages/EmailEditor.d.ts.map +1 -0
- package/dist/admin/pages/EmailEditor.js +126 -0
- package/dist/admin/pages/EmailEditor.js.map +1 -0
- package/dist/admin/pages/EmailFragmentEditor.d.ts +9 -0
- package/dist/admin/pages/EmailFragmentEditor.d.ts.map +1 -0
- package/dist/admin/pages/EmailFragmentEditor.js +93 -0
- package/dist/admin/pages/EmailFragmentEditor.js.map +1 -0
- package/dist/admin/pages/EmailTemplateEditorPage.d.ts +12 -0
- package/dist/admin/pages/EmailTemplateEditorPage.d.ts.map +1 -0
- package/dist/admin/pages/EmailTemplateEditorPage.js +15 -0
- package/dist/admin/pages/EmailTemplateEditorPage.js.map +1 -0
- package/dist/admin/pages/EmailTemplatesPage.d.ts +8 -0
- package/dist/admin/pages/EmailTemplatesPage.d.ts.map +1 -0
- package/dist/admin/pages/EmailTemplatesPage.js +55 -0
- package/dist/admin/pages/EmailTemplatesPage.js.map +1 -0
- package/dist/admin/pages/EmailsList.d.ts +8 -0
- package/dist/admin/pages/EmailsList.d.ts.map +1 -0
- package/dist/admin/pages/EmailsList.js +85 -0
- package/dist/admin/pages/EmailsList.js.map +1 -0
- package/dist/backend/commands/email-activation.commands.d.ts +42 -0
- package/dist/backend/commands/email-activation.commands.d.ts.map +1 -0
- package/dist/backend/commands/email-activation.commands.js +51 -0
- package/dist/backend/commands/email-activation.commands.js.map +1 -0
- package/dist/backend/entities/email-block-sales-channel.entity.d.ts +11 -0
- package/dist/backend/entities/email-block-sales-channel.entity.d.ts.map +1 -0
- package/dist/backend/entities/email-block-sales-channel.entity.js +39 -0
- package/dist/backend/entities/email-block-sales-channel.entity.js.map +1 -0
- package/dist/backend/entities/email-block.entity.d.ts +24 -0
- package/dist/backend/entities/email-block.entity.d.ts.map +1 -0
- package/dist/backend/entities/email-block.entity.js +85 -0
- package/dist/backend/entities/email-block.entity.js.map +1 -0
- package/dist/backend/entities/email-template-sales-channel.entity.d.ts +10 -0
- package/dist/backend/entities/email-template-sales-channel.entity.d.ts.map +1 -0
- package/dist/backend/entities/email-template-sales-channel.entity.js +38 -0
- package/dist/backend/entities/email-template-sales-channel.entity.js.map +1 -0
- package/dist/backend/entities/email-template.entity.d.ts +19 -0
- package/dist/backend/entities/email-template.entity.d.ts.map +1 -0
- package/dist/backend/entities/email-template.entity.js +76 -0
- package/dist/backend/entities/email-template.entity.js.map +1 -0
- package/dist/backend/entities/transactional-email-content.entity.d.ts +25 -0
- package/dist/backend/entities/transactional-email-content.entity.d.ts.map +1 -0
- package/dist/backend/entities/transactional-email-content.entity.js +77 -0
- package/dist/backend/entities/transactional-email-content.entity.js.map +1 -0
- package/dist/backend/entities/transactional-email.entity.d.ts +32 -0
- package/dist/backend/entities/transactional-email.entity.d.ts.map +1 -0
- package/dist/backend/entities/transactional-email.entity.js +100 -0
- package/dist/backend/entities/transactional-email.entity.js.map +1 -0
- package/dist/backend/index.d.ts +105 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +126 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +49 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +54 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +23 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +133 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/services/branding.service.d.ts +63 -0
- package/dist/backend/services/branding.service.d.ts.map +1 -0
- package/dist/backend/services/branding.service.js +119 -0
- package/dist/backend/services/branding.service.js.map +1 -0
- package/dist/backend/services/content-resolver.d.ts +28 -0
- package/dist/backend/services/content-resolver.d.ts.map +1 -0
- package/dist/backend/services/content-resolver.js +64 -0
- package/dist/backend/services/content-resolver.js.map +1 -0
- package/dist/backend/services/email-block.service.d.ts +25 -0
- package/dist/backend/services/email-block.service.d.ts.map +1 -0
- package/dist/backend/services/email-block.service.js +162 -0
- package/dist/backend/services/email-block.service.js.map +1 -0
- package/dist/backend/services/email-builder-registry.d.ts +9 -0
- package/dist/backend/services/email-builder-registry.d.ts.map +1 -0
- package/dist/backend/services/email-builder-registry.js +14 -0
- package/dist/backend/services/email-builder-registry.js.map +1 -0
- package/dist/backend/services/email-defaults-registry.d.ts +63 -0
- package/dist/backend/services/email-defaults-registry.d.ts.map +1 -0
- package/dist/backend/services/email-defaults-registry.js +60 -0
- package/dist/backend/services/email-defaults-registry.js.map +1 -0
- package/dist/backend/services/email-template.service.d.ts +23 -0
- package/dist/backend/services/email-template.service.d.ts.map +1 -0
- package/dist/backend/services/email-template.service.js +155 -0
- package/dist/backend/services/email-template.service.js.map +1 -0
- package/dist/backend/services/embed-resolver.d.ts +21 -0
- package/dist/backend/services/embed-resolver.d.ts.map +1 -0
- package/dist/backend/services/embed-resolver.js +68 -0
- package/dist/backend/services/embed-resolver.js.map +1 -0
- package/dist/backend/services/manifest-reconciler.d.ts +28 -0
- package/dist/backend/services/manifest-reconciler.d.ts.map +1 -0
- package/dist/backend/services/manifest-reconciler.js +98 -0
- package/dist/backend/services/manifest-reconciler.js.map +1 -0
- package/dist/backend/services/template-email.d.ts +26 -0
- package/dist/backend/services/template-email.d.ts.map +1 -0
- package/dist/backend/services/template-email.js +69 -0
- package/dist/backend/services/template-email.js.map +1 -0
- package/dist/backend/services/transactional-email.service.d.ts +138 -0
- package/dist/backend/services/transactional-email.service.d.ts.map +1 -0
- package/dist/backend/services/transactional-email.service.js +410 -0
- package/dist/backend/services/transactional-email.service.js.map +1 -0
- package/dist/manifest.d.ts +195 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +367 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260629T113442_transactional_emails_init.d.ts +6 -0
- package/dist/migrations/20260629T113442_transactional_emails_init.d.ts.map +1 -0
- package/dist/migrations/20260629T113442_transactional_emails_init.js +133 -0
- package/dist/migrations/20260629T113442_transactional_emails_init.js.map +1 -0
- package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.d.ts +6 -0
- package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.d.ts.map +1 -0
- package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.js +49 -0
- package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.js.map +1 -0
- package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.d.ts +6 -0
- package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.d.ts.map +1 -0
- package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.js +60 -0
- package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.js.map +1 -0
- package/dist/migrations/index.d.ts +29 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +33 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/transactional-emails.md +176 -0
- package/i18n/en.json +92 -0
- package/i18n/pl.json +92 -0
- package/package.json +113 -0
- 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":"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"}
|
|
@@ -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":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAI5D,OAAO,EAAE,yBAAyB,EAAyC,MAAM,aAAa,CAAC;AAG/F,OAAO,EAAE,iBAAiB,EAAsB,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;AAwE9E,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,MAAM,MAAM,GAAG,GAA8B,EAAE,CAAC,GAAG,CAAC,MAAM,EAA6B,CAAC;IAExF,+EAA+E;IAC/E,MAAM,OAAO,GAGT,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAErC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;QACd,uEAAuE;QACvE,8CAA8C;QAC9C,6BAA6B,EAAE,GAAG;aAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,qBAAqB,EAAE,CAAC;aAC7C,SAAS,EAAE;QAEd,yEAAyE;QACzE,mDAAmD;QACnD,0BAA0B,EAAE,GAAG;aAC5B,UAAU,CAAC,GAAqB,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC;aACpD,SAAS,EAAE;QAEd,mBAAmB,EAAE,GAAG;aACrB,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,sBAAsB,EAA6B,EAAE,EAAE,CAChG,yBAAyB,CAAC;YACxB,SAAS;YACT,QAAQ,EAAE,eAAe;YACzB,UAAU;YACV,SAAS,EAAE,sBAAsB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;YAChE,gBAAgB,EAAE,MAAM,EAAE,CAAC,6BAA6B;YACxD,eAAe,EAAE,QAAQ,CAAmB,GAAG,EAAE,kBAAkB,CAAC;YACpE,aAAa,EAAE,QAAQ,CACrB,GAAG,EACH,sBAAsB,CACvB;YACD,MAAM,EAAE,QAAQ,CAA2C,GAAG,EAAE,aAAa,CAAC;YAC9E,2DAA2D;YAC3D,kEAAkE;YAClE,uCAAuC;YACvC,gBAAgB,EAAE,QAAQ,CACxB,GAAG,EACH,uBAAuB,CACxB;YACD,eAAe,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,0BAA0B,CAAC,OAAO,CAAC;YAClF,YAAY,EAAE,CAAC,UAAmB,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,CAC1D,MAAM,EAAE,CAAC,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC;YACrD,kBAAkB,EAAE,CAAC,GAAmB,EAAE,EAAE,CAC1C,MAAM,EAAE,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,WAAW;YAChD,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;gBACvB,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;YAC1B,CAAC;YACD,cAAc,EAAE,CAAC,QAAQ,EAAE,EAAE;gBAC3B,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC9B,CAAC;SACF,CAAC,CACL;aACA,SAAS,EAAE;KACf,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,kCAAkC,EAClC,GAAG;SACA,UAAU,CACT,GAAiD,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,CACtF;SACA,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,uBAAuB,EACvB,GAAG;SACA,UAAU,CAAC,GAAwC,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC;SAC1F,SAAS,EAAE,CACf,CAAC;IAEF,mEAAmE;IACnE,+EAA+E;IAC/E,8DAA8D;IAC9D,2EAA2E;IAC3E,8EAA8E;IAC9E,6EAA6E;IAC7E,wCAAwC;IACxC,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,mBAAmB,EACnB,GAAG;SACA,UAAU,CAAC,GAAkB,EAAE,CAC9B,iBAAiB,CAAC;QAChB,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS;QAC5C,iEAAiE;QACjE,6DAA6D;QAC7D,kDAAkD;QAClD,0BAA0B,EAAE,KAAK,IAAI,EAAE,CACrC,CAAC,MAAM,MAAM,EAAE,CAAC,0BAA0B,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE;QACnE,eAAe,EAAE,KAAK,EAAE,cAAsB,EAAE,EAAE,CAChD,CAAC,MAAM,MAAM,EAAE,CAAC,0BAA0B,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,EAAE,eAAe;YACpF,OAAO;KACV,CAAC,CACH;SACA,SAAS,EAAE,CACf,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;QACd,iBAAiB,EAAE,GAAG;aACnB,UAAU,CACT,CAAC,EAAE,6BAA6B,EAA6B,EAAE,EAAE,CAC/D,6BAA6B,CAChC;aACA,SAAS,EAAE;KACf,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,MAAM,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,sBAAsB;IACtB,UAAU;IACV,yBAAyB;IACzB,aAAa;IACb,yBAAyB;IACzB,kBAAkB;CACnB,CAAC"}
|
|
@@ -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":"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"}
|
|
@@ -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"}
|