strapi-plugin-magic-mail 2.2.4 → 2.2.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 (71) hide show
  1. package/README.md +0 -2
  2. package/dist/server/index.js +1 -1
  3. package/dist/server/index.mjs +1 -1
  4. package/package.json +1 -3
  5. package/admin/jsconfig.json +0 -10
  6. package/admin/src/components/AddAccountModal.jsx +0 -1943
  7. package/admin/src/components/Initializer.jsx +0 -14
  8. package/admin/src/components/LicenseGuard.jsx +0 -475
  9. package/admin/src/components/PluginIcon.jsx +0 -5
  10. package/admin/src/hooks/useAuthRefresh.js +0 -44
  11. package/admin/src/hooks/useLicense.js +0 -158
  12. package/admin/src/index.js +0 -87
  13. package/admin/src/pages/Analytics.jsx +0 -762
  14. package/admin/src/pages/App.jsx +0 -111
  15. package/admin/src/pages/EmailDesigner/EditorPage.jsx +0 -1424
  16. package/admin/src/pages/EmailDesigner/TemplateList.jsx +0 -1807
  17. package/admin/src/pages/HomePage.jsx +0 -1170
  18. package/admin/src/pages/LicensePage.jsx +0 -430
  19. package/admin/src/pages/RoutingRules.jsx +0 -1141
  20. package/admin/src/pages/Settings.jsx +0 -603
  21. package/admin/src/pluginId.js +0 -3
  22. package/admin/src/translations/de.json +0 -71
  23. package/admin/src/translations/en.json +0 -70
  24. package/admin/src/translations/es.json +0 -71
  25. package/admin/src/translations/fr.json +0 -71
  26. package/admin/src/translations/pt.json +0 -71
  27. package/admin/src/utils/fetchWithRetry.js +0 -123
  28. package/admin/src/utils/getTranslation.js +0 -5
  29. package/admin/src/utils/theme.js +0 -85
  30. package/server/jsconfig.json +0 -10
  31. package/server/src/bootstrap.js +0 -157
  32. package/server/src/config/features.js +0 -260
  33. package/server/src/config/index.js +0 -9
  34. package/server/src/content-types/email-account/schema.json +0 -93
  35. package/server/src/content-types/email-event/index.js +0 -8
  36. package/server/src/content-types/email-event/schema.json +0 -57
  37. package/server/src/content-types/email-link/index.js +0 -8
  38. package/server/src/content-types/email-link/schema.json +0 -49
  39. package/server/src/content-types/email-log/index.js +0 -8
  40. package/server/src/content-types/email-log/schema.json +0 -106
  41. package/server/src/content-types/email-template/schema.json +0 -74
  42. package/server/src/content-types/email-template-version/schema.json +0 -60
  43. package/server/src/content-types/index.js +0 -33
  44. package/server/src/content-types/routing-rule/schema.json +0 -59
  45. package/server/src/controllers/accounts.js +0 -229
  46. package/server/src/controllers/analytics.js +0 -361
  47. package/server/src/controllers/controller.js +0 -26
  48. package/server/src/controllers/email-designer.js +0 -474
  49. package/server/src/controllers/index.js +0 -21
  50. package/server/src/controllers/license.js +0 -269
  51. package/server/src/controllers/oauth.js +0 -474
  52. package/server/src/controllers/routing-rules.js +0 -129
  53. package/server/src/controllers/test.js +0 -301
  54. package/server/src/destroy.js +0 -27
  55. package/server/src/index.js +0 -25
  56. package/server/src/middlewares/index.js +0 -3
  57. package/server/src/policies/index.js +0 -3
  58. package/server/src/register.js +0 -5
  59. package/server/src/routes/admin.js +0 -469
  60. package/server/src/routes/content-api.js +0 -37
  61. package/server/src/routes/index.js +0 -9
  62. package/server/src/services/account-manager.js +0 -329
  63. package/server/src/services/analytics.js +0 -512
  64. package/server/src/services/email-designer.js +0 -717
  65. package/server/src/services/email-router.js +0 -1446
  66. package/server/src/services/index.js +0 -17
  67. package/server/src/services/license-guard.js +0 -423
  68. package/server/src/services/oauth.js +0 -515
  69. package/server/src/services/service.js +0 -7
  70. package/server/src/utils/encryption.js +0 -81
  71. package/server/src/utils/logger.js +0 -84
