Render emails using NunjucksComponent's environment

This commit is contained in:
Alice Gaudon 2020-11-03 10:29:36 +01:00
parent cfb7bddca6
commit 4d0c714dbd
9 changed files with 44 additions and 21 deletions

View File

@ -1,7 +1,7 @@
import nodemailer, {SentMessageInfo, Transporter} from "nodemailer";
import config from "config";
import {Options} from "nodemailer/lib/mailer";
import nunjucks from 'nunjucks';
import {Environment} from 'nunjucks';
import * as util from "util";
import {WrappingError} from "./Utils";
import mjml2html from "mjml";
@ -45,9 +45,14 @@ export default class Mail {
if (this.transporter) this.transporter.close();
}
public static parse(template: string, data: { [p: string]: unknown }, textOnly: boolean): string {
public static parse(
environment: Environment,
template: string,
data: { [p: string]: unknown },
textOnly: boolean,
): string {
data.text = textOnly;
const nunjucksResult = nunjucks.render(template, data);
const nunjucksResult = environment.render(template, data);
if (textOnly) return nunjucksResult;
const mjmlResult = mjml2html(nunjucksResult, {});
@ -59,11 +64,13 @@ export default class Mail {
return mjmlResult.html;
}
private readonly template: MailTemplate;
private readonly options: Options = {};
private readonly data: ParsedUrlQueryInput;
public constructor(template: MailTemplate, data: ParsedUrlQueryInput = {}) {
public constructor(
private readonly environment: Environment,
private readonly template: MailTemplate,
private readonly data: ParsedUrlQueryInput = {},
) {
this.template = template;
this.data = data;
this.options.subject = this.template.getSubject(data);
@ -106,8 +113,10 @@ export default class Mail {
log.debug('Send mail', this.options);
// Render email
this.options.html = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, false);
this.options.text = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, true);
this.options.html = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk',
this.data, false);
this.options.text = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk',
this.data, true);
// Send email
results.push(await Mail.getTransporter().sendMail(this.options));

View File

@ -7,8 +7,15 @@ import {PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE} from "../Mails";
import Mail from "../Mail";
import Controller from "../Controller";
import config from "config";
import Application from "../Application";
import NunjucksComponent from "../components/NunjucksComponent";
export default abstract class AuthGuard<P extends AuthProof<User>> {
public constructor(
private readonly app: Application,
) {
}
protected abstract async getProofForSession(session: Express.Session): Promise<P | null>;
protected async getProofForRequest(_req: Request): Promise<P | null> {
@ -77,7 +84,7 @@ export default abstract class AuthGuard<P extends AuthProof<User>> {
}
if (!user.isApproved()) {
await new Mail(PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, {
await new Mail(this.app.as(NunjucksComponent).getEnvironment(), PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, {
username: (await user.mainEmail.get())?.getOrFail('email'),
link: config.get<string>('base_url') + Controller.route('accounts-approval'),
}).send(config.get<string>('app.contact_email'));

View File

@ -1,6 +1,7 @@
import {Request, Response} from "express";
import Controller from "../Controller";
import Mail from "../Mail";
import NunjucksComponent from "../components/NunjucksComponent";
export default class MailController extends Controller {
public routes(): void {
@ -9,6 +10,7 @@ export default class MailController extends Controller {
protected async getMail(request: Request, response: Response): Promise<void> {
const template = request.params['template'];
response.send(Mail.parse(`mails/${template}.mjml.njk`, request.query, false));
response.send(Mail.parse(this.getApp().as(NunjucksComponent).getEnvironment(),
`mails/${template}.mjml.njk`, request.query, false));
}
}

View File

@ -99,6 +99,7 @@ export default abstract class MagicLinkAuthController extends AuthController {
// Register (email link)
const geo = geoip.lookup(req.ip);
await MagicLinkController.sendMagicLink(
this.getApp(),
req.getSession().id,
isRegistration ? this.registerMagicLinkActionType : this.loginMagicLinkActionType,
Controller.route('auth', undefined, {

View File

@ -8,9 +8,11 @@ import MagicLink from "../models/MagicLink";
import config from "config";
import Application from "../../Application";
import {ParsedUrlQueryInput} from "querystring";
import NunjucksComponent from "../../components/NunjucksComponent";
export default abstract class MagicLinkController<A extends Application> extends Controller {
public static async sendMagicLink(
app: Application,
sessionId: string,
actionType: string,
original_url: string,
@ -32,7 +34,7 @@ export default abstract class MagicLinkController<A extends Application> extends
await link.save();
// Send email
await new Mail(mailTemplate, Object.assign(data, {
await new Mail(app.as(NunjucksComponent).getEnvironment(), mailTemplate, Object.assign(data, {
link: `${config.get<string>('base_url')}${Controller.route('magic_link', undefined, {
id: link.id,
token: token,

View File

@ -13,7 +13,7 @@ import Middleware from "../Middleware";
export default class NunjucksComponent extends ApplicationComponent {
private readonly viewsPath: string[];
private env?: Environment;
private environment?: Environment;
public constructor(viewsPath: string[] = ['views']) {
super();
@ -32,7 +32,7 @@ export default class NunjucksComponent extends ApplicationComponent {
log.warn('Couldn\'t determine coreVersion.', e);
}
this.env = new nunjucks.Environment([
this.environment = new nunjucks.Environment([
...this.viewsPath.map(path => new nunjucks.FileSystemLoader(path)),
new nunjucks.FileSystemLoader(path.join(__dirname, '../../../views')),
new nunjucks.FileSystemLoader(path.join(__dirname, '../views')),
@ -60,7 +60,7 @@ export default class NunjucksComponent extends ApplicationComponent {
.addFilter('hex', (v: number) => {
return v.toString(16);
});
this.env.express(app);
this.environment.express(app);
app.set('view engine', 'njk');
}
@ -68,8 +68,9 @@ export default class NunjucksComponent extends ApplicationComponent {
this.use(NunjucksMiddleware);
}
public getEnv(): Environment | undefined {
return this.env;
public getEnvironment(): Environment {
if (!this.environment) throw new Error('Environment not initialized.');
return this.environment;
}
}
@ -77,7 +78,7 @@ export class NunjucksMiddleware extends Middleware {
private env?: Environment;
protected async handle(req: Request, res: Response, next: NextFunction): Promise<void> {
this.env = this.app.as(NunjucksComponent).getEnv();
this.env = this.app.as(NunjucksComponent).getEnvironment();
res.locals.url = req.url;
res.locals.params = req.params;
res.locals.query = req.query;

View File

@ -71,7 +71,7 @@ export default class WebSocketServerComponent extends ApplicationComponent {
});
});
const env = this.nunjucksComponent?.getEnv();
const env = this.nunjucksComponent?.getEnvironment();
if (env) {
env.addGlobal('websocketUrl', config.get('public_websocket_url'));
}

View File

@ -8,6 +8,7 @@ import {ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE} from "../Mails";
import UserEmail from "../auth/models/UserEmail";
import UserApprovedComponent from "../auth/models/UserApprovedComponent";
import {RequireAdminMiddleware, RequireAuthMiddleware} from "../auth/AuthComponent";
import NunjucksComponent from "../components/NunjucksComponent";
export default class BackendController extends Controller {
private static readonly menu: BackendMenuElement[] = [];
@ -75,7 +76,7 @@ export default class BackendController extends Controller {
await account.save();
if (email && email.email) {
await new Mail(ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, {
await new Mail(this.getApp().as(NunjucksComponent).getEnvironment(), ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, {
approved: true,
link: config.get<string>('base_url') + Controller.route('auth'),
}).send(email.email);
@ -91,7 +92,7 @@ export default class BackendController extends Controller {
await account.delete();
if (email && email.email) {
await new Mail(ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, {
await new Mail(this.getApp().as(NunjucksComponent).getEnvironment(), ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, {
approved: false,
}).send(email.email);
}

View File

@ -99,7 +99,7 @@ export class TestApp extends Application {
public async getProofForSession(session: Express.Session): Promise<MagicLink | null> {
return await MagicLink.bySessionId(session.id, ['login', 'register']);
}
}));
}(this)));
// Utils
this.use(new FormHelperComponent());