Merge pull request #836 from Hexastack/feat/use-handlebars-infavor-of-ejs
Some checks are pending
Build and Push Docker API Image / build-and-push (push) Waiting to run
Build and Push Docker Base Image / build-and-push (push) Waiting to run
Build and Push Docker UI Image / build-and-push (push) Waiting to run

feat: use handlebars as template engine for mailing & remove ejs
This commit is contained in:
Med Marrouchi 2025-03-21 01:27:27 +01:00 committed by GitHub
commit 5e31cfe459
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 91 additions and 59 deletions

9
api/package-lock.json generated
View File

@ -33,7 +33,6 @@
"connect-mongo": "^5.1.0", "connect-mongo": "^5.1.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",
@ -7408,7 +7407,8 @@
"node_modules/async": { "node_modules/async": {
"version": "3.2.5", "version": "3.2.5",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
"optional": true
}, },
"node_modules/async-mutex": { "node_modules/async-mutex": {
"version": "0.4.1", "version": "0.4.1",
@ -9629,6 +9629,7 @@
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"optional": true,
"dependencies": { "dependencies": {
"jake": "^10.8.5" "jake": "^10.8.5"
}, },
@ -10805,6 +10806,7 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"optional": true,
"dependencies": { "dependencies": {
"minimatch": "^5.0.1" "minimatch": "^5.0.1"
} }
@ -10813,6 +10815,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"optional": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@ -10821,6 +10824,7 @@
"version": "5.1.6", "version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"optional": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
@ -12526,6 +12530,7 @@
"version": "10.8.7", "version": "10.8.7",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
"integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
"optional": true,
"dependencies": { "dependencies": {
"async": "^3.2.3", "async": "^3.2.3",
"chalk": "^4.0.2", "chalk": "^4.0.2",

View File

@ -68,7 +68,6 @@
"connect-mongo": "^5.1.0", "connect-mongo": "^5.1.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",

View File

@ -71,7 +71,15 @@ const i18nOptions: I18nOptions = {
debug: false, debug: false,
}), }),
template: { template: {
adapter: new MjmlAdapter('ejs', { inlineCssEnabled: false }), adapter: new MjmlAdapter(
'handlebars',
{
inlineCssEnabled: false,
},
{
handlebar: {},
},
),
dir: path.join(process.cwd(), 'dist', 'templates'), dir: path.join(process.cwd(), 'dist', 'templates'),
options: { options: {
context: { context: {

View File

@ -2,34 +2,38 @@
<mj-body> <mj-body>
<mj-section> <mj-section>
<mj-column> <mj-column>
<mj-image width="186px" src="https://hexabot.ai/assets/images/logo.png"></mj-image> <mj-image
width="186px"
src="https://hexabot.ai/assets/images/logo.png"
></mj-image>
<mj-divider border-color="#000"></mj-divider> <mj-divider border-color="#000"></mj-divider>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('hi') %> <%= first_name %>,</mj-text {{t 'hi'}} {{first_name}},
> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('account_successfully_created_confirm_password') %></mj-text {{t 'account_successfully_created_confirm_password'}}
> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('confirm_account') %></mj-text {{t 'confirm_account'}}
> </mj-text>
<mj-button <mj-button
href="<%= this.appUrl %>/login/<%= token %>" href="{{appUrl}}/login/{{token}}"
font-size="16px" font-size="16px"
background-color="#000" background-color="#000"
><%= t('confirm') %></mj-button
> >
{{t 'confirm'}}
</mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section> <mj-section>
<mj-column> <mj-column>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('best_regards') %></mj-text {{t 'best_regards'}}
> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= this.appName %></mj-text {{appName}}
> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>
</mj-body> </mj-body>

View File

@ -2,36 +2,39 @@
<mj-body> <mj-body>
<mj-section> <mj-section>
<mj-column> <mj-column>
<mj-image width="186px" src="https://hexabot.ai/assets/images/logo.png"></mj-image> <mj-image
width="186px"
src="https://hexabot.ai/assets/images/logo.png"
></mj-image>
<mj-divider border-color="#000"></mj-divider> <mj-divider border-color="#000"></mj-divider>
<mj-text font-size="16px" color="#000" font-family="helvetica"> <mj-text font-size="16px" color="#000" font-family="helvetica">
<%= t('welcome') %>, {{t 'welcome'}}
</mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica">
{{t 'invitation_for_account_creation'}} {{appName}} {{t 'account'}}.
</mj-text> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica"
><%= `${t('invitation_for_account_creation')} ${this.appName} >{{ t 'create_account'}}</mj-text
${t('account')}.` %></mj-text
>
<mj-text font-size="16px" color="#000" font-family="helvetica"
><%= t('create_account') %></mj-text
> >
<mj-button <mj-button
href="<%= this.appUrl %>/register/<%= token %>" href="{{appUrl}}/register/{{token}}"
font-size="16px" font-size="16px"
background-color="#000" background-color="#000"
><%= t('join') %></mj-button >
{{t 'join'}}</mj-button
> >
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section> <mj-section>
<mj-column> <mj-column>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica"
><%= t('best_regards') %></mj-text >{{t 'best_regards'}}
> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica"
><%= this.appName %></mj-text >{{this.appName }}</mj-text
> >
</mj-column> </mj-column>
</mj-section> </mj-section>

View File

@ -2,33 +2,37 @@
<mj-body> <mj-body>
<mj-section> <mj-section>
<mj-column> <mj-column>
<mj-image width="186px" src="https://hexabot.ai/assets/images/logo.png"></mj-image> <mj-image
width="186px"
src="https://hexabot.ai/assets/images/logo.png"
></mj-image>
<mj-divider border-color="#000"></mj-divider> <mj-divider border-color="#000"></mj-divider>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('hi') %> <%= first_name %>,</mj-text {{t 'hi'}} {{first_name}},
> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('password_reset_request') %></mj-text {{t 'password_reset_request'}}
> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('click_to_reset_password') %></mj-text {{t 'click_to_reset_password'}}
> </mj-text>
<mj-button <mj-button
href="<%= this.appUrl %>/reset/<%= token %>" href="{{appUrl}}/reset/{{token}}"
font-size="16px" font-size="16px"
background-color="#000" background-color="#000"
><%= t('reset') %></mj-button
> >
{{t 'reset'}}
</mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section> <mj-section>
<mj-column> <mj-column>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= t('best_regards') %></mj-text {{t 'best_regards'}}
> </mj-text>
<mj-text font-size="16px" color="#000" font-family="helvetica" <mj-text font-size="16px" color="#000" font-family="helvetica">
><%= this.appName %></mj-text {{appName}}
> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>
</mj-body> </mj-body>

View File

@ -1,11 +1,13 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
// eslint-disable-next-line import/order
import { MailerService } from '@nestjs-modules/mailer';
import { import {
Inject, Inject,
Injectable, Injectable,
@ -13,7 +15,6 @@ import {
Optional, Optional,
} from '@nestjs/common'; } from '@nestjs/common';
import { JwtService, JwtSignOptions } from '@nestjs/jwt'; import { JwtService, JwtSignOptions } from '@nestjs/jwt';
import { MailerService } from '@nestjs-modules/mailer';
import { config } from '@/config'; import { config } from '@/config';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
@ -69,6 +70,8 @@ export class InvitationService extends BaseService<
to: dto.email, to: dto.email,
template: 'invitation.mjml', template: 'invitation.mjml',
context: { context: {
appName: config.parameters.appName,
appUrl: config.uiBaseUrl,
token: jwt, token: jwt,
// TODO: Which language should we use? // TODO: Which language should we use?
t: (key: string) => t: (key: string) =>

View File

@ -1,11 +1,13 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
// eslint-disable-next-line import/order
import { MailerService } from '@nestjs-modules/mailer';
import { import {
BadRequestException, BadRequestException,
Inject, Inject,
@ -16,7 +18,6 @@ import {
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { JwtService, JwtSignOptions } from '@nestjs/jwt'; import { JwtService, JwtSignOptions } from '@nestjs/jwt';
import { MailerService } from '@nestjs-modules/mailer';
import { compareSync } from 'bcryptjs'; import { compareSync } from 'bcryptjs';
import { config } from '@/config'; import { config } from '@/config';
@ -66,6 +67,8 @@ export class PasswordResetService {
to: dto.email, to: dto.email,
template: 'password_reset.mjml', template: 'password_reset.mjml',
context: { context: {
appName: config.parameters.appName,
appUrl: config.uiBaseUrl,
token: jwt, token: jwt,
first_name: user.first_name, first_name: user.first_name,
t: (key: string) => t: (key: string) =>

View File

@ -1,11 +1,13 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
// eslint-disable-next-line import/order
import { MailerService } from '@nestjs-modules/mailer';
import { import {
Inject, Inject,
Injectable, Injectable,
@ -14,7 +16,6 @@ import {
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { JwtService, JwtSignOptions } from '@nestjs/jwt'; import { JwtService, JwtSignOptions } from '@nestjs/jwt';
import { MailerService } from '@nestjs-modules/mailer';
import { config } from '@/config'; import { config } from '@/config';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
@ -83,6 +84,8 @@ export class ValidateAccountService {
to: dto.email, to: dto.email,
template: 'account_confirmation.mjml', template: 'account_confirmation.mjml',
context: { context: {
appName: config.parameters.appName,
appUrl: config.uiBaseUrl,
token: confirmationToken, token: confirmationToken,
first_name: dto.first_name, first_name: dto.first_name,
t: (key: string) => t: (key: string) =>