diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/email.ts | 101 | ||||
| -rw-r--r-- | src/lib/handlebars.ts | 97 | 
2 files changed, 97 insertions, 101 deletions
diff --git a/src/lib/email.ts b/src/lib/email.ts index e7243aa..57f69f5 100644 --- a/src/lib/email.ts +++ b/src/lib/email.ts @@ -1,13 +1,15 @@  import sgMail from "@sendgrid/mail"; +import sgHelpers from "@sendgrid/helpers"; +  import nodemailer, { Transporter } from "nodemailer";  import { getConfig } from "./config.js";  import SMTPTransport from "nodemailer/lib/smtp-transport/index.js";  import { exitWithError } from "./process.js"; -import { renderTemplate } from "./handlebars.js"; -import { ExpressHandlebars } from "express-handlebars"; +import { HandlebarsSingleton } from "./handlebars.js"; +  const config = getConfig(); -type EmailTemplate = +type EmailTemplateName =      | "addEventAttendee"      | "addEventComment"      | "createEvent" @@ -16,6 +18,7 @@ type EmailTemplate =      | "deleteEvent"      | "editEvent"      | "eventGroupUpdated" +    | "removeEventAttendee"      | "subscribed"      | "unattendEvent"; @@ -89,30 +92,6 @@ export const initEmailService = async (): Promise<boolean> => {      }  }; -export const sendTemplatedEmail = async ( -    hbs: ExpressHandlebars, -    to: string, -    bcc: string, -    subject: string, -    template: string, -    data: object, -): Promise<boolean> => { -    const [html, text] = await Promise.all([ -        hbs.renderView(`./views/emails/${template}Html.handlebars`, { -            cache: true, -            layout: "email.handlebars", -            ...data, -        }), -        hbs.renderView(`./views/emails/${template}Text.handlebars`, { -            cache: true, -            layout: "email.handlebars", -            ...data, -        }), -    ]); - -    return await sendEmail(to, bcc, subject, text, html); -}; -  export const sendEmail = async (      to: string | string[],      bcc: string | string[] | undefined, @@ -132,11 +111,11 @@ export const sendEmail = async (                      html,                  });                  return true; -            } catch (e: Error) { -                if (e.response) { -                    console.error(e.response.body); +            } catch (e: unknown | sgHelpers.classes.ResponseError) { +                if (e instanceof sgHelpers.classes.ResponseError) { +                    console.error('sendgrid error', e.response.body);                  } else { -                    console.error(e); +                    console.error('sendgrid error', e);                  }                  return false;              } @@ -164,15 +143,10 @@ export const sendEmail = async (                          nodemailer.createTransport(nodemailerConfig);                  }                  await nodemailerTransporter.sendMail({ -                    envelope: { -                        from: config.general.email, -                        to, -                        bcc, -                    },                      from: config.general.email,                      to,                      bcc, -                    subject, +                    subject: `${config.general.site_name}: ${subject}`,                      text,                      html,                  }); @@ -187,25 +161,42 @@ export const sendEmail = async (  };  export const sendEmailFromTemplate = async ( -    to: string, -    bcc: string, +    to: string | string[], +    bcc: string | string[] | undefined,      subject: string, -    template: EmailTemplate, -    templateData: Record<string, unknown>, -    req: Request, +    templateName: EmailTemplateName, +    templateData: object,  ): Promise<boolean> => { -    const html = await renderTemplate(req, `${template}/${template}Html`, { -        siteName: config.general.site_name, -        siteLogo: config.general.email_logo_url, -        domain: config.general.domain, -        cache: true, -        layout: "email.handlebars", -        ...templateData, -    }); -    const text = await renderTemplate( -        req, -        `${template}/${template}Text`, -        templateData, -    ); +    const [html, text] = await Promise.all([ +        HandlebarsSingleton.instance.renderView( +            `./views/emails/${templateName}/${templateName}Html.handlebars`, +            { +                domain: config.general.domain, +                contactEmail: config.general.email, +                siteName: config.general.site_name, +                mailService: config.general.mail_service, +                siteLogo: config.general.email_logo_url, +                isFederated: config.general.is_federated || true, +                cache: true, +                layout: "email.handlebars", +                ...templateData, +            } +        ), +        HandlebarsSingleton.instance.renderView( +            `./views/emails/${templateName}/${templateName}Text.handlebars`, +            { +                domain: config.general.domain, +                contactEmail: config.general.email, +                siteName: config.general.site_name, +                mailService: config.general.mail_service, +                siteLogo: config.general.email_logo_url, +                isFederated: config.general.is_federated || true, +                cache: true, +                layout: "email.handlebars", +                ...templateData, +            } +        ), +    ]); +      return await sendEmail(to, bcc, subject, text, html);  }; diff --git a/src/lib/handlebars.ts b/src/lib/handlebars.ts index 42f8010..6d4f796 100644 --- a/src/lib/handlebars.ts +++ b/src/lib/handlebars.ts @@ -1,50 +1,55 @@ -import { Request } from "express"; -import { ExpressHandlebars } from "express-handlebars"; +import hbs, { ExpressHandlebars } from "express-handlebars"; +import { RenderViewOptions } from "express-handlebars/types/index.js"; -export const renderTemplate = async ( -    req: Request, -    templateName: string, -    data: Record<string, unknown>, -): Promise<string> => { -    return new Promise<string>((resolve, reject) => { -        req.app -            .get("hbsInstance") -            .renderView( -                `./views/emails/${templateName}.handlebars`, -                data, -                (err: any, html: string) => { -                    if (err) { -                        console.error(err); -                        reject(err); -                    } -                    resolve(html); +export class HandlebarsSingleton { +    static #instance: HandlebarsSingleton; +    hbsInstance: hbs.ExpressHandlebars; + +    private constructor() {  +        this.hbsInstance = hbs.create({ +            defaultLayout: "main", +            partialsDir: ["views/partials/"], +            layoutsDir: "views/layouts/", +            helpers: { +                plural: function (number: number, text: string) { +                    const singular = number === 1; +                    // If no text parameter was given, just return a conditional s. +                    if (typeof text !== "string") return singular ? "" : "s"; +                    // Split with regex into group1/group2 or group1(group3) +                    const match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); +                    // If no match, just append a conditional s. +                    if (!match) return text + (singular ? "" : "s"); +                    // We have a good match, so fire away +                    return ( +                        (singular && match[1]) || // Singular case +                        match[2] || // Plural case: 'bagel/bagels' --> bagels +                        match[1] + (match[3] || "s") +                    ); // Plural case: 'bagel(s)' or 'bagel' --> bagels +                }, +                json: function (context: object) { +                    return JSON.stringify(context);                  }, -            ); -    }); -}; +            }, +        }); +    } + +    public static get instance(): HandlebarsSingleton { +        if (!HandlebarsSingleton.#instance) { +            HandlebarsSingleton.#instance = new HandlebarsSingleton(); +        } + +        return HandlebarsSingleton.#instance; +    } +     +    public get engine(): ExpressHandlebars["engine"] { +        return this.hbsInstance.engine; +    } -export const renderEmail = async ( -    hbsInstance: ExpressHandlebars, -    templateName: string, -    data: Record<string, unknown>, -): Promise<{ html: string, text: string }> => { -    const [html, text] = await Promise.all([ -        hbsInstance.renderView( -            `./views/emails/${templateName}Html.handlebars`, -            { -                cache: true, -                layout: "email.handlebars", -                ...data, -            } -        ), -        hbsInstance.renderView( -            `./views/emails/${templateName}Text.handlebars`, -            { -                cache: true, -                layout: "email.handlebars", -                ...data, -            } -        ), -    ]); -    return { html, text } +    /** +     * Finally, any singleton can define some business logic, which can be +     * executed on its instance. +     */ +    public renderView(viewPath: string, options: RenderViewOptions): Promise<string> { +        return this.hbsInstance.renderView(viewPath, options); +    }  }  | 