@@ -1,717 +0,0 @@
1
- /**
2
- * Email Designer Service
3
- *
4
- * Handles email template creation, updates, versioning, and rendering
5
- *
6
- * [SUCCESS] Migrated to strapi.documents() API (Strapi v5 Best Practice)
7
- */
8
-
9
- 'use strict';
10
-
11
- const Mustache = require('mustache');
12
- const htmlToTextLib = require('html-to-text');
13
- const decode = require('decode-html');
14
-
15
- // Content Type UIDs
16
- const EMAIL_TEMPLATE_UID = 'plugin::magic-mail.email-template';
17
- const EMAIL_TEMPLATE_VERSION_UID = 'plugin::magic-mail.email-template-version';
18
-
19
- // ============================================================
20
- // HELPER FUNCTIONS
21
- // ============================================================
22
-
23
- /**
24
- * Safely convert HTML to plain text
25
- * Handles various html-to-text library versions
26
- */
27
- const convertHtmlToText = (html, options = { wordwrap: 130 }) => {
28
- try {
29
- if (!html || typeof html !== 'string') {
30
- return '';
31
- }
32
-
33
- if (!htmlToTextLib) {
34
- return html.replace(/<[^>]*>/g, '');
35
- }
36
-
37
- // Try different API styles
38
- if (htmlToTextLib.htmlToText && typeof htmlToTextLib.htmlToText === 'function') {
39
- return htmlToTextLib.htmlToText(html, options);
40
- } else if (htmlToTextLib.convert && typeof htmlToTextLib.convert === 'function') {
41
- return htmlToTextLib.convert(html, options);
42
- } else if (typeof htmlToTextLib === 'function') {
43
- return htmlToTextLib(html, options);
44
- } else if (htmlToTextLib.default) {
45
- if (typeof htmlToTextLib.default.htmlToText === 'function') {
46
- return htmlToTextLib.default.htmlToText(html, options);
47
- } else if (typeof htmlToTextLib.default.convert === 'function') {
48
- return htmlToTextLib.default.convert(html, options);
49
- } else if (typeof htmlToTextLib.default === 'function') {
50
- return htmlToTextLib.default(html, options);
51
- }
52
- }
53
-
54
- // Fallback
55
- return html.replace(/<[^>]*>/g, '');
56
- } catch (error) {
57
- strapi.log.error('[magic-mail] Error converting HTML to text:', error);
58
- return (html || '').replace(/<[^>]*>/g, '');
59
- }
60
- };
61
-
62
- // ============================================================
63
- // SERVICE
64
- // ============================================================
65
-
66
- module.exports = ({ strapi }) => ({
67
-
68
- // ============================================================
69
- // TEMPLATE CRUD OPERATIONS
70
- // ============================================================
71
-
72
- /**
73
- * Get all templates
74
- */
75
- async findAll(filters = {}) {
76
- return strapi.documents(EMAIL_TEMPLATE_UID).findMany({
77
- filters,
78
- sort: [{ createdAt: 'desc' }],
79
- });
80
- },
81
-
82
- /**
83
- * Get template by ID (documentId) with populated versions
84
- */
85
- async findOne(documentId) {
86
- return strapi.documents(EMAIL_TEMPLATE_UID).findOne({
87
- documentId,
88
- populate: ['versions'],
89
- });
90
- },
91
-
92
- /**
93
- * Get template by numeric ID (for backward compatibility)
94
- */
95
- async findById(id) {
96
- const results = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
97
- filters: { id },
98
- limit: 1,
99
- populate: ['versions'],
100
- });
101
- return results.length > 0 ? results[0] : null;
102
- },
103
-
104
- /**
105
- * Get template by reference ID
106
- */
107
- async findByReferenceId(templateReferenceId) {
108
- const results = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
109
- filters: { templateReferenceId },
110
- limit: 1,
111
- });
112
- return results.length > 0 ? results[0] : null;
113
- },
114
-
115
- /**
116
- * Create new template with automatic initial version
117
- */
118
- async create(data) {
119
- strapi.log.info('[magic-mail] [TEST] Creating new template...');
120
-
121
- // 1. Check license limits
122
- const maxTemplates = await strapi
123
- .plugin('magic-mail')
124
- .service('license-guard')
125
- .getMaxEmailTemplates();
126
-
127
- // Use native count() method for efficiency
128
- const currentCount = await strapi.documents(EMAIL_TEMPLATE_UID).count();
129
-
130
- if (maxTemplates !== -1 && currentCount >= maxTemplates) {
131
- throw new Error(
132
- `Template limit reached (${maxTemplates}). Upgrade your license to create more templates.`
133
- );
134
- }
135
-
136
- // 2. Validate reference ID is unique
137
- if (data.templateReferenceId) {
138
- const existing = await this.findByReferenceId(data.templateReferenceId);
139
- if (existing) {
140
- throw new Error(`Template with reference ID ${data.templateReferenceId} already exists`);
141
- }
142
- }
143
-
144
- // 3. Create template
145
- const template = await strapi.documents(EMAIL_TEMPLATE_UID).create({
146
- data: {
147
- ...data,
148
- isActive: data.isActive !== undefined ? data.isActive : true,
149
- },
150
- });
151
-
152
- strapi.log.info(`[magic-mail] [SUCCESS] Template created: documentId=${template.documentId}, name="${template.name}"`);
153
-
154
- // 4. Create initial version if versioning enabled
155
- const hasVersioning = await strapi
156
- .plugin('magic-mail')
157
- .service('license-guard')
158
- .hasFeature('email-designer-versioning');
159
-
160
- if (hasVersioning) {
161
- strapi.log.info('[magic-mail] [SAVE] Creating initial version...');
162
-
163
- await this.createVersion(template.documentId, {
164
- name: data.name,
165
- subject: data.subject,
166
- design: data.design,
167
- bodyHtml: data.bodyHtml,
168
- bodyText: data.bodyText,
169
- tags: data.tags,
170
- });
171
-
172
- strapi.log.info('[magic-mail] [SUCCESS] Initial version created');
173
- } else {
174
- strapi.log.info('[magic-mail] [SKIP] Versioning not enabled, skipping initial version');
175
- }
176
-
177
- return template;
178
- },
179
-
180
- /**
181
- * Update template with automatic version snapshot
182
- */
183
- async update(documentId, data) {
184
- strapi.log.info(`[magic-mail] [UPDATE] Updating template documentId: ${documentId}`);
185
-
186
- // 1. Load existing template
187
- const template = await this.findOne(documentId);
188
- if (!template) {
189
- throw new Error('Template not found');
190
- }
191
-
192
- strapi.log.info(`[magic-mail] [INFO] Found template: documentId=${template.documentId}, name="${template.name}"`);
193
-
194
- // 2. Create version snapshot BEFORE update (if versioning enabled)
195
- const hasVersioning = await strapi
196
- .plugin('magic-mail')
197
- .service('license-guard')
198
- .hasFeature('email-designer-versioning');
199
-
200
- if (hasVersioning) {
201
- strapi.log.info('[magic-mail] [SAVE] Creating version snapshot before update...');
202
-
203
- await this.createVersion(template.documentId, {
204
- name: template.name,
205
- subject: template.subject,
206
- design: template.design,
207
- bodyHtml: template.bodyHtml,
208
- bodyText: template.bodyText,
209
- tags: template.tags,
210
- });
211
-
212
- strapi.log.info('[magic-mail] [SUCCESS] Version snapshot created');
213
- }
214
-
215
- // 3. Update template
216
- const updateData = { ...data };
217
- if ('versions' in updateData) {
218
- delete updateData.versions;
219
- strapi.log.warn('[magic-mail] [WARNING] Removed versions field from update data');
220
- }
221
-
222
- const updated = await strapi.documents(EMAIL_TEMPLATE_UID).update({
223
- documentId,
224
- data: updateData,
225
- });
226
-
227
- strapi.log.info(`[magic-mail] [SUCCESS] Template updated: documentId=${updated.documentId}`);
228
- return updated;
229
- },
230
-
231
- /**
232
- * Delete template and all its versions
233
- */
234
- async delete(documentId) {
235
- strapi.log.info(`[magic-mail] [DELETE] Deleting template documentId: ${documentId}`);
236
-
237
- const template = await this.findOne(documentId);
238
- if (!template) {
239
- throw new Error('Template not found');
240
- }
241
-
242
- strapi.log.info(`[magic-mail] [DELETE] Template: documentId=${template.documentId}, name="${template.name}"`);
243
-
244
- // Delete all versions
245
- const allVersions = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findMany({
246
- filters: {
247
- template: {
248
- documentId: template.documentId,
249
- },
250
- },
251
- });
252
-
253
- strapi.log.info(`[magic-mail] [DELETE] Found ${allVersions.length} versions to delete`);
254
-
255
- for (const version of allVersions) {
256
- try {
257
- await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({
258
- documentId: version.documentId
259
- });
260
- strapi.log.info(`[magic-mail] [DELETE] Deleted version #${version.versionNumber}`);
261
- } catch (versionError) {
262
- strapi.log.warn(`[magic-mail] [WARNING] Failed to delete version: ${versionError.message}`);
263
- }
264
- }
265
-
266
- // Delete template
267
- const result = await strapi.documents(EMAIL_TEMPLATE_UID).delete({ documentId });
268
-
269
- strapi.log.info(`[magic-mail] [SUCCESS] Template "${template.name}" and ${allVersions.length} versions deleted`);
270
- return result;
271
- },
272
-
273
- /**
274
- * Duplicate template
275
- */
276
- async duplicate(documentId) {
277
- strapi.log.info(`[magic-mail] [INFO] Duplicating template documentId: ${documentId}`);
278
-
279
- const original = await this.findOne(documentId);
280
- if (!original) {
281
- throw new Error('Template not found');
282
- }
283
-
284
- strapi.log.info(`[magic-mail] [PACKAGE] Original template: documentId=${original.documentId}, name="${original.name}"`);
285
-
286
- const duplicateData = {
287
- name: `${original.name} copy`,
288
- subject: original.subject,
289
- design: original.design,
290
- bodyHtml: original.bodyHtml,
291
- bodyText: original.bodyText,
292
- category: original.category,
293
- tags: original.tags,
294
- isActive: original.isActive,
295
- templateReferenceId: Date.now() + Math.floor(Math.random() * 1000),
296
- };
297
-
298
- const duplicated = await this.create(duplicateData);
299
-
300
- strapi.log.info(`[magic-mail] [SUCCESS] Template duplicated: documentId=${duplicated.documentId}`);
301
- return duplicated;
302
- },
303
-
304
- // ============================================================
305
- // VERSIONING OPERATIONS
306
- // ============================================================
307
-
308
- /**
309
- * Create a new version for a template
310
- */
311
- async createVersion(templateDocumentId, data) {
312
- strapi.log.info(`[magic-mail] [SNAPSHOT] Creating version for template documentId: ${templateDocumentId}`);
313
-
314
- // 1. Verify template exists
315
- const template = await strapi.documents(EMAIL_TEMPLATE_UID).findOne({
316
- documentId: templateDocumentId,
317
- });
318
- if (!template) {
319
- throw new Error(`Template ${templateDocumentId} not found`);
320
- }
321
-
322
- strapi.log.info(`[magic-mail] [PACKAGE] Template found: documentId=${template.documentId}, name="${template.name}"`);
323
-
324
- // 2. Calculate next version number
325
- const existingVersions = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findMany({
326
- filters: {
327
- template: {
328
- documentId: templateDocumentId,
329
- },
330
- },
331
- sort: [{ versionNumber: 'desc' }],
332
- });
333
-
334
- const versionNumber = existingVersions.length > 0
335
- ? Math.max(...existingVersions.map(v => v.versionNumber || 0)) + 1
336
- : 1;
337
-
338
- strapi.log.info(`[magic-mail] [STATS] Existing versions: ${existingVersions.length} → Next version: #${versionNumber}`);
339
-
340
- // 3. Create version WITH template relation
341
- const createdVersion = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).create({
342
- data: {
343
- versionNumber,
344
- ...data,
345
- template: templateDocumentId, // Document Service handles relations with documentId
346
- },
347
- });
348
-
349
- strapi.log.info(`[magic-mail] [SUCCESS] Version created: documentId=${createdVersion.documentId}, v${versionNumber}`);
350
- return createdVersion;
351
- },
352
-
353
- /**
354
- * Get all versions for a template
355
- */
356
- async getVersions(templateDocumentId) {
357
- strapi.log.info(`[magic-mail] 📜 Fetching versions for template documentId: ${templateDocumentId}`);
358
-
359
- const template = await strapi.documents(EMAIL_TEMPLATE_UID).findOne({
360
- documentId: templateDocumentId,
361
- populate: ['versions'],
362
- });
363
-
364
- if (!template) {
365
- throw new Error('Template not found');
366
- }
367
-
368
- strapi.log.info(`[magic-mail] [PACKAGE] Template has ${template.versions?.length || 0} versions`);
369
-
370
- if (template.versions && template.versions.length > 0) {
371
- const sortedVersions = [...template.versions].sort((a, b) => b.versionNumber - a.versionNumber);
372
- return sortedVersions;
373
- }
374
-
375
- // Fallback: find via filter
376
- const versions = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findMany({
377
- filters: {
378
- template: {
379
- documentId: templateDocumentId,
380
- },
381
- },
382
- sort: [{ versionNumber: 'desc' }],
383
- });
384
-
385
- strapi.log.info(`[magic-mail] [SUCCESS] Found ${versions.length} versions`);
386
- return versions;
387
- },
388
-
389
- /**
390
- * Restore template from a specific version
391
- */
392
- async restoreVersion(templateDocumentId, versionDocumentId) {
393
- strapi.log.info(`[magic-mail] [RESTORE] Restoring template ${templateDocumentId} from version ${versionDocumentId}`);
394
-
395
- const version = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findOne({
396
- documentId: versionDocumentId,
397
- populate: ['template'],
398
- });
399
-
400
- if (!version) {
401
- throw new Error('Version not found');
402
- }
403
-
404
- // Verify version belongs to this template
405
- if (version.template?.documentId !== templateDocumentId) {
406
- throw new Error('Version does not belong to this template');
407
- }
408
-
409
- // Update template with version data (creates new version via update)
410
- const restored = await this.update(templateDocumentId, {
411
- name: version.name,
412
- subject: version.subject,
413
- design: version.design,
414
- bodyHtml: version.bodyHtml,
415
- bodyText: version.bodyText,
416
- tags: version.tags,
417
- });
418
-
419
- strapi.log.info(`[magic-mail] [SUCCESS] Template restored from version #${version.versionNumber}`);
420
- return restored;
421
- },
422
-
423
- /**
424
- * Delete a single version
425
- */
426
- async deleteVersion(templateDocumentId, versionDocumentId) {
427
- strapi.log.info(`[magic-mail] [DELETE] Deleting version ${versionDocumentId}`);
428
-
429
- const version = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findOne({
430
- documentId: versionDocumentId,
431
- populate: ['template'],
432
- });
433
-
434
- if (!version) {
435
- throw new Error('Version not found');
436
- }
437
-
438
- if (version.template?.documentId !== templateDocumentId) {
439
- throw new Error('Version does not belong to this template');
440
- }
441
-
442
- await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({
443
- documentId: versionDocumentId
444
- });
445
-
446
- strapi.log.info(`[magic-mail] [SUCCESS] Version v${version.versionNumber} deleted`);
447
- return { success: true, message: 'Version deleted' };
448
- },
449
-
450
- /**
451
- * Delete all versions for a template
452
- */
453
- async deleteAllVersions(templateDocumentId) {
454
- strapi.log.info(`[magic-mail] [DELETE] Deleting all versions for template ${templateDocumentId}`);
455
-
456
- const template = await strapi.documents(EMAIL_TEMPLATE_UID).findOne({
457
- documentId: templateDocumentId,
458
- populate: ['versions'],
459
- });
460
-
461
- if (!template) {
462
- throw new Error('Template not found');
463
- }
464
-
465
- const versionCount = template.versions?.length || 0;
466
- if (versionCount === 0) {
467
- return { success: true, message: 'No versions to delete', deletedCount: 0 };
468
- }
469
-
470
- let deletedCount = 0;
471
- for (const version of template.versions) {
472
- try {
473
- await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({
474
- documentId: version.documentId
475
- });
476
- deletedCount++;
477
- } catch (error) {
478
- strapi.log.error(`[magic-mail] [ERROR] Failed to delete version: ${error.message}`);
479
- }
480
- }
481
-
482
- strapi.log.info(`[magic-mail] [SUCCESS] Deleted ${deletedCount}/${versionCount} versions`);
483
- return { success: true, deletedCount };
484
- },
485
-
486
- // ============================================================
487
- // RENDERING
488
- // ============================================================
489
-
490
- /**
491
- * Render template with dynamic data using Mustache
492
- */
493
- async renderTemplate(templateReferenceId, data = {}) {
494
- const template = await this.findByReferenceId(templateReferenceId);
495
-
496
- if (!template) {
497
- throw new Error(`Template with reference ID ${templateReferenceId} not found`);
498
- }
499
-
500
- if (!template.isActive) {
501
- throw new Error(`Template ${template.name} is inactive`);
502
- }
503
-
504
- let { bodyHtml = '', bodyText = '', subject = '' } = template;
505
-
506
- // Convert <% %> to {{ }} for Mustache
507
- bodyHtml = bodyHtml.replace(/<%/g, '{{').replace(/%>/g, '}}');
508
- bodyText = bodyText.replace(/<%/g, '{{').replace(/%>/g, '}}');
509
- subject = subject.replace(/<%/g, '{{').replace(/%>/g, '}}');
510
-
511
- if ((!bodyText || !bodyText.length) && bodyHtml && bodyHtml.length) {
512
- bodyText = convertHtmlToText(bodyHtml, { wordwrap: 130 });
513
- }
514
-
515
- const decodedHtml = decode(bodyHtml);
516
- const decodedText = decode(bodyText);
517
- const decodedSubject = decode(subject);
518
-
519
- const renderedHtml = Mustache.render(decodedHtml, data);
520
- const renderedText = Mustache.render(decodedText, data);
521
- const renderedSubject = Mustache.render(decodedSubject, data);
522
-
523
- return {
524
- html: renderedHtml,
525
- text: renderedText,
526
- subject: renderedSubject,
527
- templateName: template.name,
528
- category: template.category,
529
- };
530
- },
531
-
532
- // ============================================================
533
- // IMPORT/EXPORT
534
- // ============================================================
535
-
536
- /**
537
- * Export templates as JSON
538
- */
539
- async exportTemplates(templateDocumentIds = []) {
540
- strapi.log.info('[magic-mail] [EXPORT] Exporting templates...');
541
-
542
- let templates;
543
- if (templateDocumentIds.length > 0) {
544
- templates = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
545
- filters: { documentId: { $in: templateDocumentIds } },
546
- });
547
- } else {
548
- templates = await this.findAll();
549
- }
550
-
551
- const exported = templates.map((template) => ({
552
- templateReferenceId: template.templateReferenceId,
553
- name: template.name,
554
- subject: template.subject,
555
- design: template.design,
556
- bodyHtml: template.bodyHtml,
557
- bodyText: template.bodyText,
558
- category: template.category,
559
- tags: template.tags,
560
- }));
561
-
562
- strapi.log.info(`[magic-mail] [SUCCESS] Exported ${exported.length} templates`);
563
- return exported;
564
- },
565
-
566
- /**
567
- * Import templates from JSON
568
- */
569
- async importTemplates(templates) {
570
- strapi.log.info(`[magic-mail] [IMPORT] Importing ${templates.length} templates...`);
571
-
572
- const results = [];
573
-
574
- for (const templateData of templates) {
575
- try {
576
- const existing = await this.findByReferenceId(templateData.templateReferenceId);
577
-
578
- if (existing) {
579
- const updated = await this.update(existing.documentId, templateData);
580
- results.push({ success: true, action: 'updated', template: updated });
581
- } else {
582
- const created = await this.create(templateData);
583
- results.push({ success: true, action: 'created', template: created });
584
- }
585
- } catch (error) {
586
- results.push({
587
- success: false,
588
- action: 'failed',
589
- error: error.message,
590
- templateName: templateData.name,
591
- });
592
- }
593
- }
594
-
595
- return results;
596
- },
597
-
598
- // ============================================================
599
- // STATISTICS
600
- // ============================================================
601
-
602
- /**
603
- * Get template statistics
604
- */
605
- async getStats() {
606
- const allTemplates = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
607
- fields: ['isActive', 'category'],
608
- });
609
-
610
- const total = allTemplates.length;
611
- const active = allTemplates.filter(t => t.isActive === true).length;
612
-
613
- const categoryMap = allTemplates.reduce((acc, template) => {
614
- const category = template.category || 'custom';
615
- acc[category] = (acc[category] || 0) + 1;
616
- return acc;
617
- }, {});
618
-
619
- const byCategory = Object.entries(categoryMap).map(([category, count]) => ({ category, count }));
620
-
621
- const maxTemplates = await strapi
622
- .plugin('magic-mail')
623
- .service('license-guard')
624
- .getMaxEmailTemplates();
625
-
626
- return {
627
- total,
628
- active,
629
- inactive: total - active,
630
- byCategory,
631
- maxTemplates,
632
- remaining: maxTemplates === -1 ? -1 : Math.max(0, maxTemplates - total),
633
- };
634
- },
635
-
636
- // ============================================================
637
- // STRAPI CORE EMAIL TEMPLATES
638
- // ============================================================
639
-
640
- /**
641
- * Get Strapi core email template
642
- */
643
- async getCoreTemplate(coreEmailType) {
644
- if (!['reset-password', 'email-confirmation'].includes(coreEmailType)) {
645
- throw new Error('Invalid core email type');
646
- }
647
-
648
- const pluginStoreEmailKey =
649
- coreEmailType === 'email-confirmation' ? 'email_confirmation' : 'reset_password';
650
-
651
- const pluginStore = await strapi.store({
652
- environment: '',
653
- type: 'plugin',
654
- name: 'users-permissions',
655
- });
656
-
657
- const emailConfig = await pluginStore.get({ key: 'email' });
658
-
659
- let data = null;
660
- if (emailConfig && emailConfig[pluginStoreEmailKey]) {
661
- data = emailConfig[pluginStoreEmailKey];
662
- }
663
-
664
- const messageConverted = data?.options?.message
665
- ? data.options.message.replace(/<%|&#x3C;%/g, '{{').replace(/%>|%&#x3E;/g, '}}')
666
- : '';
667
-
668
- const subjectConverted = data?.options?.object
669
- ? data.options.object.replace(/<%|&#x3C;%/g, '{{').replace(/%>|%&#x3E;/g, '}}')
670
- : '';
671
-
672
- return {
673
- from: data?.options?.from || null,
674
- message: messageConverted || '',
675
- subject: subjectConverted || '',
676
- bodyHtml: messageConverted || '',
677
- bodyText: messageConverted ? convertHtmlToText(messageConverted, { wordwrap: 130 }) : '',
678
- coreEmailType,
679
- design: data?.design || null,
680
- };
681
- },
682
-
683
- /**
684
- * Update Strapi core email template
685
- */
686
- async updateCoreTemplate(coreEmailType, data) {
687
- if (!['reset-password', 'email-confirmation'].includes(coreEmailType)) {
688
- throw new Error('Invalid core email type');
689
- }
690
-
691
- const pluginStoreEmailKey =
692
- coreEmailType === 'email-confirmation' ? 'email_confirmation' : 'reset_password';
693
-
694
- const pluginStore = await strapi.store({
695
- environment: '',
696
- type: 'plugin',
697
- name: 'users-permissions',
698
- });
699
-
700
- const emailsConfig = await pluginStore.get({ key: 'email' });
701
-
702
- emailsConfig[pluginStoreEmailKey] = {
703
- ...emailsConfig[pluginStoreEmailKey],
704
- options: {
705
- ...(emailsConfig[pluginStoreEmailKey]?.options || {}),
706
- message: data.message.replace(/{{/g, '<%').replace(/}}/g, '%>'),
707
- object: data.subject.replace(/{{/g, '<%').replace(/}}/g, '%>'),
708
- },
709
- design: data.design,
710
- };
711
-
712
- await pluginStore.set({ key: 'email', value: emailsConfig });
713
-
714
- strapi.log.info(`[magic-mail] [SUCCESS] Core email template updated: ${pluginStoreEmailKey}`);
715
- return { message: 'Saved' };
716
- },
717
- });