@ackplus/nest-dynamic-templates 0.1.51 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -284
- package/eslint.config.mjs +22 -0
- package/jest.config.ts +10 -0
- package/package.json +7 -58
- package/project.json +38 -0
- package/src/{index.d.ts → index.ts} +14 -1
- package/src/lib/constant.ts +2 -0
- package/src/lib/dto/create-template-layout.dto.ts +65 -0
- package/src/lib/dto/create-template.dto.ts +80 -0
- package/src/lib/dto/render-content-template-layout.dto.ts +32 -0
- package/src/lib/dto/render-content-template.dto.ts +37 -0
- package/src/lib/dto/render-template-layout.dto.ts +55 -0
- package/src/lib/dto/render-template.dto.ts +74 -0
- package/src/lib/dto/template-filter.dto.ts +52 -0
- package/src/lib/dto/template-layout-filter.dto.ts +51 -0
- package/src/lib/engines/language/html.engine.ts +49 -0
- package/src/lib/engines/language/markdown.engine.ts +37 -0
- package/src/lib/engines/language/mjml.engine.ts +44 -0
- package/src/lib/engines/language/text.engine.ts +15 -0
- package/src/lib/engines/{language-engine.d.ts → language-engine.ts} +7 -1
- package/src/lib/engines/template/ejs.engine.ts +33 -0
- package/src/lib/engines/template/handlebars.engine.ts +35 -0
- package/src/lib/engines/template/nunjucks.engine.ts +70 -0
- package/src/lib/engines/template/pug.engine.ts +43 -0
- package/src/lib/engines/{template-engine.d.ts → template-engine.ts} +6 -1
- package/src/lib/entities/template-layout.entity.ts +99 -0
- package/src/lib/entities/template.entity.ts +105 -0
- package/src/lib/errors/{template.errors.js → template.errors.ts} +20 -23
- package/src/lib/interfaces/{module-config.interface.d.ts → module-config.interface.ts} +16 -1
- package/src/lib/interfaces/template.types.ts +25 -0
- package/src/lib/nest-dynamic-templates.module.ts +143 -0
- package/src/lib/services/template-config.service.ts +112 -0
- package/src/lib/services/template-engine.registry.ts +109 -0
- package/src/lib/services/template-layout.service.ts +407 -0
- package/src/lib/services/template.service.ts +476 -0
- package/src/test/{helpers.js → helpers.ts} +5 -8
- package/src/test/nunjucks.service.spec.ts +157 -0
- package/src/test/pug.service.spec-temp +254 -0
- package/src/test/template-layout.service.spec.ts +422 -0
- package/src/test/template.service.spec.ts +862 -0
- package/src/test/test-database.config.ts +24 -0
- package/src/test/test-database.d.ts +6 -0
- package/src/test/test.setup.ts +34 -0
- package/src/types/ioredis.d.ts +6 -0
- package/src/types/mjml.d.ts +5 -0
- package/tsconfig.json +17 -0
- package/tsconfig.lib.json +14 -0
- package/tsconfig.spec.json +15 -0
- package/src/index.js +0 -23
- package/src/index.js.map +0 -1
- package/src/lib/constant.d.ts +0 -2
- package/src/lib/constant.js +0 -6
- package/src/lib/constant.js.map +0 -1
- package/src/lib/dto/create-template-layout.dto.d.ts +0 -15
- package/src/lib/dto/create-template-layout.dto.js +0 -87
- package/src/lib/dto/create-template-layout.dto.js.map +0 -1
- package/src/lib/dto/create-template.dto.d.ts +0 -17
- package/src/lib/dto/create-template.dto.js +0 -104
- package/src/lib/dto/create-template.dto.js.map +0 -1
- package/src/lib/dto/render-content-template-layout.dto.d.ts +0 -7
- package/src/lib/dto/render-content-template-layout.dto.js +0 -41
- package/src/lib/dto/render-content-template-layout.dto.js.map +0 -1
- package/src/lib/dto/render-content-template.dto.d.ts +0 -8
- package/src/lib/dto/render-content-template.dto.js +0 -47
- package/src/lib/dto/render-content-template.dto.js.map +0 -1
- package/src/lib/dto/render-template-layout.dto.d.ts +0 -10
- package/src/lib/dto/render-template-layout.dto.js +0 -68
- package/src/lib/dto/render-template-layout.dto.js.map +0 -1
- package/src/lib/dto/render-template.dto.d.ts +0 -14
- package/src/lib/dto/render-template.dto.js +0 -91
- package/src/lib/dto/render-template.dto.js.map +0 -1
- package/src/lib/dto/template-filter.dto.d.ts +0 -8
- package/src/lib/dto/template-filter.dto.js +0 -62
- package/src/lib/dto/template-filter.dto.js.map +0 -1
- package/src/lib/dto/template-layout-filter.dto.d.ts +0 -7
- package/src/lib/dto/template-layout-filter.dto.js +0 -61
- package/src/lib/dto/template-layout-filter.dto.js.map +0 -1
- package/src/lib/engines/language/html.engine.d.ts +0 -9
- package/src/lib/engines/language/html.engine.js +0 -79
- package/src/lib/engines/language/html.engine.js.map +0 -1
- package/src/lib/engines/language/index.js +0 -12
- package/src/lib/engines/language/index.js.map +0 -1
- package/src/lib/engines/language/markdown.engine.d.ts +0 -9
- package/src/lib/engines/language/markdown.engine.js +0 -26
- package/src/lib/engines/language/markdown.engine.js.map +0 -1
- package/src/lib/engines/language/mjml.engine.d.ts +0 -9
- package/src/lib/engines/language/mjml.engine.js +0 -76
- package/src/lib/engines/language/mjml.engine.js.map +0 -1
- package/src/lib/engines/language/text.engine.d.ts +0 -7
- package/src/lib/engines/language/text.engine.js +0 -16
- package/src/lib/engines/language/text.engine.js.map +0 -1
- package/src/lib/engines/language-engine.js +0 -7
- package/src/lib/engines/language-engine.js.map +0 -1
- package/src/lib/engines/template/ejs.engine.d.ts +0 -10
- package/src/lib/engines/template/ejs.engine.js +0 -66
- package/src/lib/engines/template/ejs.engine.js.map +0 -1
- package/src/lib/engines/template/handlebars.engine.d.ts +0 -10
- package/src/lib/engines/template/handlebars.engine.js +0 -67
- package/src/lib/engines/template/handlebars.engine.js.map +0 -1
- package/src/lib/engines/template/index.js +0 -12
- package/src/lib/engines/template/index.js.map +0 -1
- package/src/lib/engines/template/nunjucks.engine.d.ts +0 -16
- package/src/lib/engines/template/nunjucks.engine.js +0 -63
- package/src/lib/engines/template/nunjucks.engine.js.map +0 -1
- package/src/lib/engines/template/pug.engine.d.ts +0 -12
- package/src/lib/engines/template/pug.engine.js +0 -75
- package/src/lib/engines/template/pug.engine.js.map +0 -1
- package/src/lib/engines/template-engine.js +0 -7
- package/src/lib/engines/template-engine.js.map +0 -1
- package/src/lib/entities/template-layout.entity.d.ts +0 -20
- package/src/lib/entities/template-layout.entity.js +0 -121
- package/src/lib/entities/template-layout.entity.js.map +0 -1
- package/src/lib/entities/template.entity.d.ts +0 -21
- package/src/lib/entities/template.entity.js +0 -128
- package/src/lib/entities/template.entity.js.map +0 -1
- package/src/lib/errors/template.errors.d.ts +0 -19
- package/src/lib/errors/template.errors.js.map +0 -1
- package/src/lib/interfaces/module-config.interface.js +0 -3
- package/src/lib/interfaces/module-config.interface.js.map +0 -1
- package/src/lib/interfaces/template.types.d.ts +0 -22
- package/src/lib/interfaces/template.types.js +0 -25
- package/src/lib/interfaces/template.types.js.map +0 -1
- package/src/lib/nest-dynamic-templates.module.d.ts +0 -10
- package/src/lib/nest-dynamic-templates.module.js +0 -131
- package/src/lib/nest-dynamic-templates.module.js.map +0 -1
- package/src/lib/services/template-config.service.d.ts +0 -18
- package/src/lib/services/template-config.service.js +0 -66
- package/src/lib/services/template-config.service.js.map +0 -1
- package/src/lib/services/template-engine.registry.d.ts +0 -21
- package/src/lib/services/template-engine.registry.js +0 -94
- package/src/lib/services/template-engine.registry.js.map +0 -1
- package/src/lib/services/template-layout.service.d.ts +0 -24
- package/src/lib/services/template-layout.service.js +0 -301
- package/src/lib/services/template-layout.service.js.map +0 -1
- package/src/lib/services/template.service.d.ts +0 -26
- package/src/lib/services/template.service.js +0 -366
- package/src/lib/services/template.service.js.map +0 -1
- package/src/test/helpers.d.ts +0 -4
- package/src/test/helpers.js.map +0 -1
- package/src/test/test-database.config.d.ts +0 -4
- package/src/test/test-database.config.js +0 -24
- package/src/test/test-database.config.js.map +0 -1
- package/src/test/test.setup.d.ts +0 -3
- package/src/test/test.setup.js +0 -29
- package/src/test/test.setup.js.map +0 -1
- package/tsconfig.tsbuildinfo +0 -1
- /package/src/lib/engines/language/{index.d.ts → index.ts} +0 -0
- /package/src/lib/engines/template/{index.d.ts → index.ts} +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { IsString, IsEnum, IsObject, IsOptional, IsNotEmpty } from 'class-validator';
|
|
2
|
+
import { TemplateTypeEnum } from '../interfaces/template.types';
|
|
3
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class RenderTemplateLayoutDto {
|
|
7
|
+
|
|
8
|
+
@ApiPropertyOptional({
|
|
9
|
+
type: String,
|
|
10
|
+
default: 'en',
|
|
11
|
+
description: 'The locale code, i.e en, fr, es, etc.'
|
|
12
|
+
})
|
|
13
|
+
@IsString()
|
|
14
|
+
@IsOptional()
|
|
15
|
+
locale?: string = 'en';
|
|
16
|
+
|
|
17
|
+
@ApiProperty({ type: String })
|
|
18
|
+
@IsString()
|
|
19
|
+
@IsNotEmpty()
|
|
20
|
+
name: string;
|
|
21
|
+
|
|
22
|
+
@ApiProperty({
|
|
23
|
+
type: String,
|
|
24
|
+
default: 'system',
|
|
25
|
+
description: 'The scope of the template, i.e user, organization, etc.'
|
|
26
|
+
})
|
|
27
|
+
@IsString()
|
|
28
|
+
scope: string;
|
|
29
|
+
|
|
30
|
+
@ApiPropertyOptional({
|
|
31
|
+
type: String,
|
|
32
|
+
default: null,
|
|
33
|
+
description: 'The scope id of the template, i.e user id, organization id, etc.'
|
|
34
|
+
})
|
|
35
|
+
@IsString()
|
|
36
|
+
@IsOptional()
|
|
37
|
+
scopeId?: string;
|
|
38
|
+
|
|
39
|
+
@ApiPropertyOptional({
|
|
40
|
+
type: String,
|
|
41
|
+
nullable: true,
|
|
42
|
+
description: 'The context of the template'
|
|
43
|
+
})
|
|
44
|
+
@IsObject()
|
|
45
|
+
@IsOptional()
|
|
46
|
+
context?: Record<string, any>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
export class RenderTemplateLayoutOutput {
|
|
51
|
+
@ApiProperty({ type: String })
|
|
52
|
+
@IsString()
|
|
53
|
+
@IsNotEmpty()
|
|
54
|
+
content: string;
|
|
55
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { IsString, IsEnum, IsObject, IsOptional, IsNotEmpty, Matches, ValidateIf } from 'class-validator';
|
|
2
|
+
import { TemplateLanguageEnum, TemplateTypeEnum } from '../interfaces/template.types';
|
|
3
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class RenderTemplateDto {
|
|
7
|
+
|
|
8
|
+
@ApiPropertyOptional({
|
|
9
|
+
type: String,
|
|
10
|
+
default: 'en',
|
|
11
|
+
description: 'The locale code, i.e en, fr, es, etc.'
|
|
12
|
+
})
|
|
13
|
+
@IsString()
|
|
14
|
+
@IsOptional()
|
|
15
|
+
locale?: string = 'en';
|
|
16
|
+
|
|
17
|
+
@ApiProperty({ type: String })
|
|
18
|
+
@IsString()
|
|
19
|
+
@IsNotEmpty()
|
|
20
|
+
@ValidateIf((object, value) => !object?.content)
|
|
21
|
+
name?: string;
|
|
22
|
+
|
|
23
|
+
@ApiProperty({ type: String })
|
|
24
|
+
@IsString()
|
|
25
|
+
@IsNotEmpty()
|
|
26
|
+
@ValidateIf((object, value) => !object?.name)
|
|
27
|
+
content?: string;
|
|
28
|
+
|
|
29
|
+
@ApiProperty({ enum: TemplateLanguageEnum, type: String })
|
|
30
|
+
@IsEnum(TemplateLanguageEnum)
|
|
31
|
+
@IsNotEmpty()
|
|
32
|
+
@ValidateIf((object, value) => !object?.name)
|
|
33
|
+
language?: TemplateLanguageEnum;
|
|
34
|
+
|
|
35
|
+
@ApiPropertyOptional({
|
|
36
|
+
type: String,
|
|
37
|
+
default: 'system',
|
|
38
|
+
description: 'The scope of the template, i.e user, organization, etc.'
|
|
39
|
+
})
|
|
40
|
+
@IsString()
|
|
41
|
+
@IsOptional()
|
|
42
|
+
scope?: string = 'system';
|
|
43
|
+
|
|
44
|
+
@ApiPropertyOptional({
|
|
45
|
+
type: String,
|
|
46
|
+
default: null,
|
|
47
|
+
description: 'The scope id of the template, i.e user id, organization id, etc.'
|
|
48
|
+
})
|
|
49
|
+
@IsString()
|
|
50
|
+
@IsOptional()
|
|
51
|
+
scopeId?: string;
|
|
52
|
+
|
|
53
|
+
@ApiPropertyOptional({
|
|
54
|
+
type: String,
|
|
55
|
+
nullable: true,
|
|
56
|
+
description: 'The context of the template'
|
|
57
|
+
})
|
|
58
|
+
@IsObject()
|
|
59
|
+
@IsOptional()
|
|
60
|
+
context?: Record<string, any>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
export class RenderTemplateOutputDTO {
|
|
65
|
+
@ApiProperty({ type: String })
|
|
66
|
+
@IsString()
|
|
67
|
+
@IsNotEmpty()
|
|
68
|
+
content: string;
|
|
69
|
+
|
|
70
|
+
@ApiProperty({ type: String })
|
|
71
|
+
@IsString()
|
|
72
|
+
@IsNotEmpty()
|
|
73
|
+
subject: string;
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { IsEnum, IsOptional, IsString, IsArray } from 'class-validator';
|
|
2
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
3
|
+
import { TemplateTypeEnum } from '../interfaces/template.types';
|
|
4
|
+
|
|
5
|
+
export class TemplateFilterDto {
|
|
6
|
+
@ApiProperty({
|
|
7
|
+
type: String,
|
|
8
|
+
required: false,
|
|
9
|
+
description: 'Filter by scope (e.g., system, tenant, organization)'
|
|
10
|
+
})
|
|
11
|
+
@IsString()
|
|
12
|
+
@IsOptional()
|
|
13
|
+
scope?: string;
|
|
14
|
+
|
|
15
|
+
@ApiProperty({
|
|
16
|
+
type: String,
|
|
17
|
+
required: false,
|
|
18
|
+
description: 'Filter by scope ID (e.g., tenant ID, organization ID)'
|
|
19
|
+
})
|
|
20
|
+
@IsString()
|
|
21
|
+
@IsOptional()
|
|
22
|
+
scopeId?: string;
|
|
23
|
+
|
|
24
|
+
@ApiProperty({
|
|
25
|
+
enum: TemplateTypeEnum,
|
|
26
|
+
required: false,
|
|
27
|
+
description: 'Filter by template type'
|
|
28
|
+
})
|
|
29
|
+
@IsEnum(TemplateTypeEnum)
|
|
30
|
+
@IsOptional()
|
|
31
|
+
type?: TemplateTypeEnum;
|
|
32
|
+
|
|
33
|
+
@ApiProperty({
|
|
34
|
+
type: String,
|
|
35
|
+
required: false,
|
|
36
|
+
description: 'Filter by locale (e.g., en, fr, es)'
|
|
37
|
+
})
|
|
38
|
+
@IsString()
|
|
39
|
+
@IsOptional()
|
|
40
|
+
locale?: string;
|
|
41
|
+
|
|
42
|
+
@ApiProperty({
|
|
43
|
+
type: [String],
|
|
44
|
+
required: false,
|
|
45
|
+
description: 'Exclude templates with these names'
|
|
46
|
+
})
|
|
47
|
+
@IsArray()
|
|
48
|
+
@IsString({ each: true })
|
|
49
|
+
@IsOptional()
|
|
50
|
+
excludeNames?: string[];
|
|
51
|
+
|
|
52
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { IsOptional, IsString, IsArray } from 'class-validator';
|
|
2
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
3
|
+
|
|
4
|
+
export class TemplateLayoutFilterDto {
|
|
5
|
+
@ApiProperty({
|
|
6
|
+
type: String,
|
|
7
|
+
required: false,
|
|
8
|
+
description: 'Filter by scope (e.g., system, tenant, organization)'
|
|
9
|
+
})
|
|
10
|
+
@IsString()
|
|
11
|
+
@IsOptional()
|
|
12
|
+
scope?: string;
|
|
13
|
+
|
|
14
|
+
@ApiProperty({
|
|
15
|
+
type: String,
|
|
16
|
+
required: false,
|
|
17
|
+
description: 'Filter by scope ID (e.g., tenant ID, organization ID)'
|
|
18
|
+
})
|
|
19
|
+
@IsString()
|
|
20
|
+
@IsOptional()
|
|
21
|
+
scopeId?: string;
|
|
22
|
+
|
|
23
|
+
@ApiProperty({
|
|
24
|
+
type: String,
|
|
25
|
+
required: false,
|
|
26
|
+
description: 'Filter by template type'
|
|
27
|
+
})
|
|
28
|
+
@IsString()
|
|
29
|
+
@IsOptional()
|
|
30
|
+
type?: string;
|
|
31
|
+
|
|
32
|
+
@ApiProperty({
|
|
33
|
+
type: String,
|
|
34
|
+
required: false,
|
|
35
|
+
description: 'Filter by locale (e.g., en, fr, es)'
|
|
36
|
+
})
|
|
37
|
+
@IsString()
|
|
38
|
+
@IsOptional()
|
|
39
|
+
locale?: string;
|
|
40
|
+
|
|
41
|
+
@ApiProperty({
|
|
42
|
+
type: [String],
|
|
43
|
+
required: false,
|
|
44
|
+
description: 'Exclude templates with these names'
|
|
45
|
+
})
|
|
46
|
+
@IsArray()
|
|
47
|
+
@IsString({ each: true })
|
|
48
|
+
@IsOptional()
|
|
49
|
+
excludeNames?: string[];
|
|
50
|
+
|
|
51
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TemplateLanguageEnum } from '../../interfaces/template.types';
|
|
2
|
+
import { LanguageEngine } from '../language-engine';
|
|
3
|
+
|
|
4
|
+
export class HtmlEngine extends LanguageEngine {
|
|
5
|
+
static override engineName = TemplateLanguageEnum.HTML;
|
|
6
|
+
|
|
7
|
+
private options: any; // Using any for now since htmlparser2 types are causing issues
|
|
8
|
+
|
|
9
|
+
constructor(options?: any) {
|
|
10
|
+
super();
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async render(content: string): Promise<string> {
|
|
15
|
+
try {
|
|
16
|
+
// For HTML, we just return the content as is
|
|
17
|
+
// But we validate it first to ensure it's valid HTML
|
|
18
|
+
const isValid = await this.validate(content);
|
|
19
|
+
if (!isValid) {
|
|
20
|
+
throw new Error('Invalid HTML content');
|
|
21
|
+
}
|
|
22
|
+
return content;
|
|
23
|
+
} catch (error: any) {
|
|
24
|
+
throw new Error(`Failed to render HTML: ${error?.message || 'Unknown error'}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async validate(content: string): Promise<boolean> {
|
|
29
|
+
try {
|
|
30
|
+
const { Parser } = await import('htmlparser2');
|
|
31
|
+
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
const parser = new Parser({
|
|
34
|
+
onerror: () => {
|
|
35
|
+
resolve(false);
|
|
36
|
+
},
|
|
37
|
+
onend: () => {
|
|
38
|
+
resolve(true);
|
|
39
|
+
}
|
|
40
|
+
}, this.options);
|
|
41
|
+
|
|
42
|
+
parser.write(content);
|
|
43
|
+
parser.end();
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { TemplateLanguageEnum } from '../../interfaces/template.types';
|
|
3
|
+
import { LanguageEngine } from '../language-engine';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class MarkdownEngine extends LanguageEngine {
|
|
7
|
+
static override engineName = TemplateLanguageEnum.MARKDOWN;
|
|
8
|
+
|
|
9
|
+
private options: any; // Using any for now since marked types are causing issues
|
|
10
|
+
|
|
11
|
+
constructor(options?: any) {
|
|
12
|
+
super();
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async render(content: string): Promise<string> {
|
|
17
|
+
// try {
|
|
18
|
+
// const { marked } = await import('marked');
|
|
19
|
+
// return marked(content, this.options);
|
|
20
|
+
// } catch (error: any) {
|
|
21
|
+
// throw new Error(`Failed to render Markdown: ${error?.message || 'Unknown error'}`);
|
|
22
|
+
// }
|
|
23
|
+
return content;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async validate(content: string): Promise<boolean> {
|
|
27
|
+
// try {
|
|
28
|
+
// const { marked } = await import('marked');
|
|
29
|
+
// // Basic Markdown validation - check for common syntax errors
|
|
30
|
+
// marked(content, this.options);
|
|
31
|
+
// return true;
|
|
32
|
+
// } catch (error) {
|
|
33
|
+
// return false;
|
|
34
|
+
// }
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { TemplateLanguageEnum } from '../../interfaces/template.types';
|
|
2
|
+
import { LanguageEngine } from '../language-engine';
|
|
3
|
+
|
|
4
|
+
export class MjmlEngine extends LanguageEngine {
|
|
5
|
+
|
|
6
|
+
static override engineName = TemplateLanguageEnum.MJML;
|
|
7
|
+
|
|
8
|
+
private readonly options: any = {};
|
|
9
|
+
|
|
10
|
+
constructor(options: any = {}) {
|
|
11
|
+
super();
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async render(content: string): Promise<string> {
|
|
16
|
+
try {
|
|
17
|
+
const mjml2html = (await import('mjml')).default;
|
|
18
|
+
const result = mjml2html(content, {
|
|
19
|
+
...this.options,
|
|
20
|
+
validationLevel: 'strict',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (result.errors && result.errors.length > 0) {
|
|
24
|
+
throw new Error(`MJML validation errors: ${result.errors.join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result.html;
|
|
28
|
+
} catch (error: any) {
|
|
29
|
+
throw new Error(`MJML rendering error: ${error?.message || 'Unknown error'}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async validate(content: string): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
const mjml2html = (await import('mjml')).default;
|
|
36
|
+
const result = mjml2html(content, {
|
|
37
|
+
validationLevel: 'strict',
|
|
38
|
+
});
|
|
39
|
+
return result.errors.length === 0;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TemplateLanguageEnum } from '../../interfaces/template.types';
|
|
2
|
+
import { LanguageEngine } from '../language-engine';
|
|
3
|
+
|
|
4
|
+
export class TextEngine extends LanguageEngine {
|
|
5
|
+
|
|
6
|
+
static override engineName = TemplateLanguageEnum.TEXT;
|
|
7
|
+
|
|
8
|
+
async render(content: string): Promise<string> {
|
|
9
|
+
return content;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async validate(content: string): Promise<boolean> {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
export abstract class LanguageEngine {
|
|
3
|
+
|
|
2
4
|
static engineName: string;
|
|
5
|
+
|
|
3
6
|
abstract render(content: string, data?: Record<string, any>): Promise<string>;
|
|
7
|
+
|
|
4
8
|
abstract validate(content: string): Promise<boolean>;
|
|
9
|
+
|
|
10
|
+
|
|
5
11
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TemplateEngine } from '../template-engine';
|
|
2
|
+
import { TemplateEngineEnum } from '../../interfaces/template.types';
|
|
3
|
+
import { EngineOptions } from '../../interfaces/module-config.interface';
|
|
4
|
+
export class EjsEngine extends TemplateEngine {
|
|
5
|
+
|
|
6
|
+
static override engineName = TemplateEngineEnum.EJS;
|
|
7
|
+
|
|
8
|
+
private options: ejs.Options;
|
|
9
|
+
|
|
10
|
+
constructor(options?: EngineOptions<ejs.Options>) {
|
|
11
|
+
super();
|
|
12
|
+
this.options = options || {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async render(content: string, data?: Record<string, any>): Promise<string> {
|
|
16
|
+
try {
|
|
17
|
+
const ejs = await import('ejs');
|
|
18
|
+
return ejs.render(content, data, this.options);
|
|
19
|
+
} catch (error: any) {
|
|
20
|
+
throw new Error(`Failed to render EJS template: ${error?.message || 'Unknown error'}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async validate(content: string): Promise<boolean> {
|
|
25
|
+
try {
|
|
26
|
+
const ejs = await import('ejs');
|
|
27
|
+
ejs.compile(content, this.options);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TemplateEngineEnum } from '../../interfaces/template.types';
|
|
2
|
+
import { TemplateEngine } from '../template-engine';
|
|
3
|
+
import { EngineOptions } from '../../interfaces/module-config.interface';
|
|
4
|
+
|
|
5
|
+
export class HandlebarsEngine extends TemplateEngine {
|
|
6
|
+
|
|
7
|
+
static override engineName = TemplateEngineEnum.HANDLEBARS;
|
|
8
|
+
|
|
9
|
+
private options: Handlebars.ParseOptions;
|
|
10
|
+
|
|
11
|
+
constructor(options?: EngineOptions<Handlebars.ParseOptions>) {
|
|
12
|
+
super();
|
|
13
|
+
this.options = options || {};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async render(content: string, data?: Record<string, any>): Promise<string> {
|
|
17
|
+
try {
|
|
18
|
+
const Handlebars = await import('handlebars');
|
|
19
|
+
const template = Handlebars.compile(content, this.options);
|
|
20
|
+
return template(data);
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
throw new Error(`Failed to render Handlebars template: ${error?.message || 'Unknown error'}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async validate(content: string): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
const Handlebars = await import('handlebars');
|
|
29
|
+
Handlebars.precompile(content, this.options);
|
|
30
|
+
return true;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { omit } from 'lodash';
|
|
2
|
+
import { TemplateEngineEnum } from '../../interfaces/template.types';
|
|
3
|
+
import { TemplateEngine } from '../template-engine';
|
|
4
|
+
import type { Environment, ConfigureOptions } from 'nunjucks';
|
|
5
|
+
import { CustomEngineOptions, EngineOptions } from '../../interfaces/module-config.interface';
|
|
6
|
+
|
|
7
|
+
export class NunjucksEngine extends TemplateEngine {
|
|
8
|
+
|
|
9
|
+
static override engineName = TemplateEngineEnum.NUNJUCKS;
|
|
10
|
+
|
|
11
|
+
private env: Environment;
|
|
12
|
+
|
|
13
|
+
constructor(private options?: EngineOptions<ConfigureOptions>, private customOptions?: CustomEngineOptions) {
|
|
14
|
+
super();
|
|
15
|
+
this.initNunjucks();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
initNunjucks() {
|
|
19
|
+
const nunjucks = require('nunjucks');
|
|
20
|
+
|
|
21
|
+
const nunjucksOptions = omit(this.options, 'filters');
|
|
22
|
+
|
|
23
|
+
this.env = nunjucks.configure({
|
|
24
|
+
autoescape: true,
|
|
25
|
+
throwOnUndefined: true,
|
|
26
|
+
...nunjucksOptions,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Register filters immediately
|
|
30
|
+
if (this.customOptions?.filters) {
|
|
31
|
+
Object.entries(this.customOptions.filters).forEach(([name, filter]) => {
|
|
32
|
+
this.registerFilter(name, filter);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async render(content: string, data?: Record<string, any>): Promise<string> {
|
|
38
|
+
try {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
this.env.renderString(content, data || {}, (err: any, result: any) => {
|
|
41
|
+
if (err) {
|
|
42
|
+
reject(err);
|
|
43
|
+
} else {
|
|
44
|
+
resolve(result);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
throw new Error(`Failed to render Nunjucks template: ${error?.message || 'Unknown error'}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async validate(content: string): Promise<boolean> {
|
|
54
|
+
try {
|
|
55
|
+
// Use renderString with empty data to validate the template
|
|
56
|
+
await this.render(content, {});
|
|
57
|
+
return true;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
registerFilter(name: string, filter: (...args: any[]) => any) {
|
|
64
|
+
this.env.addFilter(name, filter);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
registerGlobal(name: string, global: (...args: any[]) => any) {
|
|
68
|
+
this.env.addGlobal(name, global);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Options } from 'pug';
|
|
2
|
+
import { TemplateEngineEnum } from '../../interfaces/template.types';
|
|
3
|
+
import { TemplateEngine } from '../template-engine';
|
|
4
|
+
import { CustomEngineOptions, EngineOptions } from '../../interfaces/module-config.interface';
|
|
5
|
+
|
|
6
|
+
export class PugEngine extends TemplateEngine {
|
|
7
|
+
|
|
8
|
+
static override engineName = TemplateEngineEnum.PUG;
|
|
9
|
+
|
|
10
|
+
private options: Options;
|
|
11
|
+
|
|
12
|
+
constructor(options?: EngineOptions<Options>, private customOptions?: CustomEngineOptions) {
|
|
13
|
+
super();
|
|
14
|
+
this.options = options || {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async render(content: string, data: any): Promise<string> {
|
|
18
|
+
try {
|
|
19
|
+
const pug = await import('pug');
|
|
20
|
+
const template = pug.compile(content, {
|
|
21
|
+
...this.options,
|
|
22
|
+
filters: this.customOptions?.filters
|
|
23
|
+
});
|
|
24
|
+
return template({
|
|
25
|
+
...this.options.filters,
|
|
26
|
+
...data,
|
|
27
|
+
...this.customOptions?.filters
|
|
28
|
+
});
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
throw new Error(`Failed to render Pug template: ${error?.message || 'Unknown error'}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async validate(content: string): Promise<boolean> {
|
|
35
|
+
try {
|
|
36
|
+
const pug = await import('pug');
|
|
37
|
+
pug.compile(content, this.options);
|
|
38
|
+
return true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
export abstract class TemplateEngine {
|
|
3
|
+
|
|
2
4
|
static engineName: string;
|
|
5
|
+
|
|
3
6
|
abstract render(content: string, data?: Record<string, any>): Promise<string>;
|
|
7
|
+
|
|
4
8
|
abstract validate(content: string): Promise<boolean>;
|
|
9
|
+
|
|
5
10
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, Index, BaseEntity } from 'typeorm';
|
|
2
|
+
import { IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator';
|
|
3
|
+
import { TemplateEngineEnum, TemplateTypeEnum, TemplateLanguageEnum } from '../interfaces/template.types';
|
|
4
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@Entity('nest_dynamic_template_layouts')
|
|
8
|
+
@Index(['name', 'scope', 'scopeId', 'locale'], { unique: true })
|
|
9
|
+
export class NestDynamicTemplateLayout extends BaseEntity {
|
|
10
|
+
|
|
11
|
+
@ApiProperty({ type: String, format: 'uuid', readOnly: true })
|
|
12
|
+
@PrimaryGeneratedColumn('uuid')
|
|
13
|
+
id: string;
|
|
14
|
+
|
|
15
|
+
@ApiProperty({ type: String })
|
|
16
|
+
@IsString()
|
|
17
|
+
@IsNotEmpty()
|
|
18
|
+
@Matches(/^[a-z0-9\-_]+$/, { message: 'Invalid template name format' })
|
|
19
|
+
@Column()
|
|
20
|
+
name: string;
|
|
21
|
+
|
|
22
|
+
@ApiProperty({ type: String, nullable: true })
|
|
23
|
+
@IsString()
|
|
24
|
+
@IsOptional()
|
|
25
|
+
@Column({ nullable: true })
|
|
26
|
+
displayName?: string;
|
|
27
|
+
|
|
28
|
+
@ApiProperty({ type: String, nullable: true })
|
|
29
|
+
@IsString()
|
|
30
|
+
@IsOptional()
|
|
31
|
+
@Column({ type: 'text', nullable: true })
|
|
32
|
+
description?: string;
|
|
33
|
+
|
|
34
|
+
@ApiProperty({ type: String })
|
|
35
|
+
@IsString()
|
|
36
|
+
@IsOptional()
|
|
37
|
+
@Column({ type: 'text' })
|
|
38
|
+
type?: string;
|
|
39
|
+
|
|
40
|
+
@ApiProperty({ enum: TemplateEngineEnum, type: String })
|
|
41
|
+
@IsEnum(TemplateEngineEnum)
|
|
42
|
+
@Column({
|
|
43
|
+
type: 'text',
|
|
44
|
+
enum: TemplateEngineEnum,
|
|
45
|
+
default: TemplateEngineEnum.NUNJUCKS
|
|
46
|
+
})
|
|
47
|
+
engine: TemplateEngineEnum;
|
|
48
|
+
|
|
49
|
+
@ApiProperty({ enum: TemplateLanguageEnum, type: String, nullable: true })
|
|
50
|
+
@IsEnum(TemplateLanguageEnum)
|
|
51
|
+
@IsOptional()
|
|
52
|
+
@Column({ type: 'text' })
|
|
53
|
+
language?: TemplateLanguageEnum;
|
|
54
|
+
|
|
55
|
+
@ApiProperty({ type: String })
|
|
56
|
+
@IsString()
|
|
57
|
+
@IsOptional()
|
|
58
|
+
@Column('text')
|
|
59
|
+
content: string;
|
|
60
|
+
|
|
61
|
+
@ApiProperty({ type: String, nullable: true })
|
|
62
|
+
@IsString()
|
|
63
|
+
@IsOptional()
|
|
64
|
+
@Column({ nullable: true })
|
|
65
|
+
templateLayoutName?: string;
|
|
66
|
+
|
|
67
|
+
@ApiProperty({ type: String, nullable: true })
|
|
68
|
+
@IsString()
|
|
69
|
+
@Column({ nullable: true, default: 'system' })
|
|
70
|
+
scope?: string;
|
|
71
|
+
|
|
72
|
+
@ApiProperty({ type: String, nullable: true })
|
|
73
|
+
@IsString()
|
|
74
|
+
@Column({ nullable: true })
|
|
75
|
+
scopeId?: string;
|
|
76
|
+
|
|
77
|
+
@ApiProperty({ type: String, nullable: true })
|
|
78
|
+
@IsString()
|
|
79
|
+
@Column({ nullable: true, default: 'en' })
|
|
80
|
+
locale?: string;
|
|
81
|
+
|
|
82
|
+
@ApiProperty({ type: Object, nullable: true })
|
|
83
|
+
@IsOptional()
|
|
84
|
+
@Column('simple-json', { nullable: true })
|
|
85
|
+
previewContext?: Record<string, any>;
|
|
86
|
+
|
|
87
|
+
@ApiProperty({ type: Boolean })
|
|
88
|
+
@IsBoolean()
|
|
89
|
+
@Column({ default: true })
|
|
90
|
+
isActive: boolean;
|
|
91
|
+
|
|
92
|
+
@ApiProperty({ type: Date, readOnly: true })
|
|
93
|
+
@CreateDateColumn()
|
|
94
|
+
createdAt: Date;
|
|
95
|
+
|
|
96
|
+
@ApiProperty({ type: Date, readOnly: true })
|
|
97
|
+
@UpdateDateColumn()
|
|
98
|
+
updatedAt: Date;
|
|
99
|
+
}
|