@ackplus/nest-dynamic-templates 1.1.2 → 1.1.6

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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +25 -0
  3. package/src/lib/constant.js +5 -0
  4. package/src/lib/dto/create-template-layout.dto.js +86 -0
  5. package/src/lib/dto/create-template.dto.js +103 -0
  6. package/src/lib/dto/render-content-template-layout.dto.js +40 -0
  7. package/src/lib/dto/render-content-template.dto.js +46 -0
  8. package/src/lib/dto/render-template-layout.dto.js +66 -0
  9. package/src/lib/dto/render-template.dto.js +90 -0
  10. package/src/lib/dto/template-filter.dto.js +61 -0
  11. package/src/lib/dto/template-layout-filter.dto.js +60 -0
  12. package/src/lib/engines/language/html.engine.js +80 -0
  13. package/src/lib/engines/language/index.js +11 -0
  14. package/src/lib/engines/language/markdown.engine.js +39 -0
  15. package/src/lib/engines/language/mjml.engine.js +75 -0
  16. package/src/lib/engines/language/text.engine.js +15 -0
  17. package/src/lib/engines/language-engine.js +6 -0
  18. package/src/lib/engines/template/ejs.engine.js +65 -0
  19. package/src/lib/engines/template/handlebars.engine.js +66 -0
  20. package/src/lib/engines/template/index.js +11 -0
  21. package/src/lib/engines/template/nunjucks.engine.js +64 -0
  22. package/src/lib/engines/template/pug.engine.js +74 -0
  23. package/src/lib/engines/template-engine.js +6 -0
  24. package/src/lib/entities/template-layout.entity.js +120 -0
  25. package/src/lib/entities/template.entity.js +127 -0
  26. package/src/lib/errors/template.errors.js +75 -0
  27. package/src/lib/interfaces/module-config.interface.js +2 -0
  28. package/src/lib/interfaces/template.types.js +24 -0
  29. package/src/lib/nest-dynamic-templates.module.js +131 -0
  30. package/src/lib/services/template-config.service.js +101 -0
  31. package/src/lib/services/template-engine.registry.js +93 -0
  32. package/src/lib/services/template-layout.service.js +343 -0
  33. package/src/lib/services/template.service.js +414 -0
  34. package/src/test/helpers.js +33 -0
  35. package/src/test/test-database.config.js +23 -0
  36. package/src/test/test.setup.js +30 -0
