summaryrefslogtreecommitdiff
path: root/src/lib/handlebars.ts
diff options
context:
space:
mode:
authorGavin Mogan <git@gavinmogan.com>2025-04-23 17:27:55 -0700
committerGavin Mogan <git@gavinmogan.com>2025-04-23 17:43:37 -0700
commita8a17443c2d070d2d23920ffff7e4a43c905698c (patch)
treecddb37a6af4a65f774863e51be941cd291af5683 /src/lib/handlebars.ts
parentaace2c7e6ccb6e74df83faac74c427d43bfaf79b (diff)
Refactor for everywhere to use sendEmailFromTemplate everywhere
* Created a singleton to house handlebars so req doesn't need to be passed everywhere (should make unit testing easier later) * Subjectline for sendgrid and nodemailer are both always prefixed in sendEmail() * removed prefix subjectline from all other email places * added a couple if (!event) { return 404 } to help make typescript happy * some minor eslint auto fixes (looks like let => const where it can)
Diffstat (limited to 'src/lib/handlebars.ts')
-rw-r--r--src/lib/handlebars.ts97
1 files changed, 51 insertions, 46 deletions
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);
+ }
}