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.
@@ -3,8 +3,7 @@
3
3
  *
4
4
  * Handles email template creation, updates, versioning, and rendering
5
5
  *
6
- * CRITICAL: Uses ONLY entityService for all database operations
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.entityService.findMany('plugin::magic-mail.email-template', {
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 with populated versions
93
+ * Get template by numeric ID (for backward compatibility)
81
94
  */
82
- async findOne(id) {
83
- return strapi.entityService.findOne('plugin::magic-mail.email-template', id, {
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.entityService.findMany('plugin::magic-mail.email-template', {
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] 📝 Creating new template...');
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
- const currentCount = await strapi.db
118
- .query('plugin::magic-mail.email-template')
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.entityService.create('plugin::magic-mail.email-template', {
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] Template created: ID=${template.id}, name="${template.name}"`);
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] 💾 Creating initial version...');
161
+ strapi.log.info('[magic-mail] [SAVE] Creating initial version...');
153
162
 
154
- await this.createVersion(template.id, {
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] Initial version created');
172
+ strapi.log.info('[magic-mail] [SUCCESS] Initial version created');
164
173
  } else {
165
- strapi.log.info('[magic-mail] ⏭️ Versioning not enabled, skipping initial version');
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(id, data) {
182
- strapi.log.info(`[magic-mail] 🔄 Updating template ID: ${id}`);
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(id);
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] 📋 Found template: ID=${template.id}, name="${template.name}"`);
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] 💾 Creating version snapshot before update...');
201
+ strapi.log.info('[magic-mail] [SAVE] Creating version snapshot before update...');
200
202
 
201
- // Save CURRENT state as new version
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] Version snapshot created');
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] ⚠️ Removed versions field from update data to prevent relation overwrite');
219
+ strapi.log.warn('[magic-mail] [WARNING] Removed versions field from update data');
225
220
  }
226
221
 
227
- const updated = await strapi.entityService.update('plugin::magic-mail.email-template', id, {
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] Template updated: ID=${updated.id}, versions preserved`);
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(id) {
241
- strapi.log.info(`[magic-mail] 🗑️ Deleting template ID: ${id}`);
234
+ async delete(documentId) {
235
+ strapi.log.info(`[magic-mail] [DELETE] Deleting template documentId: ${documentId}`);
242
236
 
243
- // Get template first
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] 🗑️ Template: ID=${template.id}, name="${template.name}"`);
242
+ strapi.log.info(`[magic-mail] [DELETE] Template: documentId=${template.documentId}, name="${template.name}"`);
250
243
 
251
- // Delete all versions using Document Service (supports documentId filtering)
252
- const allVersions = await strapi.documents('plugin::magic-mail.email-template-version').findMany({
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] 🗑️ Found ${allVersions.length} versions to delete`);
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
- await strapi
266
- .documents('plugin::magic-mail.email-template-version')
267
- .delete({ documentId: version.documentId });
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
- strapi.log.warn(`[magic-mail] ⚠️ Failed to delete version ${version.id}: ${versionError.message}`);
262
+ strapi.log.warn(`[magic-mail] [WARNING] Failed to delete version: ${versionError.message}`);
272
263
  }
273
264
  }
274
265
 
275
266
  // Delete template
276
- const result = await strapi.entityService.delete('plugin::magic-mail.email-template', id);
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(id) {
290
- strapi.log.info(`[magic-mail] 📋 Duplicating template ID: ${id}`);
276
+ async duplicate(documentId) {
277
+ strapi.log.info(`[magic-mail] [INFO] Duplicating template documentId: ${documentId}`);
291
278
 
292
- // Get original template
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] 📦 Original template: ID=${original.id}, name="${original.name}"`);
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] Template duplicated: ID=${duplicated.id}, name="${duplicated.name}"`);
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(templateId, data) {
341
- strapi.log.info(`[magic-mail] 📸 Creating version for template ID: ${templateId}`);
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.entityService.findOne('plugin::magic-mail.email-template', templateId);
315
+ const template = await strapi.documents(EMAIL_TEMPLATE_UID).findOne({
316
+ documentId: templateDocumentId,
317
+ });
345
318
  if (!template) {
346
- throw new Error(`Template ${templateId} not found`);
319
+ throw new Error(`Template ${templateDocumentId} not found`);
347
320
  }
348
321
 
349
- strapi.log.info(`[magic-mail] 📦 Template found: ID=${template.id}, name="${template.name}"`);
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.entityService.findMany('plugin::magic-mail.email-template-version', {
325
+ const existingVersions = await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).findMany({
353
326
  filters: {
354
327
  template: {
355
- id: templateId, // Use numeric ID in filter
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] 📊 Existing versions: ${existingVersions.length} → Next version: #${versionNumber}`);
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
- // In Strapi v5, we need to use connect for relations!
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] 📝 Version created with connect: ID=${createdVersion.id}`);
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(templateId) {
413
- strapi.log.info(`[magic-mail] 📜 Fetching versions for template ID: ${templateId}`);
356
+ async getVersions(templateDocumentId) {
357
+ strapi.log.info(`[magic-mail] 📜 Fetching versions for template documentId: ${templateDocumentId}`);
414
358
 
415
- // Verify template exists
416
- const template = await strapi.entityService.findOne('plugin::magic-mail.email-template', templateId, {
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] 📦 Template has ${template.versions?.length || 0} versions in relation`);
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
- // OPTION 2: Fallback - try finding with filter (shouldn't be needed)
436
- strapi.log.warn(`[magic-mail] ⚠️ Template has no populated versions, trying filter...`);
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
- id: templateId,
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] Found ${versions.length} versions via filter`);
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(templateId, versionId) {
460
- strapi.log.info(`[magic-mail] 🔄 Restoring template ${templateId} from version ${versionId}`);
392
+ async restoreVersion(templateDocumentId, versionDocumentId) {
393
+ strapi.log.info(`[magic-mail] [RESTORE] Restoring template ${templateDocumentId} from version ${versionDocumentId}`);
461
394
 
462
- // Get version
463
- const version = await strapi.entityService.findOne('plugin::magic-mail.email-template-version', versionId, {
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
- // For new versions (with relation): check template.id
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
- strapi.log.info(`[magic-mail] Version ${versionId} found in template's versions array (old version without relation)`);
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] Template restored from version #${version.versionNumber}`);
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(templateId, versionId) {
522
- strapi.log.info(`[magic-mail] 🗑️ Deleting version ${versionId} from template ${templateId}`);
426
+ async deleteVersion(templateDocumentId, versionDocumentId) {
427
+ strapi.log.info(`[magic-mail] [DELETE] Deleting version ${versionDocumentId}`);
523
428
 
524
- // Get version
525
- const version = await strapi.entityService.findOne('plugin::magic-mail.email-template-version', versionId, {
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
- // Verify version belongs to this template (same logic as restore)
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
- // Delete version
558
- await strapi.entityService.delete('plugin::magic-mail.email-template-version', versionId);
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(templateId) {
571
- strapi.log.info(`[magic-mail] 🗑️ Deleting all versions for template ${templateId}`);
453
+ async deleteAllVersions(templateDocumentId) {
454
+ strapi.log.info(`[magic-mail] [DELETE] Deleting all versions for template ${templateDocumentId}`);
572
455
 
573
- // Get template with versions
574
- const template = await strapi.entityService.findOne('plugin::magic-mail.email-template', templateId, {
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.entityService.delete('plugin::magic-mail.email-template-version', version.id);
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] Failed to delete version ${version.id}: ${error.message}`);
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] Deleted ${deletedCount}/${versionCount} versions`);
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 (backward compatibility)
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 (Advanced+ License)
533
+ // IMPORT/EXPORT
670
534
  // ============================================================
671
535
 
672
536
  /**
673
537
  * Export templates as JSON
674
538
  */
675
- async exportTemplates(templateIds = []) {
676
- strapi.log.info('[magic-mail] 📤 Exporting templates...');
539
+ async exportTemplates(templateDocumentIds = []) {
540
+ strapi.log.info('[magic-mail] [EXPORT] Exporting templates...');
677
541
 
678
542
  let templates;
679
- if (templateIds.length > 0) {
680
- templates = await strapi.entityService.findMany('plugin::magic-mail.email-template', {
681
- filters: { id: { $in: templateIds } },
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] Exported ${exported.length} templates`);
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] 📥 Importing ${templates.length} templates...`);
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
- // Update existing
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.entityService.findMany('plugin::magic-mail.email-template', {
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 (reset-password, email-confirmation)
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
- // Convert <%= %> to {{ }} for Mustache
814
- const messageConverted = data && data.options && data.options.message
664
+ const messageConverted = data?.options?.message
815
665
  ? data.options.message.replace(/<%|&#x3C;%/g, '{{').replace(/%>|%&#x3E;/g, '}}')
816
666
  : '';
817
667
 
818
- const subjectConverted = data && data.options && data.options.object
668
+ const subjectConverted = data?.options?.object
819
669
  ? data.options.object.replace(/<%|&#x3C;%/g, '{{').replace(/%>|%&#x3E;/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
- ...(emailsConfig[pluginStoreEmailKey] ? emailsConfig[pluginStoreEmailKey].options : {}),
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] Core email template updated: ${pluginStoreEmailKey}`);
867
-
714
+ strapi.log.info(`[magic-mail] [SUCCESS] Core email template updated: ${pluginStoreEmailKey}`);
868
715
  return { message: 'Saved' };
869
716
  },
870
717
  });