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( '<%- text %>' )({ href: href, text: text || href, }); } function renderHR(): string { return '
'; } 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; } }