ffffng/server/services/mailTemplateService.ts

121 lines
3.5 KiB
TypeScript

import _ from "lodash";
import deepExtend from "deep-extend";
import { readFileSync, promises as fs } from "graceful-fs";
import moment from "moment";
import { htmlToText } from "nodemailer-html-to-text";
import { config } from "../config";
import Logger from "../logger";
import { editNodeUrl } from "../utils/urlBuilder";
import type { Transporter } from "nodemailer";
import type { MailData, Mail } from "../types";
const templateBasePath = __dirname + "/../mailTemplates";
const snippetsBasePath = templateBasePath + "/snippets";
const templateFunctions: {
[key: string]:
| ((name: string, data: MailData) => string)
| ((data: MailData) => string)
| ((href: string, text: string) => string)
| ((unix: number) => string);
} = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function renderSnippet(this: any, name: string, data: MailData): string {
const snippetFile = snippetsBasePath + "/" + name + ".html";
return _.template(readFileSync(snippetFile).toString())(
deepExtend(
{},
this, // parent data
data,
templateFunctions
)
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function snippet(name: string): (this: any, data: MailData) => string {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function (this: any, data: MailData): string {
return renderSnippet.bind(this)(name, data);
};
}
function renderLink(href: string, text: string): string {
// noinspection HtmlUnknownTarget
return _.template(
'<a href="<%- href %>#" style="color: #E5287A;"><%- text %></a>'
)({
href: href,
text: text || href,
});
}
function renderHR(): string {
return '<hr style="border-top: 1px solid #333333; border-left: 0; border-right: 0; border-bottom: 0;" />';
}
function formatDateTime(unix: number): string {
return moment.unix(unix).locale("de").local().format("DD.MM.YYYY HH:mm");
}
function formatFromNow(unix: number): string {
return moment.unix(unix).locale("de").fromNow();
}
templateFunctions.header = snippet("header");
templateFunctions.footer = snippet("footer");
templateFunctions.monitoringFooter = snippet("monitoring-footer");
templateFunctions.snippet = renderSnippet;
templateFunctions.link = renderLink;
templateFunctions.hr = renderHR;
templateFunctions.formatDateTime = formatDateTime;
templateFunctions.formatFromNow = formatFromNow;
export function configureTransporter(transporter: Transporter): void {
transporter.use(
"compile",
htmlToText({
tables: [".table"],
})
);
}
export async function render(
mailOptions: Mail
): Promise<{ subject: string; body: string }> {
const templatePathPrefix = templateBasePath + "/" + mailOptions.email;
const subject = await fs.readFile(templatePathPrefix + ".subject.txt");
const body = await fs.readFile(templatePathPrefix + ".body.html");
const data = deepExtend(
{},
mailOptions.data,
{
community: config.client.community,
editNodeUrl: editNodeUrl(),
},
templateFunctions
);
try {
return {
subject: _.template(subject.toString())(data).trim(),
body: _.template(body.toString())(data),
};
} catch (error) {
Logger.tag("mail", "template").error(
"Error rendering template for mail[" + mailOptions.id + "]:",
error
);
throw error;
}
}