Răsfoiți Sursa

feat(email-plugin): Support dynamic globalTemplateVars (#2950)

Closes #2933
Marc Schipperheyn 1 an în urmă
părinte
comite
cab67b6850

+ 36 - 1
packages/email-plugin/src/plugin.spec.ts

@@ -6,6 +6,7 @@ import {
     DefaultLogger,
     EventBus,
     Injector,
+    JobQueueService,
     LanguageCode,
     Logger,
     LogLevel,
@@ -239,6 +240,37 @@ describe('EmailPlugin', () => {
             expect(onSend.mock.calls[0][0].subject).toBe('Hello baz');
         });
 
+        it('loads globalTemplateVars async', async () => {
+            const handler = new EmailEventListener('test')
+                .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
+                .setRecipient(() => 'test@test.com')
+                .setSubject('Job {{ name }}, {{ primaryColor }}');
+
+            await initPluginWithHandlers([handler], {
+                globalTemplateVars: async (_ctxLocal: RequestContext, injector: Injector) => {
+                    const jobQueueService = injector.get(JobQueueService);
+                    const jobQueue = await jobQueueService.createQueue({
+                        name: 'hello-service',
+                        // eslint-disable-next-line
+                        process: async job => {
+                            return 'hello';
+                        },
+                    });
+                    const name = jobQueue.name;
+
+                    return {
+                        name,
+                        primaryColor: 'blue',
+                    };
+                },
+            });
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(onSend.mock.calls[0][0].subject).toBe(`Job hello-service, blue`);
+        });
+
         it('interpolates from', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
@@ -922,7 +954,10 @@ class FakeCustomSender implements EmailSender {
 const pause = () => new Promise(resolve => setTimeout(resolve, 100));
 
 class MockEvent extends VendureEvent {
-    constructor(public ctx: RequestContext, public shouldSend: boolean) {
+    constructor(
+        public ctx: RequestContext,
+        public shouldSend: boolean,
+    ) {
         super();
     }
 }

+ 37 - 1
packages/email-plugin/src/plugin.ts

@@ -124,6 +124,38 @@ import {
  * </mj-table>
  * ```
  *
+ * ### Setting global variables using `globalTemplateVars`
+ *
+ * `globalTemplateVars` is an object that can be passed to the configuration of the Email Plugin with static object variables.
+ * You can also pass an async function that will be called with the `RequestContext` and the `Injector` so you can access services
+ * and e.g. load channel specific theme configurations.
+ *
+ * @example
+ * ```ts
+ * EmailPlugin.init({
+ *    globalTemplateVars: {
+ *      primaryColor: '#FF0000',
+ *      fromAddress: 'no-reply@ourstore.com'
+ *    }
+ * })
+ * ```
+ * or
+ * ```ts
+ * EmailPlugin.init({
+ *    globalTemplateVars: async (ctx, injector) => {
+ *      const myAsyncService = injector.get(MyAsyncService);
+ *      const asyncValue = await myAsyncService.get(ctx);
+ *      const channel = ctx.channel;
+ *      const { primaryColor } = channel.customFields.theme;
+ *      const theme = {
+ *         primaryColor,
+ *         asyncValue,
+ *      };
+ *      return theme;
+ *    }
+ * })
+ * ```
+ *
  * ### Handlebars helpers
  *
  * The following helper functions are available for use in email templates:
@@ -378,9 +410,13 @@ export class EmailPlugin implements OnApplicationBootstrap, OnApplicationShutdow
         const { type } = handler;
         try {
             const injector = new Injector(this.moduleRef);
+            let globalTemplateVars = this.options.globalTemplateVars;
+            if (typeof globalTemplateVars === 'function') {
+                globalTemplateVars = await globalTemplateVars(event.ctx, injector);
+            }
             const result = await handler.handle(
                 event as any,
-                EmailPlugin.options.globalTemplateVars,
+                globalTemplateVars as { [key: string]: any },
                 injector,
             );
             if (!result) {

+ 43 - 5
packages/email-plugin/src/types.ts

@@ -4,9 +4,9 @@ import { Injector, RequestContext, SerializedRequestContext, VendureEvent } from
 import { Attachment } from 'nodemailer/lib/mailer';
 import SESTransport from 'nodemailer/lib/ses-transport';
 import SMTPTransport from 'nodemailer/lib/smtp-transport';
-import { EmailEventHandler } from './handler/event-handler';
 
 import { EmailGenerator } from './generator/email-generator';
+import { EmailEventHandler } from './handler/event-handler';
 import { EmailSender } from './sender/email-sender';
 import { TemplateLoader } from './template-loader/template-loader';
 
@@ -31,6 +31,41 @@ export type EventWithContext = VendureEvent & { ctx: RequestContext };
  */
 export type EventWithAsyncData<Event extends EventWithContext, R> = Event & { data: R };
 
+/**
+ * @description
+ * Allows you to dynamically load the "globalTemplateVars" key async and access Vendure services
+ * to create the object. This is not a requirement. You can also specify a simple static object if your
+ * projects doesn't need to access async or dynamic values.
+ *
+ * @example
+ * ```ts
+ *
+ * EmailPlugin.init({
+ *    globalTemplateVars: async (ctx, injector) => {
+ *          const myAsyncService = injector.get(MyAsyncService);
+ *          const asyncValue = await myAsyncService.get(ctx);
+ *          const channel = ctx.channel;
+ *          const { primaryColor } = channel.customFields.theme;
+ *          const theme = {
+ *              primaryColor,
+ *              asyncValue,
+ *          };
+ *          return theme;
+ *      }
+ *   [...]
+ * })
+ *
+ * ```
+ *
+ * @docsCategory core plugins/EmailPlugin
+ * @docsPage EmailPluginOptions
+ * @docsWeight 0
+ */
+export type GlobalTemplateVarsFn = (
+    ctx: RequestContext,
+    injector: Injector,
+) => Promise<{ [key: string]: any }>;
+
 /**
  * @description
  * Configuration for the EmailPlugin.
@@ -75,9 +110,10 @@ export interface EmailPluginOptions {
      * @description
      * An object containing variables which are made available to all templates. For example,
      * the storefront URL could be defined here and then used in the "email address verification"
-     * email.
+     * email. Use the GlobalTemplateVarsFn if you need to retrieve variables from Vendure or
+     * plugin services.
      */
-    globalTemplateVars?: { [key: string]: any };
+    globalTemplateVars?: { [key: string]: any } | GlobalTemplateVarsFn;
     /**
      * @description
      * An optional allowed EmailSender, used to allow custom implementations of the send functionality
@@ -97,9 +133,11 @@ export interface EmailPluginOptions {
 }
 
 /**
- * EmailPLuginOptions type after initialization, where templateLoader is no longer optional
+ * EmailPLuginOptions type after initialization, where templateLoader and themeInjector are no longer optional
  */
-export type InitializedEmailPluginOptions = EmailPluginOptions & { templateLoader: TemplateLoader };
+export type InitializedEmailPluginOptions = EmailPluginOptions & {
+    templateLoader: TemplateLoader;
+};
 
 /**
  * @description