Render emails using NunjucksComponent's environment
This commit is contained in:
parent
cfb7bddca6
commit
4d0c714dbd
25
src/Mail.ts
25
src/Mail.ts
@ -1,7 +1,7 @@
|
|||||||
import nodemailer, {SentMessageInfo, Transporter} from "nodemailer";
|
import nodemailer, {SentMessageInfo, Transporter} from "nodemailer";
|
||||||
import config from "config";
|
import config from "config";
|
||||||
import {Options} from "nodemailer/lib/mailer";
|
import {Options} from "nodemailer/lib/mailer";
|
||||||
import nunjucks from 'nunjucks';
|
import {Environment} from 'nunjucks';
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import {WrappingError} from "./Utils";
|
import {WrappingError} from "./Utils";
|
||||||
import mjml2html from "mjml";
|
import mjml2html from "mjml";
|
||||||
@ -45,9 +45,14 @@ export default class Mail {
|
|||||||
if (this.transporter) this.transporter.close();
|
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;
|
data.text = textOnly;
|
||||||
const nunjucksResult = nunjucks.render(template, data);
|
const nunjucksResult = environment.render(template, data);
|
||||||
if (textOnly) return nunjucksResult;
|
if (textOnly) return nunjucksResult;
|
||||||
|
|
||||||
const mjmlResult = mjml2html(nunjucksResult, {});
|
const mjmlResult = mjml2html(nunjucksResult, {});
|
||||||
@ -59,11 +64,13 @@ export default class Mail {
|
|||||||
return mjmlResult.html;
|
return mjmlResult.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly template: MailTemplate;
|
|
||||||
private readonly options: Options = {};
|
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.template = template;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.options.subject = this.template.getSubject(data);
|
this.options.subject = this.template.getSubject(data);
|
||||||
@ -106,8 +113,10 @@ export default class Mail {
|
|||||||
log.debug('Send mail', this.options);
|
log.debug('Send mail', this.options);
|
||||||
|
|
||||||
// Render email
|
// Render email
|
||||||
this.options.html = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, false);
|
this.options.html = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk',
|
||||||
this.options.text = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, true);
|
this.data, false);
|
||||||
|
this.options.text = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk',
|
||||||
|
this.data, true);
|
||||||
|
|
||||||
// Send email
|
// Send email
|
||||||
results.push(await Mail.getTransporter().sendMail(this.options));
|
results.push(await Mail.getTransporter().sendMail(this.options));
|
||||||
|
@ -7,8 +7,15 @@ import {PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE} from "../Mails";
|
|||||||
import Mail from "../Mail";
|
import Mail from "../Mail";
|
||||||
import Controller from "../Controller";
|
import Controller from "../Controller";
|
||||||
import config from "config";
|
import config from "config";
|
||||||
|
import Application from "../Application";
|
||||||
|
import NunjucksComponent from "../components/NunjucksComponent";
|
||||||
|
|
||||||
export default abstract class AuthGuard<P extends AuthProof<User>> {
|
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 abstract async getProofForSession(session: Express.Session): Promise<P | null>;
|
||||||
|
|
||||||
protected async getProofForRequest(_req: Request): 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()) {
|
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'),
|
username: (await user.mainEmail.get())?.getOrFail('email'),
|
||||||
link: config.get<string>('base_url') + Controller.route('accounts-approval'),
|
link: config.get<string>('base_url') + Controller.route('accounts-approval'),
|
||||||
}).send(config.get<string>('app.contact_email'));
|
}).send(config.get<string>('app.contact_email'));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Request, Response} from "express";
|
import {Request, Response} from "express";
|
||||||
import Controller from "../Controller";
|
import Controller from "../Controller";
|
||||||
import Mail from "../Mail";
|
import Mail from "../Mail";
|
||||||
|
import NunjucksComponent from "../components/NunjucksComponent";
|
||||||
|
|
||||||
export default class MailController extends Controller {
|
export default class MailController extends Controller {
|
||||||
public routes(): void {
|
public routes(): void {
|
||||||
@ -9,6 +10,7 @@ export default class MailController extends Controller {
|
|||||||
|
|
||||||
protected async getMail(request: Request, response: Response): Promise<void> {
|
protected async getMail(request: Request, response: Response): Promise<void> {
|
||||||
const template = request.params['template'];
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,7 @@ export default abstract class MagicLinkAuthController extends AuthController {
|
|||||||
// Register (email link)
|
// Register (email link)
|
||||||
const geo = geoip.lookup(req.ip);
|
const geo = geoip.lookup(req.ip);
|
||||||
await MagicLinkController.sendMagicLink(
|
await MagicLinkController.sendMagicLink(
|
||||||
|
this.getApp(),
|
||||||
req.getSession().id,
|
req.getSession().id,
|
||||||
isRegistration ? this.registerMagicLinkActionType : this.loginMagicLinkActionType,
|
isRegistration ? this.registerMagicLinkActionType : this.loginMagicLinkActionType,
|
||||||
Controller.route('auth', undefined, {
|
Controller.route('auth', undefined, {
|
||||||
|
@ -8,9 +8,11 @@ import MagicLink from "../models/MagicLink";
|
|||||||
import config from "config";
|
import config from "config";
|
||||||
import Application from "../../Application";
|
import Application from "../../Application";
|
||||||
import {ParsedUrlQueryInput} from "querystring";
|
import {ParsedUrlQueryInput} from "querystring";
|
||||||
|
import NunjucksComponent from "../../components/NunjucksComponent";
|
||||||
|
|
||||||
export default abstract class MagicLinkController<A extends Application> extends Controller {
|
export default abstract class MagicLinkController<A extends Application> extends Controller {
|
||||||
public static async sendMagicLink(
|
public static async sendMagicLink(
|
||||||
|
app: Application,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
actionType: string,
|
actionType: string,
|
||||||
original_url: string,
|
original_url: string,
|
||||||
@ -32,7 +34,7 @@ export default abstract class MagicLinkController<A extends Application> extends
|
|||||||
await link.save();
|
await link.save();
|
||||||
|
|
||||||
// Send email
|
// 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, {
|
link: `${config.get<string>('base_url')}${Controller.route('magic_link', undefined, {
|
||||||
id: link.id,
|
id: link.id,
|
||||||
token: token,
|
token: token,
|
||||||
|
@ -13,7 +13,7 @@ import Middleware from "../Middleware";
|
|||||||
|
|
||||||
export default class NunjucksComponent extends ApplicationComponent {
|
export default class NunjucksComponent extends ApplicationComponent {
|
||||||
private readonly viewsPath: string[];
|
private readonly viewsPath: string[];
|
||||||
private env?: Environment;
|
private environment?: Environment;
|
||||||
|
|
||||||
public constructor(viewsPath: string[] = ['views']) {
|
public constructor(viewsPath: string[] = ['views']) {
|
||||||
super();
|
super();
|
||||||
@ -32,7 +32,7 @@ export default class NunjucksComponent extends ApplicationComponent {
|
|||||||
log.warn('Couldn\'t determine coreVersion.', e);
|
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)),
|
...this.viewsPath.map(path => new nunjucks.FileSystemLoader(path)),
|
||||||
new nunjucks.FileSystemLoader(path.join(__dirname, '../../../views')),
|
new nunjucks.FileSystemLoader(path.join(__dirname, '../../../views')),
|
||||||
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) => {
|
.addFilter('hex', (v: number) => {
|
||||||
return v.toString(16);
|
return v.toString(16);
|
||||||
});
|
});
|
||||||
this.env.express(app);
|
this.environment.express(app);
|
||||||
app.set('view engine', 'njk');
|
app.set('view engine', 'njk');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,8 +68,9 @@ export default class NunjucksComponent extends ApplicationComponent {
|
|||||||
this.use(NunjucksMiddleware);
|
this.use(NunjucksMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEnv(): Environment | undefined {
|
public getEnvironment(): Environment {
|
||||||
return this.env;
|
if (!this.environment) throw new Error('Environment not initialized.');
|
||||||
|
return this.environment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ export class NunjucksMiddleware extends Middleware {
|
|||||||
private env?: Environment;
|
private env?: Environment;
|
||||||
|
|
||||||
protected async handle(req: Request, res: Response, next: NextFunction): Promise<void> {
|
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.url = req.url;
|
||||||
res.locals.params = req.params;
|
res.locals.params = req.params;
|
||||||
res.locals.query = req.query;
|
res.locals.query = req.query;
|
||||||
|
@ -71,7 +71,7 @@ export default class WebSocketServerComponent extends ApplicationComponent {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const env = this.nunjucksComponent?.getEnv();
|
const env = this.nunjucksComponent?.getEnvironment();
|
||||||
if (env) {
|
if (env) {
|
||||||
env.addGlobal('websocketUrl', config.get('public_websocket_url'));
|
env.addGlobal('websocketUrl', config.get('public_websocket_url'));
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import {ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE} from "../Mails";
|
|||||||
import UserEmail from "../auth/models/UserEmail";
|
import UserEmail from "../auth/models/UserEmail";
|
||||||
import UserApprovedComponent from "../auth/models/UserApprovedComponent";
|
import UserApprovedComponent from "../auth/models/UserApprovedComponent";
|
||||||
import {RequireAdminMiddleware, RequireAuthMiddleware} from "../auth/AuthComponent";
|
import {RequireAdminMiddleware, RequireAuthMiddleware} from "../auth/AuthComponent";
|
||||||
|
import NunjucksComponent from "../components/NunjucksComponent";
|
||||||
|
|
||||||
export default class BackendController extends Controller {
|
export default class BackendController extends Controller {
|
||||||
private static readonly menu: BackendMenuElement[] = [];
|
private static readonly menu: BackendMenuElement[] = [];
|
||||||
@ -75,7 +76,7 @@ export default class BackendController extends Controller {
|
|||||||
await account.save();
|
await account.save();
|
||||||
|
|
||||||
if (email && email.email) {
|
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,
|
approved: true,
|
||||||
link: config.get<string>('base_url') + Controller.route('auth'),
|
link: config.get<string>('base_url') + Controller.route('auth'),
|
||||||
}).send(email.email);
|
}).send(email.email);
|
||||||
@ -91,7 +92,7 @@ export default class BackendController extends Controller {
|
|||||||
await account.delete();
|
await account.delete();
|
||||||
|
|
||||||
if (email && email.email) {
|
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,
|
approved: false,
|
||||||
}).send(email.email);
|
}).send(email.email);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ export class TestApp extends Application {
|
|||||||
public async getProofForSession(session: Express.Session): Promise<MagicLink | null> {
|
public async getProofForSession(session: Express.Session): Promise<MagicLink | null> {
|
||||||
return await MagicLink.bySessionId(session.id, ['login', 'register']);
|
return await MagicLink.bySessionId(session.id, ['login', 'register']);
|
||||||
}
|
}
|
||||||
}));
|
}(this)));
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
this.use(new FormHelperComponent());
|
this.use(new FormHelperComponent());
|
||||||
|
Loading…
Reference in New Issue
Block a user