{
- return await this.compileTemplate.execute(
- CompileTemplateCommand.create({
- template: content as string,
- data: {
- ...payload,
- },
- })
- );
+ return await this.compileTemplate.execute({
+ i18next: i18nInstance,
+ template: content as string,
+ data: {
+ ...payload,
+ },
+ });
}
}
diff --git a/libs/application-generic/src/usecases/compile-template/compile-template.command.ts b/libs/application-generic/src/usecases/compile-template/compile-template.command.ts
index 6fc7147263c..f84214dba4f 100644
--- a/libs/application-generic/src/usecases/compile-template/compile-template.command.ts
+++ b/libs/application-generic/src/usecases/compile-template/compile-template.command.ts
@@ -8,4 +8,6 @@ export class CompileTemplateCommand extends BaseCommand {
@IsObject()
data: any; // eslint-disable-line @typescript-eslint/no-explicit-any
+
+ i18next?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}
diff --git a/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts b/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts
index 9b2393168b2..9cca122c4a1 100644
--- a/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts
+++ b/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts
@@ -16,129 +16,116 @@ describe('Compile Template', function () {
});
it('should render custom html', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- branding: {
- color: '#e7e7e7e9',
- },
- name: 'Test Name',
+ const result = await useCase.execute({
+ data: {
+ branding: {
+ color: '#e7e7e7e9',
},
- template: '{{name}}
',
- })
- );
+ name: 'Test Name',
+ },
+ template: '{{name}}
',
+ });
expect(result).toEqual('Test Name
');
});
it('should render pluralisation in html', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- branding: {
- color: '#e7e7e7e9',
- },
- dog_count: 1,
- sausage_count: 2,
+ const result = await useCase.execute({
+ data: {
+ branding: {
+ color: '#e7e7e7e9',
},
- template:
- '{{dog_count}} {{pluralize dog_count "dog" "dogs"}} and {{sausage_count}} {{pluralize sausage_count "sausage" "sausages"}} for {{pluralize dog_count "him" "them"}}
',
- })
- );
+ dog_count: 1,
+ sausage_count: 2,
+ },
+ template:
+ '{{dog_count}} {{pluralize dog_count "dog" "dogs"}} and {{sausage_count}} {{pluralize sausage_count "sausage" "sausages"}} for {{pluralize dog_count "him" "them"}}
',
+ });
expect(result).toEqual('1 dog and 2 sausages for him
');
});
it('should render unique values of array', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- names: [{ name: 'dog' }, { name: 'cat' }, { name: 'dog' }],
- },
- template:
- '{{#each (unique names "name")}}{{this}}-{{/each}}
',
- })
- );
+ const result = await useCase.execute({
+ data: {
+ names: [{ name: 'dog' }, { name: 'cat' }, { name: 'dog' }],
+ },
+ template: '{{#each (unique names "name")}}{{this}}-{{/each}}
',
+ });
expect(result).toEqual('dog-cat-
');
});
it('should render groupBy values of array', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- names: [
- {
- name: 'Name 1',
- age: '30',
- },
- {
- name: 'Name 2',
- age: '31',
- },
- {
- name: 'Name 1',
- age: '32',
- },
- ],
- },
- template:
- '{{#each (groupBy names "name")}}{{key}}
{{#each items}}{{age}}-{{/each}}{{/each}}',
- })
- );
+ const result = await useCase.execute({
+ data: {
+ names: [
+ {
+ name: 'Name 1',
+ age: '30',
+ },
+ {
+ name: 'Name 2',
+ age: '31',
+ },
+ {
+ name: 'Name 1',
+ age: '32',
+ },
+ ],
+ },
+ template:
+ '{{#each (groupBy names "name")}}{{key}}
{{#each items}}{{age}}-{{/each}}{{/each}}',
+ });
expect(result).toEqual('Name 1
30-32-Name 2
31-');
});
it('should render sortBy values of array', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- people: [
- {
- name: 'a75',
- item1: false,
- item2: false,
- id: 1,
- updated_at: '2023-01-01T06:25:24Z',
- },
- {
- name: 'z32',
- item1: true,
- item2: false,
- id: 3,
- updated_at: '2023-01-09T11:25:13Z',
- },
- {
- name: 'e77',
- item1: false,
- item2: false,
- id: 2,
- updated_at: '2023-01-05T04:13:24Z',
- },
- ],
- },
- template: `{{#each (sortBy people 'updated_at')}}{{name}} - {{id}}{{/each}}`,
- })
- );
+ const result = await useCase.execute({
+ data: {
+ people: [
+ {
+ name: 'a75',
+ item1: false,
+ item2: false,
+ id: 1,
+ updated_at: '2023-01-01T06:25:24Z',
+ },
+ {
+ name: 'z32',
+ item1: true,
+ item2: false,
+ id: 3,
+ updated_at: '2023-01-09T11:25:13Z',
+ },
+ {
+ name: 'e77',
+ item1: false,
+ item2: false,
+ id: 2,
+ updated_at: '2023-01-05T04:13:24Z',
+ },
+ ],
+ },
+ template: `{{#each (sortBy people 'updated_at')}}{{name}} - {{id}}{{/each}}`,
+ });
expect(result).toEqual('a75 - 1e77 - 2z32 - 3');
});
it('should allow the user to specify handlebars helpers', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- branding: {
- color: '#e7e7e7e9',
- },
- message: 'hello world',
- messageTwo: 'hEllo world',
+ const result = await useCase.execute({
+ data: {
+ branding: {
+ color: '#e7e7e7e9',
},
- template:
- '{{titlecase message}} and {{lowercase messageTwo}} and {{uppercase message}}
',
- })
- );
+ message: 'hello world',
+ messageTwo: 'hEllo world',
+ },
+ template:
+ '{{titlecase message}} and {{lowercase messageTwo}} and {{uppercase message}}
',
+ });
expect(result).toEqual(
'Hello World and hello world and HELLO WORLD
'
@@ -146,65 +133,55 @@ describe('Compile Template', function () {
});
it('should allow apostrophes to be in data', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- message: "hello' world",
- },
- template: '{{message}}
',
- })
- );
+ const result = await useCase.execute({
+ data: {
+ message: "hello' world",
+ },
+ template: '{{message}}
',
+ });
expect(result).toEqual("hello' world
");
});
describe('Date Formation', function () {
it('should allow user to format the date', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- date: '2020-01-01',
- },
- template: "{{dateFormat date 'EEEE, MMMM Do yyyy'}}
",
- })
- );
+ const result = await useCase.execute({
+ data: {
+ date: '2020-01-01',
+ },
+ template: "{{dateFormat date 'EEEE, MMMM Do yyyy'}}
",
+ });
expect(result).toEqual('Wednesday, January 1st 2020
');
});
it('should not fail and return same date for invalid date', async function () {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: {
- date: 'ABCD',
- },
- template: "{{dateFormat date 'EEEE, MMMM Do yyyy'}}
",
- })
- );
+ const result = await useCase.execute({
+ data: {
+ date: 'ABCD',
+ },
+ template: "{{dateFormat date 'EEEE, MMMM Do yyyy'}}
",
+ });
expect(result).toEqual('ABCD
');
});
});
describe('Number formating', () => {
it('should format number', async () => {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: { number: 1000000000 },
- template:
- '{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}
',
- })
- );
+ const result = await useCase.execute({
+ data: { number: 1000000000 },
+ template:
+ '{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}
',
+ });
expect(result).toEqual('1|000|000|000,00
');
});
it('should not fail and return passed value', async () => {
- const result = await useCase.execute(
- CompileTemplateCommand.create({
- data: { number: 'Not a number' },
- template:
- '{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}
',
- })
- );
+ const result = await useCase.execute({
+ data: { number: 'Not a number' },
+ template:
+ '{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}
',
+ });
expect(result).toEqual('Not a number
');
});
diff --git a/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts b/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts
index 61c452b5e13..7253a4a8184 100644
--- a/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts
+++ b/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts
@@ -4,7 +4,6 @@ import { format } from 'date-fns';
import { HandlebarHelpersEnum } from '@novu/shared';
import { CompileTemplateCommand } from './compile-template.command';
-import * as i18next from 'i18next';
import { ApiException } from '../../utils/exceptions';
const assertResult = (condition: boolean, options) => {
@@ -13,212 +12,228 @@ const assertResult = (condition: boolean, options) => {
return typeof fn === 'function' ? fn(this) : condition;
};
-Handlebars.registerHelper(
- HandlebarHelpersEnum.I18N,
- function (key, { hash, data, fn }) {
- const options = {
- ...data.root.i18next,
- ...hash,
- returnObjects: false,
- };
+function createHandlebarsInstance(i18next: any) {
+ const handlebars = Handlebars.create();
+
+ handlebars.registerHelper('json', function (context) {
+ return JSON.stringify(context);
+ });
+
+ if (i18next) {
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.I18N,
+ function (key, { hash, data, fn }) {
+ const options = {
+ ...data.root.i18next,
+ ...hash,
+ returnObjects: false,
+ };
+
+ const replace = (options.replace = {
+ // eslint-disable-next-line
+ // @ts-ignore
+ ...this,
+ ...options.replace,
+ ...hash,
+ });
+ delete replace.i18next; // may creep in if this === data.root
+
+ if (fn) {
+ options.defaultValue = fn(replace);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ return new handlebars.SafeString(i18next.t(key, options));
+ }
+ );
+ }
- const replace = (options.replace = {
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.EQUALS,
+ function (arg1, arg2, options) {
// eslint-disable-next-line
- // @ts-ignore
- ...this,
- ...options.replace,
- ...hash,
- });
- delete replace.i18next; // may creep in if this === data.root
-
- if (fn) {
- options.defaultValue = fn(replace);
+ // @ts-expect-error
+ return arg1 == arg2 ? options.fn(this) : options.inverse(this);
}
-
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- return new Handlebars.SafeString(i18next.t(key, options));
- }
-);
-Handlebars.registerHelper(
- HandlebarHelpersEnum.EQUALS,
- function (arg1, arg2, options) {
- // eslint-disable-next-line
- // @ts-expect-error
- return arg1 == arg2 ? options.fn(this) : options.inverse(this);
- }
-);
-
-Handlebars.registerHelper(HandlebarHelpersEnum.TITLECASE, function (value) {
- return value
- ?.split(' ')
- .map(
- (letter) => letter.charAt(0).toUpperCase() + letter.slice(1).toLowerCase()
- )
- .join(' ');
-});
-
-Handlebars.registerHelper(HandlebarHelpersEnum.UPPERCASE, function (value) {
- return value?.toUpperCase();
-});
-
-Handlebars.registerHelper(HandlebarHelpersEnum.LOWERCASE, function (value) {
- return value?.toLowerCase();
-});
-
-Handlebars.registerHelper(
- HandlebarHelpersEnum.PLURALIZE,
- function (number, single, plural) {
- return number === 1 ? single : plural;
- }
-);
-
-Handlebars.registerHelper(
- HandlebarHelpersEnum.DATEFORMAT,
- function (date, dateFormat) {
- // Format date if parameters are valid
- if (date && dateFormat && !isNaN(Date.parse(date))) {
- return format(new Date(date), dateFormat);
+ );
+
+ handlebars.registerHelper(HandlebarHelpersEnum.TITLECASE, function (value) {
+ return value
+ ?.split(' ')
+ .map(
+ (letter) =>
+ letter.charAt(0).toUpperCase() + letter.slice(1).toLowerCase()
+ )
+ .join(' ');
+ });
+
+ handlebars.registerHelper(HandlebarHelpersEnum.UPPERCASE, function (value) {
+ return value?.toUpperCase();
+ });
+
+ handlebars.registerHelper(HandlebarHelpersEnum.LOWERCASE, function (value) {
+ return value?.toLowerCase();
+ });
+
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.PLURALIZE,
+ function (number, single, plural) {
+ return number === 1 ? single : plural;
}
-
- return date;
- }
-);
-
-Handlebars.registerHelper(
- HandlebarHelpersEnum.GROUP_BY,
- function (array, property) {
- if (!Array.isArray(array)) return [];
- const map = {};
- array.forEach((item) => {
- if (item[property]) {
- const key = item[property];
- if (!map[key]) {
- map[key] = [item];
- } else {
- map[key].push(item);
- }
+ );
+
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.DATEFORMAT,
+ function (date, dateFormat) {
+ // Format date if parameters are valid
+ if (date && dateFormat && !isNaN(Date.parse(date))) {
+ return format(new Date(date), dateFormat);
}
- });
- const result = [];
- for (const [key, value] of Object.entries(map)) {
- result.push({ key: key, items: value });
+ return date;
}
-
- return result;
- }
-);
-
-Handlebars.registerHelper(
- HandlebarHelpersEnum.UNIQUE,
- function (array, property) {
- if (!Array.isArray(array)) return '';
-
- return array
- .map((item) => {
+ );
+
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.GROUP_BY,
+ function (array, property) {
+ if (!Array.isArray(array)) return [];
+ const map = {};
+ array.forEach((item) => {
if (item[property]) {
- return item[property];
+ const key = item[property];
+ if (!map[key]) {
+ map[key] = [item];
+ } else {
+ map[key].push(item);
+ }
}
- })
- .filter((value, index, self) => self.indexOf(value) === index);
- }
-);
+ });
-Handlebars.registerHelper(
- HandlebarHelpersEnum.SORT_BY,
- function (array, property) {
- if (!Array.isArray(array)) return '';
- if (!property) return array.sort();
+ const result = [];
+ for (const [key, value] of Object.entries(map)) {
+ result.push({ key: key, items: value });
+ }
- return array.sort(function (a, b) {
- const _x = a[property];
- const _y = b[property];
+ return result;
+ }
+ );
+
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.UNIQUE,
+ function (array, property) {
+ if (!Array.isArray(array)) return '';
+
+ return array
+ .map((item) => {
+ if (item[property]) {
+ return item[property];
+ }
+ })
+ .filter((value, index, self) => self.indexOf(value) === index);
+ }
+ );
- return _x < _y ? -1 : _x > _y ? 1 : 0;
- });
- }
-);
-
-// based on: https://gist.github.com/DennyLoko/61882bc72176ca74a0f2
-Handlebars.registerHelper(
- HandlebarHelpersEnum.NUMBERFORMAT,
- function (number, options) {
- if (isNaN(number)) {
- return number;
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.SORT_BY,
+ function (array, property) {
+ if (!Array.isArray(array)) return '';
+ if (!property) return array.sort();
+
+ return array.sort(function (a, b) {
+ const _x = a[property];
+ const _y = b[property];
+
+ return _x < _y ? -1 : _x > _y ? 1 : 0;
+ });
}
+ );
+
+ // based on: https://gist.github.com/DennyLoko/61882bc72176ca74a0f2
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.NUMBERFORMAT,
+ function (number, options) {
+ if (isNaN(number)) {
+ return number;
+ }
- const decimalLength = options.hash.decimalLength || 2;
- const thousandsSep = options.hash.thousandsSep || ',';
- const decimalSep = options.hash.decimalSep || '.';
+ const decimalLength = options.hash.decimalLength || 2;
+ const thousandsSep = options.hash.thousandsSep || ',';
+ const decimalSep = options.hash.decimalSep || '.';
- const value = parseFloat(number);
+ const value = parseFloat(number);
- const re = '\\d(?=(\\d{3})+' + (decimalLength > 0 ? '\\D' : '$') + ')';
+ const re = '\\d(?=(\\d{3})+' + (decimalLength > 0 ? '\\D' : '$') + ')';
- const num = value.toFixed(Math.max(0, ~~decimalLength));
+ const num = value.toFixed(Math.max(0, ~~decimalLength));
- return (decimalSep ? num.replace('.', decimalSep) : num).replace(
- new RegExp(re, 'g'),
- '$&' + thousandsSep
- );
- }
-);
+ return (decimalSep ? num.replace('.', decimalSep) : num).replace(
+ new RegExp(re, 'g'),
+ '$&' + thousandsSep
+ );
+ }
+ );
-Handlebars.registerHelper(
- HandlebarHelpersEnum.GT,
- function (arg1, arg2, options) {
- return assertResult(arg1 > arg2, options);
- }
-);
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.GT,
+ function (arg1, arg2, options) {
+ return assertResult(arg1 > arg2, options);
+ }
+ );
-Handlebars.registerHelper(
- HandlebarHelpersEnum.GTE,
- function (arg1, arg2, options) {
- return assertResult(arg1 >= arg2, options);
- }
-);
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.GTE,
+ function (arg1, arg2, options) {
+ return assertResult(arg1 >= arg2, options);
+ }
+ );
-Handlebars.registerHelper(
- HandlebarHelpersEnum.LT,
- function (arg1, arg2, options) {
- return assertResult(arg1 < arg2, options);
- }
-);
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.LT,
+ function (arg1, arg2, options) {
+ return assertResult(arg1 < arg2, options);
+ }
+ );
-Handlebars.registerHelper(
- HandlebarHelpersEnum.LTE,
- function (arg1, arg2, options) {
- return assertResult(arg1 <= arg2, options);
- }
-);
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.LTE,
+ function (arg1, arg2, options) {
+ return assertResult(arg1 <= arg2, options);
+ }
+ );
-Handlebars.registerHelper(
- HandlebarHelpersEnum.EQ,
- function (arg1, arg2, options) {
- return assertResult(arg1 === arg2, options);
- }
-);
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.EQ,
+ function (arg1, arg2, options) {
+ return assertResult(arg1 === arg2, options);
+ }
+ );
-Handlebars.registerHelper(
- HandlebarHelpersEnum.NE,
- function (arg1, arg2, options) {
- return assertResult(arg1 !== arg2, options);
- }
-);
+ handlebars.registerHelper(
+ HandlebarHelpersEnum.NE,
+ function (arg1, arg2, options) {
+ return assertResult(arg1 !== arg2, options);
+ }
+ );
+
+ return handlebars;
+}
@Injectable()
export class CompileTemplate {
async execute(command: CompileTemplateCommand): Promise {
const templateContent = command.template || '';
+
let result = '';
try {
- const template = Handlebars.compile(templateContent);
+ const handlebars = createHandlebarsInstance(command.i18next);
+ const template = handlebars.compile(templateContent);
result = template(command.data, {});
} catch (e: any) {
throw new ApiException(
- e?.message || `Message content could not be generated`
+ e?.message || `Handlebars message content could not be generated ${e}`
);
}
diff --git a/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts b/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts
index 703eeadfbf8..8e0c0a4647e 100644
--- a/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts
+++ b/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts
@@ -645,14 +645,12 @@ export class ConditionsFilter extends Filter {
job: IJob
): Promise {
try {
- return await this.compileTemplate.execute(
- CompileTemplateCommand.create({
- template: value,
- data: {
- ...variables,
- },
- })
- );
+ return await this.compileTemplate.execute({
+ template: value,
+ data: {
+ ...variables,
+ },
+ });
} catch (e: any) {
await this.executionLogRoute.execute(
ExecutionLogRouteCommand.create({
diff --git a/libs/application-generic/tsconfig.json b/libs/application-generic/tsconfig.json
index 3f478640993..e417b7aaf4c 100644
--- a/libs/application-generic/tsconfig.json
+++ b/libs/application-generic/tsconfig.json
@@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
+ "sourceMap": true,
"strictNullChecks": false,
"allowSyntheticDefaultImports": true,
"outDir": "build/main",
diff --git a/libs/application-generic/tsconfig.module.json b/libs/application-generic/tsconfig.module.json
index 9df84697738..82d60a2e6d7 100644
--- a/libs/application-generic/tsconfig.module.json
+++ b/libs/application-generic/tsconfig.module.json
@@ -1,6 +1,7 @@
{
"extends": "./tsconfig",
"compilerOptions": {
+ "sourceMap": true,
"target": "esnext",
"outDir": "build/module",
"module": "esnext",