strapi-plugin-magic-mail 2.2.4 → 2.2.5
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/dist/server/index.js +1 -1
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -3
- package/admin/jsconfig.json +0 -10
- package/admin/src/components/AddAccountModal.jsx +0 -1943
- package/admin/src/components/Initializer.jsx +0 -14
- package/admin/src/components/LicenseGuard.jsx +0 -475
- package/admin/src/components/PluginIcon.jsx +0 -5
- package/admin/src/hooks/useAuthRefresh.js +0 -44
- package/admin/src/hooks/useLicense.js +0 -158
- package/admin/src/index.js +0 -87
- package/admin/src/pages/Analytics.jsx +0 -762
- package/admin/src/pages/App.jsx +0 -111
- package/admin/src/pages/EmailDesigner/EditorPage.jsx +0 -1424
- package/admin/src/pages/EmailDesigner/TemplateList.jsx +0 -1807
- package/admin/src/pages/HomePage.jsx +0 -1170
- package/admin/src/pages/LicensePage.jsx +0 -430
- package/admin/src/pages/RoutingRules.jsx +0 -1141
- package/admin/src/pages/Settings.jsx +0 -603
- package/admin/src/pluginId.js +0 -3
- package/admin/src/translations/de.json +0 -71
- package/admin/src/translations/en.json +0 -70
- package/admin/src/translations/es.json +0 -71
- package/admin/src/translations/fr.json +0 -71
- package/admin/src/translations/pt.json +0 -71
- package/admin/src/utils/fetchWithRetry.js +0 -123
- package/admin/src/utils/getTranslation.js +0 -5
- package/admin/src/utils/theme.js +0 -85
- package/server/jsconfig.json +0 -10
- package/server/src/bootstrap.js +0 -157
- package/server/src/config/features.js +0 -260
- package/server/src/config/index.js +0 -9
- package/server/src/content-types/email-account/schema.json +0 -93
- package/server/src/content-types/email-event/index.js +0 -8
- package/server/src/content-types/email-event/schema.json +0 -57
- package/server/src/content-types/email-link/index.js +0 -8
- package/server/src/content-types/email-link/schema.json +0 -49
- package/server/src/content-types/email-log/index.js +0 -8
- package/server/src/content-types/email-log/schema.json +0 -106
- package/server/src/content-types/email-template/schema.json +0 -74
- package/server/src/content-types/email-template-version/schema.json +0 -60
- package/server/src/content-types/index.js +0 -33
- package/server/src/content-types/routing-rule/schema.json +0 -59
- package/server/src/controllers/accounts.js +0 -229
- package/server/src/controllers/analytics.js +0 -361
- package/server/src/controllers/controller.js +0 -26
- package/server/src/controllers/email-designer.js +0 -474
- package/server/src/controllers/index.js +0 -21
- package/server/src/controllers/license.js +0 -269
- package/server/src/controllers/oauth.js +0 -474
- package/server/src/controllers/routing-rules.js +0 -129
- package/server/src/controllers/test.js +0 -301
- package/server/src/destroy.js +0 -27
- package/server/src/index.js +0 -25
- package/server/src/middlewares/index.js +0 -3
- package/server/src/policies/index.js +0 -3
- package/server/src/register.js +0 -5
- package/server/src/routes/admin.js +0 -469
- package/server/src/routes/content-api.js +0 -37
- package/server/src/routes/index.js +0 -9
- package/server/src/services/account-manager.js +0 -329
- package/server/src/services/analytics.js +0 -512
- package/server/src/services/email-designer.js +0 -717
- package/server/src/services/email-router.js +0 -1446
- package/server/src/services/index.js +0 -17
- package/server/src/services/license-guard.js +0 -423
- package/server/src/services/oauth.js +0 -515
- package/server/src/services/service.js +0 -7
- package/server/src/utils/encryption.js +0 -81
- 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(/<%|<%/g, '{{').replace(/%>|%>/g, '}}')
|
|
666
|
-
: '';
|
|
667
|
-
|
|
668
|
-
const subjectConverted = data?.options?.object
|
|
669
|
-
? data.options.object.replace(/<%|<%/g, '{{').replace(/%>|%>/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
|
-
});
|