@@ -0,0 +1,343 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TemplateLayoutService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const typeorm_1 = require("@nestjs/typeorm");
7
+ const typeorm_2 = require("typeorm");
8
+ const template_layout_entity_1 = require("../entities/template-layout.entity");
9
+ const template_engine_registry_1 = require("./template-engine.registry");
10
+ const template_errors_1 = require("../errors/template.errors");
11
+ let TemplateLayoutService = class TemplateLayoutService {
12
+ constructor(layoutRepository, engineRegistry) {
13
+ this.layoutRepository = layoutRepository;
14
+ this.engineRegistry = engineRegistry;
15
+ }
16
+ async render(renderDto) {
17
+ const { name, scope, scopeId, locale, context } = renderDto;
18
+ try {
19
+ // Find template with fallback
20
+ const templateLayout = await this.findTemplateLayout(name, scope, scopeId, locale);
21
+ if (!templateLayout) {
22
+ throw new common_1.NotFoundException(`Template layout not found: ${name} in scope ${scope || 'system'}`);
23
+ }
24
+ // Validate content exists
25
+ if (!templateLayout.content) {
26
+ throw new common_1.BadRequestException(`Template layout '${name}' has no content to render`);
27
+ }
28
+ let content = templateLayout.content;
29
+ // Render content by template engine
30
+ if (templateLayout.engine) {
31
+ try {
32
+ content = await this.renderEngine(templateLayout.engine, content, context || {});
33
+ }
34
+ catch (error) {
35
+ throw new template_errors_1.TemplateEngineError(templateLayout.engine, error);
36
+ }
37
+ }
38
+ // If template has language format, process with language engine
39
+ if (templateLayout.language) {
40
+ try {
41
+ content = await this.renderLanguage(templateLayout.language, content, context || {});
42
+ }
43
+ catch (error) {
44
+ throw new template_errors_1.TemplateLanguageError(templateLayout.language, error);
45
+ }
46
+ }
47
+ return {
48
+ content
49
+ };
50
+ }
51
+ catch (error) {
52
+ // Re-throw known template errors
53
+ if (error instanceof template_errors_1.TemplateEngineError ||
54
+ error instanceof template_errors_1.TemplateLanguageError ||
55
+ error instanceof common_1.NotFoundException ||
56
+ error instanceof common_1.BadRequestException) {
57
+ throw error;
58
+ }
59
+ // Wrap unknown errors
60
+ throw new template_errors_1.TemplateRenderError('template layout rendering', error, name);
61
+ }
62
+ }
63
+ async renderContent(input) {
64
+ const { content, language, engine, context } = input;
65
+ try {
66
+ if (!content) {
67
+ throw new common_1.BadRequestException('Content is required for template layout rendering');
68
+ }
69
+ let renderContent = content;
70
+ // Render content by template engine
71
+ if (engine) {
72
+ try {
73
+ renderContent = await this.renderEngine(engine, content, context || {});
74
+ }
75
+ catch (error) {
76
+ throw new template_errors_1.TemplateEngineError(engine, error);
77
+ }
78
+ }
79
+ // Render content by language engine
80
+ if (language) {
81
+ try {
82
+ renderContent = await this.renderLanguage(language, renderContent, context || {});
83
+ }
84
+ catch (error) {
85
+ throw new template_errors_1.TemplateLanguageError(language, error);
86
+ }
87
+ }
88
+ return renderContent;
89
+ }
90
+ catch (error) {
91
+ // Re-throw known template errors
92
+ if (error instanceof template_errors_1.TemplateEngineError ||
93
+ error instanceof template_errors_1.TemplateLanguageError ||
94
+ error instanceof common_1.BadRequestException) {
95
+ throw error;
96
+ }
97
+ // Wrap unknown errors
98
+ throw new template_errors_1.TemplateRenderError('template layout content rendering', error);
99
+ }
100
+ }
101
+ async renderLanguage(language, content, context) {
102
+ try {
103
+ if (!content) {
104
+ throw new common_1.BadRequestException('Content is required for language rendering');
105
+ }
106
+ const languageEngine = this.engineRegistry.getLanguageEngine(language);
107
+ if (!languageEngine) {
108
+ throw new common_1.BadRequestException(`Language engine not found for: ${language}`);
109
+ }
110
+ return await languageEngine.render(content, context);
111
+ }
112
+ catch (error) {
113
+ if (error instanceof common_1.BadRequestException) {
114
+ throw error;
115
+ }
116
+ throw new template_errors_1.TemplateLanguageError(language, error);
117
+ }
118
+ }
119
+ async renderEngine(engine, content, context) {
120
+ try {
121
+ if (!content) {
122
+ throw new common_1.BadRequestException('Content is required for engine rendering');
123
+ }
124
+ const templateEngine = this.engineRegistry.getTemplateEngine(engine);
125
+ if (!templateEngine) {
126
+ throw new common_1.BadRequestException(`Template engine not found for: ${engine}`);
127
+ }
128
+ return await templateEngine.render(content, context);
129
+ }
130
+ catch (error) {
131
+ if (error instanceof common_1.BadRequestException) {
132
+ throw error;
133
+ }
134
+ throw new template_errors_1.TemplateEngineError(engine, error);
135
+ }
136
+ }
137
+ /**
138
+ * Get all templates, with scoped templates taking precedence over system templates
139
+ */
140
+ async getTemplateLayouts(filter = {}) {
141
+ const { scope, scopeId, type, locale, excludeNames = [], } = filter;
142
+ // Build the where clause
143
+ const where = {};
144
+ if (type)
145
+ where.type = type;
146
+ if (locale)
147
+ where.locale = locale;
148
+ if (excludeNames.length > 0)
149
+ where.name = (0, typeorm_2.Not)((0, typeorm_2.In)(excludeNames));
150
+ const systemTemplates = await this.layoutRepository.find({
151
+ where: {
152
+ ...where,
153
+ scope: 'system',
154
+ scopeId: (0, typeorm_2.IsNull)(),
155
+ },
156
+ });
157
+ if (scope === 'system') {
158
+ return systemTemplates;
159
+ }
160
+ // First get all templates matching the filters
161
+ const templates = await this.layoutRepository.find({
162
+ where: {
163
+ ...where,
164
+ scope: (0, typeorm_2.Equal)(scope),
165
+ scopeId: scopeId,
166
+ },
167
+ order: {
168
+ createdAt: 'DESC',
169
+ },
170
+ });
171
+ // Create a map to store unique templates by name+type
172
+ const templateMap = new Map();
173
+ for (const template of systemTemplates) {
174
+ const key = `${template.type}/${template.name}/${template.locale}`;
175
+ templateMap.set(key, template);
176
+ }
177
+ for (const template of templates) {
178
+ const key = `${template.type}/${template.name}/${template.locale}`;
179
+ templateMap.set(key, template);
180
+ }
181
+ // Convert map values back to array
182
+ return Array.from(templateMap.values());
183
+ }
184
+ async getTemplateLayoutById(id) {
185
+ return this.layoutRepository.findOne({
186
+ where: { id },
187
+ });
188
+ }
189
+ async findTemplateLayout(name, scope, scopeId, locale) {
190
+ // Try to find template in the following order:
191
+ // 1. Scoped template with locale
192
+ // 2. Scoped template without locale
193
+ // 3. System template with locale
194
+ // 4. System template without locale
195
+ const locales = (locale ? [locale, 'en'] : ['en']).filter(Boolean);
196
+ // First try to find in the specified scope
197
+ for (const currentLocale of locales) {
198
+ const template = await this.layoutRepository.findOne({
199
+ where: {
200
+ name,
201
+ scope,
202
+ scopeId: scope === 'system' ? (0, typeorm_2.IsNull)() : (0, typeorm_2.Equal)(scopeId),
203
+ locale: currentLocale,
204
+ }
205
+ });
206
+ if (template) {
207
+ return template;
208
+ }
209
+ }
210
+ // If not found and not already in system scope, try system scope
211
+ if (scope !== 'system') {
212
+ for (const currentLocale of locales) {
213
+ const template = await this.layoutRepository.findOne({
214
+ where: {
215
+ name,
216
+ scope: 'system',
217
+ scopeId: (0, typeorm_2.IsNull)(),
218
+ locale: currentLocale,
219
+ }
220
+ });
221
+ if (template) {
222
+ return template;
223
+ }
224
+ }
225
+ }
226
+ return null;
227
+ }
228
+ /**
229
+ * Create a system template. Only system templates can be created directly.
230
+ */
231
+ async createTemplateLayout(data) {
232
+ // Ensure this is a system template
233
+ if (data.scope !== 'system') {
234
+ throw new common_1.ForbiddenException('Only system templates can be created directly');
235
+ }
236
+ // Check if template already exists
237
+ const existingTemplate = await this.layoutRepository.findOne({
238
+ where: {
239
+ name: data.name,
240
+ scope: 'system',
241
+ scopeId: (0, typeorm_2.IsNull)(),
242
+ locale: data.locale,
243
+ },
244
+ });
245
+ if (existingTemplate) {
246
+ throw new common_1.ConflictException(`System template already exists`);
247
+ }
248
+ const template = this.layoutRepository.create({
249
+ ...data,
250
+ scopeId: undefined, // Ensure system templates have no scopeId
251
+ });
252
+ return this.layoutRepository.save(template);
253
+ }
254
+ async overwriteSystemTemplateLayout(templateId, updates) {
255
+ let template = await this.layoutRepository.findOne({
256
+ where: { id: templateId },
257
+ });
258
+ if (!template) {
259
+ throw new common_1.NotFoundException(`Template not found: ${templateId}`);
260
+ }
261
+ if (template.scope === 'system') {
262
+ if (!updates.scope) {
263
+ throw new common_1.BadRequestException('Scope is required when overwriting system template');
264
+ }
265
+ // Check if template already exists in target scope
266
+ const existingTemplate = await this.layoutRepository.findOne({
267
+ where: {
268
+ name: template.name,
269
+ locale: template.locale,
270
+ scope: updates.scope,
271
+ scopeId: updates.scopeId,
272
+ },
273
+ });
274
+ if (existingTemplate) {
275
+ // Update existing template in target scope
276
+ template = existingTemplate;
277
+ }
278
+ else {
279
+ // Create new template in target scope
280
+ const newTemplate = this.layoutRepository.create({
281
+ ...template,
282
+ id: undefined,
283
+ createdAt: undefined,
284
+ updatedAt: undefined,
285
+ scope: updates.scope,
286
+ scopeId: updates.scopeId,
287
+ });
288
+ template = newTemplate;
289
+ }
290
+ }
291
+ template = this.layoutRepository.merge(template, updates);
292
+ await this.layoutRepository.save(template);
293
+ return template;
294
+ }
295
+ /**
296
+ * Update a template
297
+ */
298
+ async updateTemplateLayout(id, updates, canUpdateSystemTemplate = false) {
299
+ // Find the template
300
+ let template = await this.layoutRepository.findOne({
301
+ where: { id },
302
+ });
303
+ if (!template) {
304
+ throw new common_1.NotFoundException(`Template not found: ${id}`);
305
+ }
306
+ // If it's a system template and we can't update it, try to overwrite it
307
+ if (template.scope === 'system' && !canUpdateSystemTemplate) {
308
+ if (updates.scope) {
309
+ // Otherwise, allow overwriting to custom scope
310
+ return this.overwriteSystemTemplateLayout(id, updates);
311
+ }
312
+ else {
313
+ throw new common_1.ForbiddenException('Cannot update system templates');
314
+ }
315
+ }
316
+ // For regular updates
317
+ template = this.layoutRepository.merge(template, updates);
318
+ return this.layoutRepository.save(template);
319
+ }
320
+ /**
321
+ * Delete a scoped template
322
+ */
323
+ async deleteTemplateLayout(id, canDeleteSystemTemplate = false) {
324
+ const template = await this.layoutRepository.findOne({
325
+ where: { id },
326
+ });
327
+ if (!template) {
328
+ throw new Error(`Template not found: ${id}`);
329
+ }
330
+ // Prevent deleting system templates
331
+ if (template.scope === 'system' && !canDeleteSystemTemplate) {
332
+ throw new common_1.ForbiddenException('Cannot delete system templates');
333
+ }
334
+ await this.layoutRepository.remove(template);
335
+ }
336
+ };
337
+ exports.TemplateLayoutService = TemplateLayoutService;
338
+ exports.TemplateLayoutService = TemplateLayoutService = tslib_1.__decorate([
339
+ (0, common_1.Injectable)(),
340
+ tslib_1.__param(0, (0, typeorm_1.InjectRepository)(template_layout_entity_1.NestDynamicTemplateLayout)),
341
+ tslib_1.__metadata("design:paramtypes", [typeorm_2.Repository,
342
+ template_engine_registry_1.TemplateEngineRegistryService])
343
+ ], TemplateLayoutService);