strapi-plugin-magic-mail 2.0.1 → 2.0.3
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 -5
- package/dist/server/index.js +602 -665
- package/dist/server/index.mjs +602 -665
- package/package.json +1 -1
- package/server/src/bootstrap.js +16 -16
- package/server/src/config/features.js +7 -7
- package/server/src/controllers/accounts.js +15 -6
- package/server/src/controllers/analytics.js +56 -42
- package/server/src/controllers/license.js +9 -7
- package/server/src/controllers/oauth.js +9 -9
- package/server/src/controllers/routing-rules.js +18 -11
- package/server/src/controllers/test.js +111 -193
- package/server/src/services/account-manager.js +73 -21
- package/server/src/services/analytics.js +88 -72
- package/server/src/services/email-designer.js +131 -284
- package/server/src/services/email-router.js +69 -43
- package/server/src/services/license-guard.js +24 -24
- package/server/src/services/oauth.js +11 -11
- package/server/src/services/service.js +1 -1
- package/server/src/utils/encryption.js +1 -1
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles email template creation, updates, versioning, and rendering
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* This ensures proper relation handling in Strapi v5
|
|
6
|
+
* [SUCCESS] Migrated to strapi.documents() API (Strapi v5 Best Practice)
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
'use strict';
|
|
@@ -13,6 +12,10 @@ const Mustache = require('mustache');
|
|
|
13
12
|
const htmlToTextLib = require('html-to-text');
|
|
14
13
|
const decode = require('decode-html');
|
|
15
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
|
+
|
|
16
19
|
// ============================================================
|
|
17
20
|
// HELPER FUNCTIONS
|
|
18
21
|
// ============================================================
|
|
@@ -70,26 +73,39 @@ module.exports = ({ strapi }) => ({
|
|
|
70
73
|
* Get all templates
|
|
71
74
|
*/
|
|
72
75
|
async findAll(filters = {}) {
|
|
73
|
-
return strapi.
|
|
76
|
+
return strapi.documents(EMAIL_TEMPLATE_UID).findMany({
|
|
74
77
|
filters,
|
|
75
|
-
sort: { createdAt: 'desc' },
|
|
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'],
|
|
76
89
|
});
|
|
77
90
|
},
|
|
78
91
|
|
|
79
92
|
/**
|
|
80
|
-
* Get template by ID
|
|
93
|
+
* Get template by numeric ID (for backward compatibility)
|
|
81
94
|
*/
|
|
82
|
-
async
|
|
83
|
-
|
|
95
|
+
async findById(id) {
|
|
96
|
+
const results = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
|
|
97
|
+
filters: { id },
|
|
98
|
+
limit: 1,
|
|
84
99
|
populate: ['versions'],
|
|
85
100
|
});
|
|
101
|
+
return results.length > 0 ? results[0] : null;
|
|
86
102
|
},
|
|
87
103
|
|
|
88
104
|
/**
|
|
89
105
|
* Get template by reference ID
|
|
90
106
|
*/
|
|
91
107
|
async findByReferenceId(templateReferenceId) {
|
|
92
|
-
const results = await strapi.
|
|
108
|
+
const results = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
|
|
93
109
|
filters: { templateReferenceId },
|
|
94
110
|
limit: 1,
|
|
95
111
|
});
|
|
@@ -98,15 +114,9 @@ module.exports = ({ strapi }) => ({
|
|
|
98
114
|
|
|
99
115
|
/**
|
|
100
116
|
* Create new template with automatic initial version
|
|
101
|
-
*
|
|
102
|
-
* Steps:
|
|
103
|
-
* 1. Check license limits
|
|
104
|
-
* 2. Validate reference ID is unique
|
|
105
|
-
* 3. Create template
|
|
106
|
-
* 4. Create initial version (if versioning enabled)
|
|
107
117
|
*/
|
|
108
118
|
async create(data) {
|
|
109
|
-
strapi.log.info('[magic-mail]
|
|
119
|
+
strapi.log.info('[magic-mail] [TEST] Creating new template...');
|
|
110
120
|
|
|
111
121
|
// 1. Check license limits
|
|
112
122
|
const maxTemplates = await strapi
|
|
@@ -114,9 +124,8 @@ module.exports = ({ strapi }) => ({
|
|
|
114
124
|
.service('license-guard')
|
|
115
125
|
.getMaxEmailTemplates();
|
|
116
126
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.count();
|
|
127
|
+
// Use native count() method for efficiency
|
|
128
|
+
const currentCount = await strapi.documents(EMAIL_TEMPLATE_UID).count();
|
|
120
129
|
|
|
121
130
|
if (maxTemplates !== -1 && currentCount >= maxTemplates) {
|
|
122
131
|
throw new Error(
|
|
@@ -133,14 +142,14 @@ module.exports = ({ strapi }) => ({
|
|
|
133
142
|
}
|
|
134
143
|
|
|
135
144
|
// 3. Create template
|
|
136
|
-
const template = await strapi.
|
|
145
|
+
const template = await strapi.documents(EMAIL_TEMPLATE_UID).create({
|
|
137
146
|
data: {
|
|
138
147
|
...data,
|
|
139
148
|
isActive: data.isActive !== undefined ? data.isActive : true,
|
|
140
149
|
},
|
|
141
150
|
});
|
|
142
151
|
|
|
143
|
-
strapi.log.info(`[magic-mail]
|
|
152
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Template created: documentId=${template.documentId}, name="${template.name}"`);
|
|
144
153
|
|
|
145
154
|
// 4. Create initial version if versioning enabled
|
|
146
155
|
const hasVersioning = await strapi
|
|
@@ -149,9 +158,9 @@ module.exports = ({ strapi }) => ({
|
|
|
149
158
|
.hasFeature('email-designer-versioning');
|
|
150
159
|
|
|
151
160
|
if (hasVersioning) {
|
|
152
|
-
strapi.log.info('[magic-mail]
|
|
161
|
+
strapi.log.info('[magic-mail] [SAVE] Creating initial version...');
|
|
153
162
|
|
|
154
|
-
await this.createVersion(template.
|
|
163
|
+
await this.createVersion(template.documentId, {
|
|
155
164
|
name: data.name,
|
|
156
165
|
subject: data.subject,
|
|
157
166
|
design: data.design,
|
|
@@ -160,9 +169,9 @@ module.exports = ({ strapi }) => ({
|
|
|
160
169
|
tags: data.tags,
|
|
161
170
|
});
|
|
162
171
|
|
|
163
|
-
strapi.log.info('[magic-mail]
|
|
172
|
+
strapi.log.info('[magic-mail] [SUCCESS] Initial version created');
|
|
164
173
|
} else {
|
|
165
|
-
strapi.log.info('[magic-mail]
|
|
174
|
+
strapi.log.info('[magic-mail] [SKIP] Versioning not enabled, skipping initial version');
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
return template;
|
|
@@ -170,24 +179,17 @@ module.exports = ({ strapi }) => ({
|
|
|
170
179
|
|
|
171
180
|
/**
|
|
172
181
|
* Update template with automatic version snapshot
|
|
173
|
-
*
|
|
174
|
-
* Steps:
|
|
175
|
-
* 1. Load existing template
|
|
176
|
-
* 2. Create version snapshot of CURRENT state (before update)
|
|
177
|
-
* 3. Update template with new data
|
|
178
|
-
*
|
|
179
|
-
* IMPORTANT: Version is created BEFORE update to preserve old state!
|
|
180
182
|
*/
|
|
181
|
-
async update(
|
|
182
|
-
strapi.log.info(`[magic-mail]
|
|
183
|
+
async update(documentId, data) {
|
|
184
|
+
strapi.log.info(`[magic-mail] [UPDATE] Updating template documentId: ${documentId}`);
|
|
183
185
|
|
|
184
186
|
// 1. Load existing template
|
|
185
|
-
const template = await this.findOne(
|
|
187
|
+
const template = await this.findOne(documentId);
|
|
186
188
|
if (!template) {
|
|
187
189
|
throw new Error('Template not found');
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
strapi.log.info(`[magic-mail]
|
|
192
|
+
strapi.log.info(`[magic-mail] [INFO] Found template: documentId=${template.documentId}, name="${template.name}"`);
|
|
191
193
|
|
|
192
194
|
// 2. Create version snapshot BEFORE update (if versioning enabled)
|
|
193
195
|
const hasVersioning = await strapi
|
|
@@ -196,10 +198,9 @@ module.exports = ({ strapi }) => ({
|
|
|
196
198
|
.hasFeature('email-designer-versioning');
|
|
197
199
|
|
|
198
200
|
if (hasVersioning) {
|
|
199
|
-
strapi.log.info('[magic-mail]
|
|
201
|
+
strapi.log.info('[magic-mail] [SAVE] Creating version snapshot before update...');
|
|
200
202
|
|
|
201
|
-
|
|
202
|
-
await this.createVersion(template.id, {
|
|
203
|
+
await this.createVersion(template.documentId, {
|
|
203
204
|
name: template.name,
|
|
204
205
|
subject: template.subject,
|
|
205
206
|
design: template.design,
|
|
@@ -208,48 +209,40 @@ module.exports = ({ strapi }) => ({
|
|
|
208
209
|
tags: template.tags,
|
|
209
210
|
});
|
|
210
211
|
|
|
211
|
-
strapi.log.info('[magic-mail]
|
|
212
|
-
} else {
|
|
213
|
-
strapi.log.info('[magic-mail] ⏭️ Versioning not enabled, skipping version snapshot');
|
|
212
|
+
strapi.log.info('[magic-mail] [SUCCESS] Version snapshot created');
|
|
214
213
|
}
|
|
215
214
|
|
|
216
215
|
// 3. Update template
|
|
217
|
-
// CRITICAL: Do NOT pass versions in data, it would overwrite the relation!
|
|
218
|
-
// Only update the fields that are explicitly provided
|
|
219
216
|
const updateData = { ...data };
|
|
220
|
-
|
|
221
|
-
// Remove any versions field if it exists (shouldn't, but be safe)
|
|
222
217
|
if ('versions' in updateData) {
|
|
223
218
|
delete updateData.versions;
|
|
224
|
-
strapi.log.warn('[magic-mail]
|
|
219
|
+
strapi.log.warn('[magic-mail] [WARNING] Removed versions field from update data');
|
|
225
220
|
}
|
|
226
221
|
|
|
227
|
-
const updated = await strapi.
|
|
222
|
+
const updated = await strapi.documents(EMAIL_TEMPLATE_UID).update({
|
|
223
|
+
documentId,
|
|
228
224
|
data: updateData,
|
|
229
225
|
});
|
|
230
226
|
|
|
231
|
-
strapi.log.info(`[magic-mail]
|
|
227
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Template updated: documentId=${updated.documentId}`);
|
|
232
228
|
return updated;
|
|
233
229
|
},
|
|
234
230
|
|
|
235
231
|
/**
|
|
236
232
|
* Delete template and all its versions
|
|
237
|
-
*
|
|
238
|
-
* Uses Document Service for version deletion to ensure cascading delete
|
|
239
233
|
*/
|
|
240
|
-
async delete(
|
|
241
|
-
strapi.log.info(`[magic-mail]
|
|
234
|
+
async delete(documentId) {
|
|
235
|
+
strapi.log.info(`[magic-mail] [DELETE] Deleting template documentId: ${documentId}`);
|
|
242
236
|
|
|
243
|
-
|
|
244
|
-
const template = await this.findOne(id);
|
|
237
|
+
const template = await this.findOne(documentId);
|
|
245
238
|
if (!template) {
|
|
246
239
|
throw new Error('Template not found');
|
|
247
240
|
}
|
|
248
241
|
|
|
249
|
-
strapi.log.info(`[magic-mail]
|
|
242
|
+
strapi.log.info(`[magic-mail] [DELETE] Template: documentId=${template.documentId}, name="${template.name}"`);
|
|
250
243
|
|
|
251
|
-
// Delete all versions
|
|
252
|
-
|
|
244
|
+
// Delete all versions
|
|
245
|
+
const allVersions = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findMany({
|
|
253
246
|
filters: {
|
|
254
247
|
template: {
|
|
255
248
|
documentId: template.documentId,
|
|
@@ -257,47 +250,39 @@ module.exports = ({ strapi }) => ({
|
|
|
257
250
|
},
|
|
258
251
|
});
|
|
259
252
|
|
|
260
|
-
strapi.log.info(`[magic-mail]
|
|
253
|
+
strapi.log.info(`[magic-mail] [DELETE] Found ${allVersions.length} versions to delete`);
|
|
261
254
|
|
|
262
|
-
// Delete each version
|
|
263
255
|
for (const version of allVersions) {
|
|
264
256
|
try {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
strapi.log.info(`[magic-mail] 🗑️ Deleted version #${version.versionNumber}`);
|
|
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}`);
|
|
270
261
|
} catch (versionError) {
|
|
271
|
-
|
|
262
|
+
strapi.log.warn(`[magic-mail] [WARNING] Failed to delete version: ${versionError.message}`);
|
|
272
263
|
}
|
|
273
264
|
}
|
|
274
265
|
|
|
275
266
|
// Delete template
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
strapi.log.info(`[magic-mail] ✅ Template "${template.name}" and ${allVersions.length} versions deleted`);
|
|
267
|
+
const result = await strapi.documents(EMAIL_TEMPLATE_UID).delete({ documentId });
|
|
279
268
|
|
|
269
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Template "${template.name}" and ${allVersions.length} versions deleted`);
|
|
280
270
|
return result;
|
|
281
271
|
},
|
|
282
272
|
|
|
283
273
|
/**
|
|
284
274
|
* Duplicate template
|
|
285
|
-
*
|
|
286
|
-
* Creates a copy of an existing template with " copy" suffix
|
|
287
|
-
* Does NOT copy versions, starts fresh
|
|
288
275
|
*/
|
|
289
|
-
async duplicate(
|
|
290
|
-
strapi.log.info(`[magic-mail]
|
|
276
|
+
async duplicate(documentId) {
|
|
277
|
+
strapi.log.info(`[magic-mail] [INFO] Duplicating template documentId: ${documentId}`);
|
|
291
278
|
|
|
292
|
-
|
|
293
|
-
const original = await this.findOne(id);
|
|
279
|
+
const original = await this.findOne(documentId);
|
|
294
280
|
if (!original) {
|
|
295
281
|
throw new Error('Template not found');
|
|
296
282
|
}
|
|
297
283
|
|
|
298
|
-
strapi.log.info(`[magic-mail]
|
|
284
|
+
strapi.log.info(`[magic-mail] [PACKAGE] Original template: documentId=${original.documentId}, name="${original.name}"`);
|
|
299
285
|
|
|
300
|
-
// Prepare duplicate data (remove system fields)
|
|
301
286
|
const duplicateData = {
|
|
302
287
|
name: `${original.name} copy`,
|
|
303
288
|
subject: original.subject,
|
|
@@ -307,15 +292,12 @@ module.exports = ({ strapi }) => ({
|
|
|
307
292
|
category: original.category,
|
|
308
293
|
tags: original.tags,
|
|
309
294
|
isActive: original.isActive,
|
|
310
|
-
// Generate new templateReferenceId (unique ID)
|
|
311
295
|
templateReferenceId: Date.now() + Math.floor(Math.random() * 1000),
|
|
312
296
|
};
|
|
313
297
|
|
|
314
|
-
// Create duplicate
|
|
315
298
|
const duplicated = await this.create(duplicateData);
|
|
316
299
|
|
|
317
|
-
strapi.log.info(`[magic-mail]
|
|
318
|
-
|
|
300
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Template duplicated: documentId=${duplicated.documentId}`);
|
|
319
301
|
return duplicated;
|
|
320
302
|
},
|
|
321
303
|
|
|
@@ -325,95 +307,57 @@ module.exports = ({ strapi }) => ({
|
|
|
325
307
|
|
|
326
308
|
/**
|
|
327
309
|
* Create a new version for a template
|
|
328
|
-
*
|
|
329
|
-
* CRITICAL: This is THE ONLY function that creates versions!
|
|
330
|
-
*
|
|
331
|
-
* Steps:
|
|
332
|
-
* 1. Verify template exists
|
|
333
|
-
* 2. Calculate next version number
|
|
334
|
-
* 3. Create version WITH template relation
|
|
335
|
-
*
|
|
336
|
-
* @param {number} templateId - Numeric ID of template
|
|
337
|
-
* @param {object} data - Version data (name, subject, bodyHtml, etc)
|
|
338
|
-
* @returns {object} Created version
|
|
339
310
|
*/
|
|
340
|
-
async createVersion(
|
|
341
|
-
strapi.log.info(`[magic-mail]
|
|
311
|
+
async createVersion(templateDocumentId, data) {
|
|
312
|
+
strapi.log.info(`[magic-mail] [SNAPSHOT] Creating version for template documentId: ${templateDocumentId}`);
|
|
342
313
|
|
|
343
314
|
// 1. Verify template exists
|
|
344
|
-
const template = await strapi.
|
|
315
|
+
const template = await strapi.documents(EMAIL_TEMPLATE_UID).findOne({
|
|
316
|
+
documentId: templateDocumentId,
|
|
317
|
+
});
|
|
345
318
|
if (!template) {
|
|
346
|
-
throw new Error(`Template ${
|
|
319
|
+
throw new Error(`Template ${templateDocumentId} not found`);
|
|
347
320
|
}
|
|
348
321
|
|
|
349
|
-
strapi.log.info(`[magic-mail]
|
|
322
|
+
strapi.log.info(`[magic-mail] [PACKAGE] Template found: documentId=${template.documentId}, name="${template.name}"`);
|
|
350
323
|
|
|
351
324
|
// 2. Calculate next version number
|
|
352
|
-
const existingVersions = await strapi.
|
|
325
|
+
const existingVersions = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findMany({
|
|
353
326
|
filters: {
|
|
354
327
|
template: {
|
|
355
|
-
|
|
328
|
+
documentId: templateDocumentId,
|
|
356
329
|
},
|
|
357
330
|
},
|
|
358
|
-
sort: { versionNumber: 'desc' },
|
|
331
|
+
sort: [{ versionNumber: 'desc' }],
|
|
359
332
|
});
|
|
360
333
|
|
|
361
334
|
const versionNumber = existingVersions.length > 0
|
|
362
335
|
? Math.max(...existingVersions.map(v => v.versionNumber || 0)) + 1
|
|
363
336
|
: 1;
|
|
364
337
|
|
|
365
|
-
strapi.log.info(`[magic-mail]
|
|
338
|
+
strapi.log.info(`[magic-mail] [STATS] Existing versions: ${existingVersions.length} → Next version: #${versionNumber}`);
|
|
366
339
|
|
|
367
340
|
// 3. Create version WITH template relation
|
|
368
|
-
|
|
369
|
-
const createdVersion = await strapi.entityService.create('plugin::magic-mail.email-template-version', {
|
|
341
|
+
const createdVersion = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).create({
|
|
370
342
|
data: {
|
|
371
343
|
versionNumber,
|
|
372
344
|
...data,
|
|
373
|
-
template:
|
|
374
|
-
connect: [templateId], // ✅ Use connect array for Strapi v5!
|
|
375
|
-
},
|
|
345
|
+
template: templateDocumentId, // Document Service handles relations with documentId
|
|
376
346
|
},
|
|
377
347
|
});
|
|
378
348
|
|
|
379
|
-
strapi.log.info(`[magic-mail]
|
|
380
|
-
|
|
381
|
-
// 4. Verify the relation was created by loading with populate
|
|
382
|
-
const verifiedVersion = await strapi.entityService.findOne(
|
|
383
|
-
'plugin::magic-mail.email-template-version',
|
|
384
|
-
createdVersion.id,
|
|
385
|
-
{
|
|
386
|
-
populate: ['template'],
|
|
387
|
-
}
|
|
388
|
-
);
|
|
389
|
-
|
|
390
|
-
strapi.log.info(
|
|
391
|
-
`[magic-mail] ✅ Version created successfully:\n` +
|
|
392
|
-
` - Version ID: ${createdVersion.id}\n` +
|
|
393
|
-
` - Version #: ${versionNumber}\n` +
|
|
394
|
-
` - Template ID: ${templateId}\n` +
|
|
395
|
-
` - Has template relation: ${!!verifiedVersion.template}\n` +
|
|
396
|
-
` - Template in DB: ${verifiedVersion.template?.id || 'NULL'}`
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
if (!verifiedVersion.template) {
|
|
400
|
-
strapi.log.error(
|
|
401
|
-
`[magic-mail] ❌ CRITICAL: Version ${createdVersion.id} was created but template relation was NOT set!\n` +
|
|
402
|
-
` This is a Strapi v5 relation bug. Investigating...`
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return verifiedVersion;
|
|
349
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Version created: documentId=${createdVersion.documentId}, v${versionNumber}`);
|
|
350
|
+
return createdVersion;
|
|
407
351
|
},
|
|
408
352
|
|
|
409
353
|
/**
|
|
410
354
|
* Get all versions for a template
|
|
411
355
|
*/
|
|
412
|
-
async getVersions(
|
|
413
|
-
strapi.log.info(`[magic-mail] 📜 Fetching versions for template
|
|
356
|
+
async getVersions(templateDocumentId) {
|
|
357
|
+
strapi.log.info(`[magic-mail] 📜 Fetching versions for template documentId: ${templateDocumentId}`);
|
|
414
358
|
|
|
415
|
-
|
|
416
|
-
|
|
359
|
+
const template = await strapi.documents(EMAIL_TEMPLATE_UID).findOne({
|
|
360
|
+
documentId: templateDocumentId,
|
|
417
361
|
populate: ['versions'],
|
|
418
362
|
});
|
|
419
363
|
|
|
@@ -421,46 +365,35 @@ module.exports = ({ strapi }) => ({
|
|
|
421
365
|
throw new Error('Template not found');
|
|
422
366
|
}
|
|
423
367
|
|
|
424
|
-
strapi.log.info(`[magic-mail]
|
|
368
|
+
strapi.log.info(`[magic-mail] [PACKAGE] Template has ${template.versions?.length || 0} versions`);
|
|
425
369
|
|
|
426
|
-
// OPTION 1: Return versions from template populate (most reliable!)
|
|
427
370
|
if (template.versions && template.versions.length > 0) {
|
|
428
|
-
// Sort by version number
|
|
429
371
|
const sortedVersions = [...template.versions].sort((a, b) => b.versionNumber - a.versionNumber);
|
|
430
|
-
|
|
431
|
-
strapi.log.info(`[magic-mail] ✅ Returning ${sortedVersions.length} versions from template populate`);
|
|
432
372
|
return sortedVersions;
|
|
433
373
|
}
|
|
434
374
|
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const versions = await strapi.entityService.findMany('plugin::magic-mail.email-template-version', {
|
|
375
|
+
// Fallback: find via filter
|
|
376
|
+
const versions = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findMany({
|
|
439
377
|
filters: {
|
|
440
378
|
template: {
|
|
441
|
-
|
|
379
|
+
documentId: templateDocumentId,
|
|
442
380
|
},
|
|
443
381
|
},
|
|
444
|
-
sort: { versionNumber: 'desc' },
|
|
445
|
-
populate: ['template'],
|
|
382
|
+
sort: [{ versionNumber: 'desc' }],
|
|
446
383
|
});
|
|
447
384
|
|
|
448
|
-
strapi.log.info(`[magic-mail]
|
|
449
|
-
|
|
385
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Found ${versions.length} versions`);
|
|
450
386
|
return versions;
|
|
451
387
|
},
|
|
452
388
|
|
|
453
389
|
/**
|
|
454
390
|
* Restore template from a specific version
|
|
455
|
-
*
|
|
456
|
-
* Updates the template with data from the version
|
|
457
|
-
* This will create a NEW version snapshot (via update logic)
|
|
458
391
|
*/
|
|
459
|
-
async restoreVersion(
|
|
460
|
-
strapi.log.info(`[magic-mail]
|
|
392
|
+
async restoreVersion(templateDocumentId, versionDocumentId) {
|
|
393
|
+
strapi.log.info(`[magic-mail] [RESTORE] Restoring template ${templateDocumentId} from version ${versionDocumentId}`);
|
|
461
394
|
|
|
462
|
-
|
|
463
|
-
|
|
395
|
+
const version = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findOne({
|
|
396
|
+
documentId: versionDocumentId,
|
|
464
397
|
populate: ['template'],
|
|
465
398
|
});
|
|
466
399
|
|
|
@@ -468,37 +401,13 @@ module.exports = ({ strapi }) => ({
|
|
|
468
401
|
throw new Error('Version not found');
|
|
469
402
|
}
|
|
470
403
|
|
|
471
|
-
strapi.log.info(`[magic-mail] 📦 Version found: ID=${version.id}, versionNumber=${version.versionNumber}, has template: ${!!version.template}`);
|
|
472
|
-
|
|
473
404
|
// Verify version belongs to this template
|
|
474
|
-
|
|
475
|
-
if (version.template?.id) {
|
|
476
|
-
if (version.template.id !== parseInt(templateId)) {
|
|
477
|
-
strapi.log.error(`[magic-mail] ❌ Version ${versionId} belongs to template ${version.template.id}, not ${templateId}`);
|
|
478
|
-
throw new Error('Version does not belong to this template');
|
|
479
|
-
}
|
|
480
|
-
strapi.log.info(`[magic-mail] ✅ Version has correct template relation`);
|
|
481
|
-
} else {
|
|
482
|
-
// For old versions (without relation): verify via template's versions array
|
|
483
|
-
strapi.log.warn(`[magic-mail] ⚠️ Version ${versionId} has no template relation, checking template's versions array...`);
|
|
484
|
-
|
|
485
|
-
const template = await strapi.entityService.findOne('plugin::magic-mail.email-template', templateId, {
|
|
486
|
-
populate: ['versions'],
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const versionExists = template.versions && template.versions.some(v => v.id === parseInt(versionId));
|
|
490
|
-
|
|
491
|
-
if (!versionExists) {
|
|
492
|
-
strapi.log.error(`[magic-mail] ❌ Version ${versionId} not found in template ${templateId} versions array`);
|
|
405
|
+
if (version.template?.documentId !== templateDocumentId) {
|
|
493
406
|
throw new Error('Version does not belong to this template');
|
|
494
407
|
}
|
|
495
408
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// Update template with version data
|
|
500
|
-
// This will automatically create a snapshot version via update()
|
|
501
|
-
const restored = await this.update(templateId, {
|
|
409
|
+
// Update template with version data (creates new version via update)
|
|
410
|
+
const restored = await this.update(templateDocumentId, {
|
|
502
411
|
name: version.name,
|
|
503
412
|
subject: version.subject,
|
|
504
413
|
design: version.design,
|
|
@@ -507,22 +416,18 @@ module.exports = ({ strapi }) => ({
|
|
|
507
416
|
tags: version.tags,
|
|
508
417
|
});
|
|
509
418
|
|
|
510
|
-
strapi.log.info(`[magic-mail]
|
|
511
|
-
|
|
419
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Template restored from version #${version.versionNumber}`);
|
|
512
420
|
return restored;
|
|
513
421
|
},
|
|
514
422
|
|
|
515
423
|
/**
|
|
516
424
|
* Delete a single version
|
|
517
|
-
*
|
|
518
|
-
* @param {number} templateId - Template ID (for verification)
|
|
519
|
-
* @param {number} versionId - Version ID to delete
|
|
520
425
|
*/
|
|
521
|
-
async deleteVersion(
|
|
522
|
-
strapi.log.info(`[magic-mail]
|
|
426
|
+
async deleteVersion(templateDocumentId, versionDocumentId) {
|
|
427
|
+
strapi.log.info(`[magic-mail] [DELETE] Deleting version ${versionDocumentId}`);
|
|
523
428
|
|
|
524
|
-
|
|
525
|
-
|
|
429
|
+
const version = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findOne({
|
|
430
|
+
documentId: versionDocumentId,
|
|
526
431
|
populate: ['template'],
|
|
527
432
|
});
|
|
528
433
|
|
|
@@ -530,48 +435,26 @@ module.exports = ({ strapi }) => ({
|
|
|
530
435
|
throw new Error('Version not found');
|
|
531
436
|
}
|
|
532
437
|
|
|
533
|
-
|
|
534
|
-
if (version.template?.id) {
|
|
535
|
-
if (version.template.id !== parseInt(templateId)) {
|
|
536
|
-
strapi.log.error(`[magic-mail] ❌ Version ${versionId} belongs to template ${version.template.id}, not ${templateId}`);
|
|
537
|
-
throw new Error('Version does not belong to this template');
|
|
538
|
-
}
|
|
539
|
-
strapi.log.info(`[magic-mail] ✅ Version has correct template relation`);
|
|
540
|
-
} else {
|
|
541
|
-
// Check via template's versions array for old versions
|
|
542
|
-
strapi.log.warn(`[magic-mail] ⚠️ Version ${versionId} has no template relation, checking template's versions array...`);
|
|
543
|
-
|
|
544
|
-
const template = await strapi.entityService.findOne('plugin::magic-mail.email-template', templateId, {
|
|
545
|
-
populate: ['versions'],
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
const versionExists = template.versions && template.versions.some(v => v.id === parseInt(versionId));
|
|
549
|
-
if (!versionExists) {
|
|
550
|
-
strapi.log.error(`[magic-mail] ❌ Version ${versionId} not found in template ${templateId} versions array`);
|
|
438
|
+
if (version.template?.documentId !== templateDocumentId) {
|
|
551
439
|
throw new Error('Version does not belong to this template');
|
|
552
440
|
}
|
|
553
|
-
|
|
554
|
-
strapi.log.info(`[magic-mail] ✅ Version ${versionId} found in template's versions array`);
|
|
555
|
-
}
|
|
556
441
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
strapi.log.info(`[magic-mail] ✅ Version ${versionId} (v${version.versionNumber}) deleted successfully`);
|
|
442
|
+
await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({
|
|
443
|
+
documentId: versionDocumentId
|
|
444
|
+
});
|
|
561
445
|
|
|
446
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Version v${version.versionNumber} deleted`);
|
|
562
447
|
return { success: true, message: 'Version deleted' };
|
|
563
448
|
},
|
|
564
449
|
|
|
565
450
|
/**
|
|
566
451
|
* Delete all versions for a template
|
|
567
|
-
*
|
|
568
|
-
* @param {number} templateId - Template ID
|
|
569
452
|
*/
|
|
570
|
-
async deleteAllVersions(
|
|
571
|
-
strapi.log.info(`[magic-mail]
|
|
453
|
+
async deleteAllVersions(templateDocumentId) {
|
|
454
|
+
strapi.log.info(`[magic-mail] [DELETE] Deleting all versions for template ${templateDocumentId}`);
|
|
572
455
|
|
|
573
|
-
|
|
574
|
-
|
|
456
|
+
const template = await strapi.documents(EMAIL_TEMPLATE_UID).findOne({
|
|
457
|
+
documentId: templateDocumentId,
|
|
575
458
|
populate: ['versions'],
|
|
576
459
|
});
|
|
577
460
|
|
|
@@ -580,36 +463,24 @@ module.exports = ({ strapi }) => ({
|
|
|
580
463
|
}
|
|
581
464
|
|
|
582
465
|
const versionCount = template.versions?.length || 0;
|
|
583
|
-
strapi.log.info(`[magic-mail] 📊 Found ${versionCount} versions to delete`);
|
|
584
|
-
|
|
585
466
|
if (versionCount === 0) {
|
|
586
467
|
return { success: true, message: 'No versions to delete', deletedCount: 0 };
|
|
587
468
|
}
|
|
588
469
|
|
|
589
|
-
// Delete each version
|
|
590
470
|
let deletedCount = 0;
|
|
591
|
-
const errors = [];
|
|
592
|
-
|
|
593
471
|
for (const version of template.versions) {
|
|
594
472
|
try {
|
|
595
|
-
await strapi.
|
|
473
|
+
await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({
|
|
474
|
+
documentId: version.documentId
|
|
475
|
+
});
|
|
596
476
|
deletedCount++;
|
|
597
|
-
strapi.log.info(`[magic-mail] 🗑️ Deleted version #${version.versionNumber} (ID: ${version.id})`);
|
|
598
477
|
} catch (error) {
|
|
599
|
-
strapi.log.error(`[magic-mail]
|
|
600
|
-
errors.push({ versionId: version.id, error: error.message });
|
|
478
|
+
strapi.log.error(`[magic-mail] [ERROR] Failed to delete version: ${error.message}`);
|
|
601
479
|
}
|
|
602
480
|
}
|
|
603
481
|
|
|
604
|
-
strapi.log.info(`[magic-mail]
|
|
605
|
-
|
|
606
|
-
return {
|
|
607
|
-
success: true,
|
|
608
|
-
message: `Deleted ${deletedCount} of ${versionCount} versions`,
|
|
609
|
-
deletedCount,
|
|
610
|
-
failedCount: versionCount - deletedCount,
|
|
611
|
-
errors: errors.length > 0 ? errors : undefined
|
|
612
|
-
};
|
|
482
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Deleted ${deletedCount}/${versionCount} versions`);
|
|
483
|
+
return { success: true, deletedCount };
|
|
613
484
|
},
|
|
614
485
|
|
|
615
486
|
// ============================================================
|
|
@@ -618,10 +489,6 @@ module.exports = ({ strapi }) => ({
|
|
|
618
489
|
|
|
619
490
|
/**
|
|
620
491
|
* Render template with dynamic data using Mustache
|
|
621
|
-
*
|
|
622
|
-
* @param {number} templateReferenceId - Template reference ID
|
|
623
|
-
* @param {object} data - Dynamic data for Mustache
|
|
624
|
-
* @returns {object} Rendered HTML, text, and subject
|
|
625
492
|
*/
|
|
626
493
|
async renderTemplate(templateReferenceId, data = {}) {
|
|
627
494
|
const template = await this.findByReferenceId(templateReferenceId);
|
|
@@ -636,22 +503,19 @@ module.exports = ({ strapi }) => ({
|
|
|
636
503
|
|
|
637
504
|
let { bodyHtml = '', bodyText = '', subject = '' } = template;
|
|
638
505
|
|
|
639
|
-
// Convert <% %> to {{ }} for Mustache
|
|
506
|
+
// Convert <% %> to {{ }} for Mustache
|
|
640
507
|
bodyHtml = bodyHtml.replace(/<%/g, '{{').replace(/%>/g, '}}');
|
|
641
508
|
bodyText = bodyText.replace(/<%/g, '{{').replace(/%>/g, '}}');
|
|
642
509
|
subject = subject.replace(/<%/g, '{{').replace(/%>/g, '}}');
|
|
643
510
|
|
|
644
|
-
// Generate text version from HTML if not provided
|
|
645
511
|
if ((!bodyText || !bodyText.length) && bodyHtml && bodyHtml.length) {
|
|
646
512
|
bodyText = convertHtmlToText(bodyHtml, { wordwrap: 130 });
|
|
647
513
|
}
|
|
648
514
|
|
|
649
|
-
// Decode HTML entities
|
|
650
515
|
const decodedHtml = decode(bodyHtml);
|
|
651
516
|
const decodedText = decode(bodyText);
|
|
652
517
|
const decodedSubject = decode(subject);
|
|
653
518
|
|
|
654
|
-
// Render with Mustache
|
|
655
519
|
const renderedHtml = Mustache.render(decodedHtml, data);
|
|
656
520
|
const renderedText = Mustache.render(decodedText, data);
|
|
657
521
|
const renderedSubject = Mustache.render(decodedSubject, data);
|
|
@@ -666,19 +530,19 @@ module.exports = ({ strapi }) => ({
|
|
|
666
530
|
},
|
|
667
531
|
|
|
668
532
|
// ============================================================
|
|
669
|
-
// IMPORT/EXPORT
|
|
533
|
+
// IMPORT/EXPORT
|
|
670
534
|
// ============================================================
|
|
671
535
|
|
|
672
536
|
/**
|
|
673
537
|
* Export templates as JSON
|
|
674
538
|
*/
|
|
675
|
-
async exportTemplates(
|
|
676
|
-
strapi.log.info('[magic-mail]
|
|
539
|
+
async exportTemplates(templateDocumentIds = []) {
|
|
540
|
+
strapi.log.info('[magic-mail] [EXPORT] Exporting templates...');
|
|
677
541
|
|
|
678
542
|
let templates;
|
|
679
|
-
if (
|
|
680
|
-
templates = await strapi.
|
|
681
|
-
filters: {
|
|
543
|
+
if (templateDocumentIds.length > 0) {
|
|
544
|
+
templates = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
|
|
545
|
+
filters: { documentId: { $in: templateDocumentIds } },
|
|
682
546
|
});
|
|
683
547
|
} else {
|
|
684
548
|
templates = await this.findAll();
|
|
@@ -695,7 +559,7 @@ module.exports = ({ strapi }) => ({
|
|
|
695
559
|
tags: template.tags,
|
|
696
560
|
}));
|
|
697
561
|
|
|
698
|
-
strapi.log.info(`[magic-mail]
|
|
562
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Exported ${exported.length} templates`);
|
|
699
563
|
return exported;
|
|
700
564
|
},
|
|
701
565
|
|
|
@@ -703,7 +567,7 @@ module.exports = ({ strapi }) => ({
|
|
|
703
567
|
* Import templates from JSON
|
|
704
568
|
*/
|
|
705
569
|
async importTemplates(templates) {
|
|
706
|
-
strapi.log.info(`[magic-mail]
|
|
570
|
+
strapi.log.info(`[magic-mail] [IMPORT] Importing ${templates.length} templates...`);
|
|
707
571
|
|
|
708
572
|
const results = [];
|
|
709
573
|
|
|
@@ -712,15 +576,11 @@ module.exports = ({ strapi }) => ({
|
|
|
712
576
|
const existing = await this.findByReferenceId(templateData.templateReferenceId);
|
|
713
577
|
|
|
714
578
|
if (existing) {
|
|
715
|
-
|
|
716
|
-
const updated = await this.update(existing.id, templateData);
|
|
579
|
+
const updated = await this.update(existing.documentId, templateData);
|
|
717
580
|
results.push({ success: true, action: 'updated', template: updated });
|
|
718
|
-
strapi.log.info(`[magic-mail] ✅ Updated template: ${templateData.name}`);
|
|
719
581
|
} else {
|
|
720
|
-
// Create new
|
|
721
582
|
const created = await this.create(templateData);
|
|
722
583
|
results.push({ success: true, action: 'created', template: created });
|
|
723
|
-
strapi.log.info(`[magic-mail] ✅ Created template: ${templateData.name}`);
|
|
724
584
|
}
|
|
725
585
|
} catch (error) {
|
|
726
586
|
results.push({
|
|
@@ -729,13 +589,9 @@ module.exports = ({ strapi }) => ({
|
|
|
729
589
|
error: error.message,
|
|
730
590
|
templateName: templateData.name,
|
|
731
591
|
});
|
|
732
|
-
strapi.log.error(`[magic-mail] ❌ Failed to import ${templateData.name}: ${error.message}`);
|
|
733
592
|
}
|
|
734
593
|
}
|
|
735
594
|
|
|
736
|
-
const successful = results.filter(r => r.success).length;
|
|
737
|
-
strapi.log.info(`[magic-mail] ✅ Import completed: ${successful}/${templates.length} templates`);
|
|
738
|
-
|
|
739
595
|
return results;
|
|
740
596
|
},
|
|
741
597
|
|
|
@@ -747,14 +603,13 @@ module.exports = ({ strapi }) => ({
|
|
|
747
603
|
* Get template statistics
|
|
748
604
|
*/
|
|
749
605
|
async getStats() {
|
|
750
|
-
const allTemplates = await strapi.
|
|
606
|
+
const allTemplates = await strapi.documents(EMAIL_TEMPLATE_UID).findMany({
|
|
751
607
|
fields: ['isActive', 'category'],
|
|
752
608
|
});
|
|
753
609
|
|
|
754
610
|
const total = allTemplates.length;
|
|
755
611
|
const active = allTemplates.filter(t => t.isActive === true).length;
|
|
756
612
|
|
|
757
|
-
// Group by category
|
|
758
613
|
const categoryMap = allTemplates.reduce((acc, template) => {
|
|
759
614
|
const category = template.category || 'custom';
|
|
760
615
|
acc[category] = (acc[category] || 0) + 1;
|
|
@@ -783,12 +638,9 @@ module.exports = ({ strapi }) => ({
|
|
|
783
638
|
// ============================================================
|
|
784
639
|
|
|
785
640
|
/**
|
|
786
|
-
* Get Strapi core email template
|
|
787
|
-
*
|
|
788
|
-
* These are stored in users-permissions plugin store
|
|
641
|
+
* Get Strapi core email template
|
|
789
642
|
*/
|
|
790
643
|
async getCoreTemplate(coreEmailType) {
|
|
791
|
-
// Validate type
|
|
792
644
|
if (!['reset-password', 'email-confirmation'].includes(coreEmailType)) {
|
|
793
645
|
throw new Error('Invalid core email type');
|
|
794
646
|
}
|
|
@@ -802,7 +654,6 @@ module.exports = ({ strapi }) => ({
|
|
|
802
654
|
name: 'users-permissions',
|
|
803
655
|
});
|
|
804
656
|
|
|
805
|
-
// Get email config
|
|
806
657
|
const emailConfig = await pluginStore.get({ key: 'email' });
|
|
807
658
|
|
|
808
659
|
let data = null;
|
|
@@ -810,12 +661,11 @@ module.exports = ({ strapi }) => ({
|
|
|
810
661
|
data = emailConfig[pluginStoreEmailKey];
|
|
811
662
|
}
|
|
812
663
|
|
|
813
|
-
|
|
814
|
-
const messageConverted = data && data.options && data.options.message
|
|
664
|
+
const messageConverted = data?.options?.message
|
|
815
665
|
? data.options.message.replace(/<%|<%/g, '{{').replace(/%>|%>/g, '}}')
|
|
816
666
|
: '';
|
|
817
667
|
|
|
818
|
-
|
|
668
|
+
const subjectConverted = data?.options?.object
|
|
819
669
|
? data.options.object.replace(/<%|<%/g, '{{').replace(/%>|%>/g, '}}')
|
|
820
670
|
: '';
|
|
821
671
|
|
|
@@ -834,7 +684,6 @@ module.exports = ({ strapi }) => ({
|
|
|
834
684
|
* Update Strapi core email template
|
|
835
685
|
*/
|
|
836
686
|
async updateCoreTemplate(coreEmailType, data) {
|
|
837
|
-
// Validate type
|
|
838
687
|
if (!['reset-password', 'email-confirmation'].includes(coreEmailType)) {
|
|
839
688
|
throw new Error('Invalid core email type');
|
|
840
689
|
}
|
|
@@ -850,11 +699,10 @@ module.exports = ({ strapi }) => ({
|
|
|
850
699
|
|
|
851
700
|
const emailsConfig = await pluginStore.get({ key: 'email' });
|
|
852
701
|
|
|
853
|
-
// Convert {{ }} back to <%= %> for Strapi
|
|
854
702
|
emailsConfig[pluginStoreEmailKey] = {
|
|
855
703
|
...emailsConfig[pluginStoreEmailKey],
|
|
856
704
|
options: {
|
|
857
|
-
|
|
705
|
+
...(emailsConfig[pluginStoreEmailKey]?.options || {}),
|
|
858
706
|
message: data.message.replace(/{{/g, '<%').replace(/}}/g, '%>'),
|
|
859
707
|
object: data.subject.replace(/{{/g, '<%').replace(/}}/g, '%>'),
|
|
860
708
|
},
|
|
@@ -863,8 +711,7 @@ module.exports = ({ strapi }) => ({
|
|
|
863
711
|
|
|
864
712
|
await pluginStore.set({ key: 'email', value: emailsConfig });
|
|
865
713
|
|
|
866
|
-
strapi.log.info(`[magic-mail]
|
|
867
|
-
|
|
714
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Core email template updated: ${pluginStoreEmailKey}`);
|
|
868
715
|
return { message: 'Saved' };
|
|
869
716
|
},
|
|
870
717
|
});
|