strapi-plugin-magic-mail 1.0.1
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/COPYRIGHT_NOTICE.txt +13 -0
- package/LICENSE +22 -0
- package/README.md +1420 -0
- package/admin/jsconfig.json +10 -0
- package/admin/src/components/AddAccountModal.jsx +1943 -0
- package/admin/src/components/Initializer.jsx +14 -0
- package/admin/src/components/LicenseGuard.jsx +475 -0
- package/admin/src/components/PluginIcon.jsx +5 -0
- package/admin/src/hooks/useAuthRefresh.js +44 -0
- package/admin/src/hooks/useLicense.js +158 -0
- package/admin/src/index.js +86 -0
- package/admin/src/pages/Analytics.jsx +762 -0
- package/admin/src/pages/App.jsx +111 -0
- package/admin/src/pages/EmailDesigner/EditorPage.jsx +1405 -0
- package/admin/src/pages/EmailDesigner/TemplateList.jsx +1807 -0
- package/admin/src/pages/HomePage.jsx +1233 -0
- package/admin/src/pages/LicensePage.jsx +424 -0
- package/admin/src/pages/RoutingRules.jsx +1141 -0
- package/admin/src/pages/Settings.jsx +603 -0
- package/admin/src/pluginId.js +3 -0
- package/admin/src/translations/de.json +71 -0
- package/admin/src/translations/en.json +70 -0
- package/admin/src/translations/es.json +71 -0
- package/admin/src/translations/fr.json +71 -0
- package/admin/src/translations/pt.json +71 -0
- package/admin/src/utils/fetchWithRetry.js +123 -0
- package/admin/src/utils/getTranslation.js +5 -0
- package/dist/_chunks/App-B-Gp4Vbr.js +7568 -0
- package/dist/_chunks/App-BymMjoGM.mjs +7543 -0
- package/dist/_chunks/LicensePage-Bl02myMx.mjs +342 -0
- package/dist/_chunks/LicensePage-CJXwPnEe.js +344 -0
- package/dist/_chunks/Settings-C_TmKwcz.mjs +400 -0
- package/dist/_chunks/Settings-zuFQ3pnn.js +402 -0
- package/dist/_chunks/de-CN-G9j1S.js +64 -0
- package/dist/_chunks/de-DS04rP54.mjs +64 -0
- package/dist/_chunks/en-BDc7Jk8u.js +64 -0
- package/dist/_chunks/en-BEFQJXvR.mjs +64 -0
- package/dist/_chunks/es-BpV1MIdm.js +64 -0
- package/dist/_chunks/es-DQHwzPpP.mjs +64 -0
- package/dist/_chunks/fr-BG1WfEVm.mjs +64 -0
- package/dist/_chunks/fr-vpziIpRp.js +64 -0
- package/dist/_chunks/pt-CMoGrOib.mjs +64 -0
- package/dist/_chunks/pt-ODpAhDNa.js +64 -0
- package/dist/admin/index.js +89 -0
- package/dist/admin/index.mjs +90 -0
- package/dist/server/index.js +6214 -0
- package/dist/server/index.mjs +6208 -0
- package/package.json +113 -0
- package/server/jsconfig.json +10 -0
- package/server/src/bootstrap.js +153 -0
- package/server/src/config/features.js +260 -0
- package/server/src/config/index.js +6 -0
- package/server/src/content-types/email-account/schema.json +93 -0
- package/server/src/content-types/email-event/index.js +8 -0
- package/server/src/content-types/email-event/schema.json +57 -0
- package/server/src/content-types/email-link/index.js +8 -0
- package/server/src/content-types/email-link/schema.json +49 -0
- package/server/src/content-types/email-log/index.js +8 -0
- package/server/src/content-types/email-log/schema.json +106 -0
- package/server/src/content-types/email-template/schema.json +74 -0
- package/server/src/content-types/email-template-version/schema.json +60 -0
- package/server/src/content-types/index.js +33 -0
- package/server/src/content-types/routing-rule/schema.json +59 -0
- package/server/src/controllers/accounts.js +220 -0
- package/server/src/controllers/analytics.js +347 -0
- package/server/src/controllers/controller.js +26 -0
- package/server/src/controllers/email-designer.js +474 -0
- package/server/src/controllers/index.js +21 -0
- package/server/src/controllers/license.js +267 -0
- package/server/src/controllers/oauth.js +474 -0
- package/server/src/controllers/routing-rules.js +122 -0
- package/server/src/controllers/test.js +383 -0
- package/server/src/destroy.js +23 -0
- package/server/src/index.js +25 -0
- package/server/src/middlewares/index.js +3 -0
- package/server/src/policies/index.js +3 -0
- package/server/src/register.js +5 -0
- package/server/src/routes/admin.js +469 -0
- package/server/src/routes/content-api.js +37 -0
- package/server/src/routes/index.js +9 -0
- package/server/src/services/account-manager.js +277 -0
- package/server/src/services/analytics.js +496 -0
- package/server/src/services/email-designer.js +870 -0
- package/server/src/services/email-router.js +1420 -0
- package/server/src/services/index.js +17 -0
- package/server/src/services/license-guard.js +418 -0
- package/server/src/services/oauth.js +515 -0
- package/server/src/services/service.js +7 -0
- package/server/src/utils/encryption.js +81 -0
- package/strapi-admin.js +4 -0
- package/strapi-server.js +4 -0
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Designer Service
|
|
3
|
+
*
|
|
4
|
+
* Handles email template creation, updates, versioning, and rendering
|
|
5
|
+
*
|
|
6
|
+
* CRITICAL: Uses ONLY entityService for all database operations
|
|
7
|
+
* This ensures proper relation handling in Strapi v5
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const Mustache = require('mustache');
|
|
13
|
+
const htmlToTextLib = require('html-to-text');
|
|
14
|
+
const decode = require('decode-html');
|
|
15
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// HELPER FUNCTIONS
|
|
18
|
+
// ============================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Safely convert HTML to plain text
|
|
22
|
+
* Handles various html-to-text library versions
|
|
23
|
+
*/
|
|
24
|
+
const convertHtmlToText = (html, options = { wordwrap: 130 }) => {
|
|
25
|
+
try {
|
|
26
|
+
if (!html || typeof html !== 'string') {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!htmlToTextLib) {
|
|
31
|
+
return html.replace(/<[^>]*>/g, '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Try different API styles
|
|
35
|
+
if (htmlToTextLib.htmlToText && typeof htmlToTextLib.htmlToText === 'function') {
|
|
36
|
+
return htmlToTextLib.htmlToText(html, options);
|
|
37
|
+
} else if (htmlToTextLib.convert && typeof htmlToTextLib.convert === 'function') {
|
|
38
|
+
return htmlToTextLib.convert(html, options);
|
|
39
|
+
} else if (typeof htmlToTextLib === 'function') {
|
|
40
|
+
return htmlToTextLib(html, options);
|
|
41
|
+
} else if (htmlToTextLib.default) {
|
|
42
|
+
if (typeof htmlToTextLib.default.htmlToText === 'function') {
|
|
43
|
+
return htmlToTextLib.default.htmlToText(html, options);
|
|
44
|
+
} else if (typeof htmlToTextLib.default.convert === 'function') {
|
|
45
|
+
return htmlToTextLib.default.convert(html, options);
|
|
46
|
+
} else if (typeof htmlToTextLib.default === 'function') {
|
|
47
|
+
return htmlToTextLib.default(html, options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fallback
|
|
52
|
+
return html.replace(/<[^>]*>/g, '');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
strapi.log.error('[magic-mail] Error converting HTML to text:', error);
|
|
55
|
+
return (html || '').replace(/<[^>]*>/g, '');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ============================================================
|
|
60
|
+
// SERVICE
|
|
61
|
+
// ============================================================
|
|
62
|
+
|
|
63
|
+
module.exports = ({ strapi }) => ({
|
|
64
|
+
|
|
65
|
+
// ============================================================
|
|
66
|
+
// TEMPLATE CRUD OPERATIONS
|
|
67
|
+
// ============================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all templates
|
|
71
|
+
*/
|
|
72
|
+
async findAll(filters = {}) {
|
|
73
|
+
return strapi.entityService.findMany('plugin::magic-mail.email-template', {
|
|
74
|
+
filters,
|
|
75
|
+
sort: { createdAt: 'desc' },
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get template by ID with populated versions
|
|
81
|
+
*/
|
|
82
|
+
async findOne(id) {
|
|
83
|
+
return strapi.entityService.findOne('plugin::magic-mail.email-template', id, {
|
|
84
|
+
populate: ['versions'],
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get template by reference ID
|
|
90
|
+
*/
|
|
91
|
+
async findByReferenceId(templateReferenceId) {
|
|
92
|
+
const results = await strapi.entityService.findMany('plugin::magic-mail.email-template', {
|
|
93
|
+
filters: { templateReferenceId },
|
|
94
|
+
limit: 1,
|
|
95
|
+
});
|
|
96
|
+
return results.length > 0 ? results[0] : null;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 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
|
+
*/
|
|
108
|
+
async create(data) {
|
|
109
|
+
strapi.log.info('[magic-mail] 📝 Creating new template...');
|
|
110
|
+
|
|
111
|
+
// 1. Check license limits
|
|
112
|
+
const maxTemplates = await strapi
|
|
113
|
+
.plugin('magic-mail')
|
|
114
|
+
.service('license-guard')
|
|
115
|
+
.getMaxEmailTemplates();
|
|
116
|
+
|
|
117
|
+
const currentCount = await strapi.db
|
|
118
|
+
.query('plugin::magic-mail.email-template')
|
|
119
|
+
.count();
|
|
120
|
+
|
|
121
|
+
if (maxTemplates !== -1 && currentCount >= maxTemplates) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Template limit reached (${maxTemplates}). Upgrade your license to create more templates.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 2. Validate reference ID is unique
|
|
128
|
+
if (data.templateReferenceId) {
|
|
129
|
+
const existing = await this.findByReferenceId(data.templateReferenceId);
|
|
130
|
+
if (existing) {
|
|
131
|
+
throw new Error(`Template with reference ID ${data.templateReferenceId} already exists`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 3. Create template
|
|
136
|
+
const template = await strapi.entityService.create('plugin::magic-mail.email-template', {
|
|
137
|
+
data: {
|
|
138
|
+
...data,
|
|
139
|
+
isActive: data.isActive !== undefined ? data.isActive : true,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
strapi.log.info(`[magic-mail] ✅ Template created: ID=${template.id}, name="${template.name}"`);
|
|
144
|
+
|
|
145
|
+
// 4. Create initial version if versioning enabled
|
|
146
|
+
const hasVersioning = await strapi
|
|
147
|
+
.plugin('magic-mail')
|
|
148
|
+
.service('license-guard')
|
|
149
|
+
.hasFeature('email-designer-versioning');
|
|
150
|
+
|
|
151
|
+
if (hasVersioning) {
|
|
152
|
+
strapi.log.info('[magic-mail] 💾 Creating initial version...');
|
|
153
|
+
|
|
154
|
+
await this.createVersion(template.id, {
|
|
155
|
+
name: data.name,
|
|
156
|
+
subject: data.subject,
|
|
157
|
+
design: data.design,
|
|
158
|
+
bodyHtml: data.bodyHtml,
|
|
159
|
+
bodyText: data.bodyText,
|
|
160
|
+
tags: data.tags,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
strapi.log.info('[magic-mail] ✅ Initial version created');
|
|
164
|
+
} else {
|
|
165
|
+
strapi.log.info('[magic-mail] ⏭️ Versioning not enabled, skipping initial version');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return template;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 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
|
+
*/
|
|
181
|
+
async update(id, data) {
|
|
182
|
+
strapi.log.info(`[magic-mail] 🔄 Updating template ID: ${id}`);
|
|
183
|
+
|
|
184
|
+
// 1. Load existing template
|
|
185
|
+
const template = await this.findOne(id);
|
|
186
|
+
if (!template) {
|
|
187
|
+
throw new Error('Template not found');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
strapi.log.info(`[magic-mail] 📋 Found template: ID=${template.id}, name="${template.name}"`);
|
|
191
|
+
|
|
192
|
+
// 2. Create version snapshot BEFORE update (if versioning enabled)
|
|
193
|
+
const hasVersioning = await strapi
|
|
194
|
+
.plugin('magic-mail')
|
|
195
|
+
.service('license-guard')
|
|
196
|
+
.hasFeature('email-designer-versioning');
|
|
197
|
+
|
|
198
|
+
if (hasVersioning) {
|
|
199
|
+
strapi.log.info('[magic-mail] 💾 Creating version snapshot before update...');
|
|
200
|
+
|
|
201
|
+
// Save CURRENT state as new version
|
|
202
|
+
await this.createVersion(template.id, {
|
|
203
|
+
name: template.name,
|
|
204
|
+
subject: template.subject,
|
|
205
|
+
design: template.design,
|
|
206
|
+
bodyHtml: template.bodyHtml,
|
|
207
|
+
bodyText: template.bodyText,
|
|
208
|
+
tags: template.tags,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
strapi.log.info('[magic-mail] ✅ Version snapshot created');
|
|
212
|
+
} else {
|
|
213
|
+
strapi.log.info('[magic-mail] ⏭️ Versioning not enabled, skipping version snapshot');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 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
|
+
const updateData = { ...data };
|
|
220
|
+
|
|
221
|
+
// Remove any versions field if it exists (shouldn't, but be safe)
|
|
222
|
+
if ('versions' in updateData) {
|
|
223
|
+
delete updateData.versions;
|
|
224
|
+
strapi.log.warn('[magic-mail] ⚠️ Removed versions field from update data to prevent relation overwrite');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const updated = await strapi.entityService.update('plugin::magic-mail.email-template', id, {
|
|
228
|
+
data: updateData,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
strapi.log.info(`[magic-mail] ✅ Template updated: ID=${updated.id}, versions preserved`);
|
|
232
|
+
return updated;
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Delete template and all its versions
|
|
237
|
+
*
|
|
238
|
+
* Uses Document Service for version deletion to ensure cascading delete
|
|
239
|
+
*/
|
|
240
|
+
async delete(id) {
|
|
241
|
+
strapi.log.info(`[magic-mail] 🗑️ Deleting template ID: ${id}`);
|
|
242
|
+
|
|
243
|
+
// Get template first
|
|
244
|
+
const template = await this.findOne(id);
|
|
245
|
+
if (!template) {
|
|
246
|
+
throw new Error('Template not found');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
strapi.log.info(`[magic-mail] 🗑️ Template: ID=${template.id}, name="${template.name}"`);
|
|
250
|
+
|
|
251
|
+
// Delete all versions using Document Service (supports documentId filtering)
|
|
252
|
+
const allVersions = await strapi.documents('plugin::magic-mail.email-template-version').findMany({
|
|
253
|
+
filters: {
|
|
254
|
+
template: {
|
|
255
|
+
documentId: template.documentId,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
strapi.log.info(`[magic-mail] 🗑️ Found ${allVersions.length} versions to delete`);
|
|
261
|
+
|
|
262
|
+
// Delete each version
|
|
263
|
+
for (const version of allVersions) {
|
|
264
|
+
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}`);
|
|
270
|
+
} catch (versionError) {
|
|
271
|
+
strapi.log.warn(`[magic-mail] ⚠️ Failed to delete version ${version.id}: ${versionError.message}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 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`);
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Duplicate template
|
|
285
|
+
*
|
|
286
|
+
* Creates a copy of an existing template with " copy" suffix
|
|
287
|
+
* Does NOT copy versions, starts fresh
|
|
288
|
+
*/
|
|
289
|
+
async duplicate(id) {
|
|
290
|
+
strapi.log.info(`[magic-mail] 📋 Duplicating template ID: ${id}`);
|
|
291
|
+
|
|
292
|
+
// Get original template
|
|
293
|
+
const original = await this.findOne(id);
|
|
294
|
+
if (!original) {
|
|
295
|
+
throw new Error('Template not found');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
strapi.log.info(`[magic-mail] 📦 Original template: ID=${original.id}, name="${original.name}"`);
|
|
299
|
+
|
|
300
|
+
// Prepare duplicate data (remove system fields)
|
|
301
|
+
const duplicateData = {
|
|
302
|
+
name: `${original.name} copy`,
|
|
303
|
+
subject: original.subject,
|
|
304
|
+
design: original.design,
|
|
305
|
+
bodyHtml: original.bodyHtml,
|
|
306
|
+
bodyText: original.bodyText,
|
|
307
|
+
category: original.category,
|
|
308
|
+
tags: original.tags,
|
|
309
|
+
isActive: original.isActive,
|
|
310
|
+
// Generate new templateReferenceId (unique ID)
|
|
311
|
+
templateReferenceId: Date.now() + Math.floor(Math.random() * 1000),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Create duplicate
|
|
315
|
+
const duplicated = await this.create(duplicateData);
|
|
316
|
+
|
|
317
|
+
strapi.log.info(`[magic-mail] ✅ Template duplicated: ID=${duplicated.id}, name="${duplicated.name}"`);
|
|
318
|
+
|
|
319
|
+
return duplicated;
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
// ============================================================
|
|
323
|
+
// VERSIONING OPERATIONS
|
|
324
|
+
// ============================================================
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 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
|
+
*/
|
|
340
|
+
async createVersion(templateId, data) {
|
|
341
|
+
strapi.log.info(`[magic-mail] 📸 Creating version for template ID: ${templateId}`);
|
|
342
|
+
|
|
343
|
+
// 1. Verify template exists
|
|
344
|
+
const template = await strapi.entityService.findOne('plugin::magic-mail.email-template', templateId);
|
|
345
|
+
if (!template) {
|
|
346
|
+
throw new Error(`Template ${templateId} not found`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
strapi.log.info(`[magic-mail] 📦 Template found: ID=${template.id}, name="${template.name}"`);
|
|
350
|
+
|
|
351
|
+
// 2. Calculate next version number
|
|
352
|
+
const existingVersions = await strapi.entityService.findMany('plugin::magic-mail.email-template-version', {
|
|
353
|
+
filters: {
|
|
354
|
+
template: {
|
|
355
|
+
id: templateId, // Use numeric ID in filter
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
sort: { versionNumber: 'desc' },
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const versionNumber = existingVersions.length > 0
|
|
362
|
+
? Math.max(...existingVersions.map(v => v.versionNumber || 0)) + 1
|
|
363
|
+
: 1;
|
|
364
|
+
|
|
365
|
+
strapi.log.info(`[magic-mail] 📊 Existing versions: ${existingVersions.length} → Next version: #${versionNumber}`);
|
|
366
|
+
|
|
367
|
+
// 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', {
|
|
370
|
+
data: {
|
|
371
|
+
versionNumber,
|
|
372
|
+
...data,
|
|
373
|
+
template: {
|
|
374
|
+
connect: [templateId], // ✅ Use connect array for Strapi v5!
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
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;
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get all versions for a template
|
|
411
|
+
*/
|
|
412
|
+
async getVersions(templateId) {
|
|
413
|
+
strapi.log.info(`[magic-mail] 📜 Fetching versions for template ID: ${templateId}`);
|
|
414
|
+
|
|
415
|
+
// Verify template exists
|
|
416
|
+
const template = await strapi.entityService.findOne('plugin::magic-mail.email-template', templateId, {
|
|
417
|
+
populate: ['versions'],
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (!template) {
|
|
421
|
+
throw new Error('Template not found');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
strapi.log.info(`[magic-mail] 📦 Template has ${template.versions?.length || 0} versions in relation`);
|
|
425
|
+
|
|
426
|
+
// OPTION 1: Return versions from template populate (most reliable!)
|
|
427
|
+
if (template.versions && template.versions.length > 0) {
|
|
428
|
+
// Sort by version number
|
|
429
|
+
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
|
+
return sortedVersions;
|
|
433
|
+
}
|
|
434
|
+
|
|
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', {
|
|
439
|
+
filters: {
|
|
440
|
+
template: {
|
|
441
|
+
id: templateId,
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
sort: { versionNumber: 'desc' },
|
|
445
|
+
populate: ['template'],
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
strapi.log.info(`[magic-mail] ✅ Found ${versions.length} versions via filter`);
|
|
449
|
+
|
|
450
|
+
return versions;
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* 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
|
+
*/
|
|
459
|
+
async restoreVersion(templateId, versionId) {
|
|
460
|
+
strapi.log.info(`[magic-mail] 🔄 Restoring template ${templateId} from version ${versionId}`);
|
|
461
|
+
|
|
462
|
+
// Get version
|
|
463
|
+
const version = await strapi.entityService.findOne('plugin::magic-mail.email-template-version', versionId, {
|
|
464
|
+
populate: ['template'],
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
if (!version) {
|
|
468
|
+
throw new Error('Version not found');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
strapi.log.info(`[magic-mail] 📦 Version found: ID=${version.id}, versionNumber=${version.versionNumber}, has template: ${!!version.template}`);
|
|
472
|
+
|
|
473
|
+
// 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`);
|
|
493
|
+
throw new Error('Version does not belong to this template');
|
|
494
|
+
}
|
|
495
|
+
|
|
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, {
|
|
502
|
+
name: version.name,
|
|
503
|
+
subject: version.subject,
|
|
504
|
+
design: version.design,
|
|
505
|
+
bodyHtml: version.bodyHtml,
|
|
506
|
+
bodyText: version.bodyText,
|
|
507
|
+
tags: version.tags,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
strapi.log.info(`[magic-mail] ✅ Template restored from version #${version.versionNumber}`);
|
|
511
|
+
|
|
512
|
+
return restored;
|
|
513
|
+
},
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Delete a single version
|
|
517
|
+
*
|
|
518
|
+
* @param {number} templateId - Template ID (for verification)
|
|
519
|
+
* @param {number} versionId - Version ID to delete
|
|
520
|
+
*/
|
|
521
|
+
async deleteVersion(templateId, versionId) {
|
|
522
|
+
strapi.log.info(`[magic-mail] 🗑️ Deleting version ${versionId} from template ${templateId}`);
|
|
523
|
+
|
|
524
|
+
// Get version
|
|
525
|
+
const version = await strapi.entityService.findOne('plugin::magic-mail.email-template-version', versionId, {
|
|
526
|
+
populate: ['template'],
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
if (!version) {
|
|
530
|
+
throw new Error('Version not found');
|
|
531
|
+
}
|
|
532
|
+
|
|
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`);
|
|
551
|
+
throw new Error('Version does not belong to this template');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
strapi.log.info(`[magic-mail] ✅ Version ${versionId} found in template's versions array`);
|
|
555
|
+
}
|
|
556
|
+
|
|
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`);
|
|
561
|
+
|
|
562
|
+
return { success: true, message: 'Version deleted' };
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Delete all versions for a template
|
|
567
|
+
*
|
|
568
|
+
* @param {number} templateId - Template ID
|
|
569
|
+
*/
|
|
570
|
+
async deleteAllVersions(templateId) {
|
|
571
|
+
strapi.log.info(`[magic-mail] 🗑️ Deleting all versions for template ${templateId}`);
|
|
572
|
+
|
|
573
|
+
// Get template with versions
|
|
574
|
+
const template = await strapi.entityService.findOne('plugin::magic-mail.email-template', templateId, {
|
|
575
|
+
populate: ['versions'],
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
if (!template) {
|
|
579
|
+
throw new Error('Template not found');
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const versionCount = template.versions?.length || 0;
|
|
583
|
+
strapi.log.info(`[magic-mail] 📊 Found ${versionCount} versions to delete`);
|
|
584
|
+
|
|
585
|
+
if (versionCount === 0) {
|
|
586
|
+
return { success: true, message: 'No versions to delete', deletedCount: 0 };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Delete each version
|
|
590
|
+
let deletedCount = 0;
|
|
591
|
+
const errors = [];
|
|
592
|
+
|
|
593
|
+
for (const version of template.versions) {
|
|
594
|
+
try {
|
|
595
|
+
await strapi.entityService.delete('plugin::magic-mail.email-template-version', version.id);
|
|
596
|
+
deletedCount++;
|
|
597
|
+
strapi.log.info(`[magic-mail] 🗑️ Deleted version #${version.versionNumber} (ID: ${version.id})`);
|
|
598
|
+
} 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 });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
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
|
+
};
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
// ============================================================
|
|
616
|
+
// RENDERING
|
|
617
|
+
// ============================================================
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* 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
|
+
*/
|
|
626
|
+
async renderTemplate(templateReferenceId, data = {}) {
|
|
627
|
+
const template = await this.findByReferenceId(templateReferenceId);
|
|
628
|
+
|
|
629
|
+
if (!template) {
|
|
630
|
+
throw new Error(`Template with reference ID ${templateReferenceId} not found`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (!template.isActive) {
|
|
634
|
+
throw new Error(`Template ${template.name} is inactive`);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
let { bodyHtml = '', bodyText = '', subject = '' } = template;
|
|
638
|
+
|
|
639
|
+
// Convert <% %> to {{ }} for Mustache (backward compatibility)
|
|
640
|
+
bodyHtml = bodyHtml.replace(/<%/g, '{{').replace(/%>/g, '}}');
|
|
641
|
+
bodyText = bodyText.replace(/<%/g, '{{').replace(/%>/g, '}}');
|
|
642
|
+
subject = subject.replace(/<%/g, '{{').replace(/%>/g, '}}');
|
|
643
|
+
|
|
644
|
+
// Generate text version from HTML if not provided
|
|
645
|
+
if ((!bodyText || !bodyText.length) && bodyHtml && bodyHtml.length) {
|
|
646
|
+
bodyText = convertHtmlToText(bodyHtml, { wordwrap: 130 });
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Decode HTML entities
|
|
650
|
+
const decodedHtml = decode(bodyHtml);
|
|
651
|
+
const decodedText = decode(bodyText);
|
|
652
|
+
const decodedSubject = decode(subject);
|
|
653
|
+
|
|
654
|
+
// Render with Mustache
|
|
655
|
+
const renderedHtml = Mustache.render(decodedHtml, data);
|
|
656
|
+
const renderedText = Mustache.render(decodedText, data);
|
|
657
|
+
const renderedSubject = Mustache.render(decodedSubject, data);
|
|
658
|
+
|
|
659
|
+
return {
|
|
660
|
+
html: renderedHtml,
|
|
661
|
+
text: renderedText,
|
|
662
|
+
subject: renderedSubject,
|
|
663
|
+
templateName: template.name,
|
|
664
|
+
category: template.category,
|
|
665
|
+
};
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
// ============================================================
|
|
669
|
+
// IMPORT/EXPORT (Advanced+ License)
|
|
670
|
+
// ============================================================
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Export templates as JSON
|
|
674
|
+
*/
|
|
675
|
+
async exportTemplates(templateIds = []) {
|
|
676
|
+
strapi.log.info('[magic-mail] 📤 Exporting templates...');
|
|
677
|
+
|
|
678
|
+
let templates;
|
|
679
|
+
if (templateIds.length > 0) {
|
|
680
|
+
templates = await strapi.entityService.findMany('plugin::magic-mail.email-template', {
|
|
681
|
+
filters: { id: { $in: templateIds } },
|
|
682
|
+
});
|
|
683
|
+
} else {
|
|
684
|
+
templates = await this.findAll();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const exported = templates.map((template) => ({
|
|
688
|
+
templateReferenceId: template.templateReferenceId,
|
|
689
|
+
name: template.name,
|
|
690
|
+
subject: template.subject,
|
|
691
|
+
design: template.design,
|
|
692
|
+
bodyHtml: template.bodyHtml,
|
|
693
|
+
bodyText: template.bodyText,
|
|
694
|
+
category: template.category,
|
|
695
|
+
tags: template.tags,
|
|
696
|
+
}));
|
|
697
|
+
|
|
698
|
+
strapi.log.info(`[magic-mail] ✅ Exported ${exported.length} templates`);
|
|
699
|
+
return exported;
|
|
700
|
+
},
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Import templates from JSON
|
|
704
|
+
*/
|
|
705
|
+
async importTemplates(templates) {
|
|
706
|
+
strapi.log.info(`[magic-mail] 📥 Importing ${templates.length} templates...`);
|
|
707
|
+
|
|
708
|
+
const results = [];
|
|
709
|
+
|
|
710
|
+
for (const templateData of templates) {
|
|
711
|
+
try {
|
|
712
|
+
const existing = await this.findByReferenceId(templateData.templateReferenceId);
|
|
713
|
+
|
|
714
|
+
if (existing) {
|
|
715
|
+
// Update existing
|
|
716
|
+
const updated = await this.update(existing.id, templateData);
|
|
717
|
+
results.push({ success: true, action: 'updated', template: updated });
|
|
718
|
+
strapi.log.info(`[magic-mail] ✅ Updated template: ${templateData.name}`);
|
|
719
|
+
} else {
|
|
720
|
+
// Create new
|
|
721
|
+
const created = await this.create(templateData);
|
|
722
|
+
results.push({ success: true, action: 'created', template: created });
|
|
723
|
+
strapi.log.info(`[magic-mail] ✅ Created template: ${templateData.name}`);
|
|
724
|
+
}
|
|
725
|
+
} catch (error) {
|
|
726
|
+
results.push({
|
|
727
|
+
success: false,
|
|
728
|
+
action: 'failed',
|
|
729
|
+
error: error.message,
|
|
730
|
+
templateName: templateData.name,
|
|
731
|
+
});
|
|
732
|
+
strapi.log.error(`[magic-mail] ❌ Failed to import ${templateData.name}: ${error.message}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const successful = results.filter(r => r.success).length;
|
|
737
|
+
strapi.log.info(`[magic-mail] ✅ Import completed: ${successful}/${templates.length} templates`);
|
|
738
|
+
|
|
739
|
+
return results;
|
|
740
|
+
},
|
|
741
|
+
|
|
742
|
+
// ============================================================
|
|
743
|
+
// STATISTICS
|
|
744
|
+
// ============================================================
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Get template statistics
|
|
748
|
+
*/
|
|
749
|
+
async getStats() {
|
|
750
|
+
const allTemplates = await strapi.entityService.findMany('plugin::magic-mail.email-template', {
|
|
751
|
+
fields: ['isActive', 'category'],
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const total = allTemplates.length;
|
|
755
|
+
const active = allTemplates.filter(t => t.isActive === true).length;
|
|
756
|
+
|
|
757
|
+
// Group by category
|
|
758
|
+
const categoryMap = allTemplates.reduce((acc, template) => {
|
|
759
|
+
const category = template.category || 'custom';
|
|
760
|
+
acc[category] = (acc[category] || 0) + 1;
|
|
761
|
+
return acc;
|
|
762
|
+
}, {});
|
|
763
|
+
|
|
764
|
+
const byCategory = Object.entries(categoryMap).map(([category, count]) => ({ category, count }));
|
|
765
|
+
|
|
766
|
+
const maxTemplates = await strapi
|
|
767
|
+
.plugin('magic-mail')
|
|
768
|
+
.service('license-guard')
|
|
769
|
+
.getMaxEmailTemplates();
|
|
770
|
+
|
|
771
|
+
return {
|
|
772
|
+
total,
|
|
773
|
+
active,
|
|
774
|
+
inactive: total - active,
|
|
775
|
+
byCategory,
|
|
776
|
+
maxTemplates,
|
|
777
|
+
remaining: maxTemplates === -1 ? -1 : Math.max(0, maxTemplates - total),
|
|
778
|
+
};
|
|
779
|
+
},
|
|
780
|
+
|
|
781
|
+
// ============================================================
|
|
782
|
+
// STRAPI CORE EMAIL TEMPLATES
|
|
783
|
+
// ============================================================
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Get Strapi core email template (reset-password, email-confirmation)
|
|
787
|
+
*
|
|
788
|
+
* These are stored in users-permissions plugin store
|
|
789
|
+
*/
|
|
790
|
+
async getCoreTemplate(coreEmailType) {
|
|
791
|
+
// Validate type
|
|
792
|
+
if (!['reset-password', 'email-confirmation'].includes(coreEmailType)) {
|
|
793
|
+
throw new Error('Invalid core email type');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const pluginStoreEmailKey =
|
|
797
|
+
coreEmailType === 'email-confirmation' ? 'email_confirmation' : 'reset_password';
|
|
798
|
+
|
|
799
|
+
const pluginStore = await strapi.store({
|
|
800
|
+
environment: '',
|
|
801
|
+
type: 'plugin',
|
|
802
|
+
name: 'users-permissions',
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Get email config
|
|
806
|
+
const emailConfig = await pluginStore.get({ key: 'email' });
|
|
807
|
+
|
|
808
|
+
let data = null;
|
|
809
|
+
if (emailConfig && emailConfig[pluginStoreEmailKey]) {
|
|
810
|
+
data = emailConfig[pluginStoreEmailKey];
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Convert <%= %> to {{ }} for Mustache
|
|
814
|
+
const messageConverted = data && data.options && data.options.message
|
|
815
|
+
? data.options.message.replace(/<%|<%/g, '{{').replace(/%>|%>/g, '}}')
|
|
816
|
+
: '';
|
|
817
|
+
|
|
818
|
+
const subjectConverted = data && data.options && data.options.object
|
|
819
|
+
? data.options.object.replace(/<%|<%/g, '{{').replace(/%>|%>/g, '}}')
|
|
820
|
+
: '';
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
from: data?.options?.from || null,
|
|
824
|
+
message: messageConverted || '',
|
|
825
|
+
subject: subjectConverted || '',
|
|
826
|
+
bodyHtml: messageConverted || '',
|
|
827
|
+
bodyText: messageConverted ? convertHtmlToText(messageConverted, { wordwrap: 130 }) : '',
|
|
828
|
+
coreEmailType,
|
|
829
|
+
design: data?.design || null,
|
|
830
|
+
};
|
|
831
|
+
},
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Update Strapi core email template
|
|
835
|
+
*/
|
|
836
|
+
async updateCoreTemplate(coreEmailType, data) {
|
|
837
|
+
// Validate type
|
|
838
|
+
if (!['reset-password', 'email-confirmation'].includes(coreEmailType)) {
|
|
839
|
+
throw new Error('Invalid core email type');
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const pluginStoreEmailKey =
|
|
843
|
+
coreEmailType === 'email-confirmation' ? 'email_confirmation' : 'reset_password';
|
|
844
|
+
|
|
845
|
+
const pluginStore = await strapi.store({
|
|
846
|
+
environment: '',
|
|
847
|
+
type: 'plugin',
|
|
848
|
+
name: 'users-permissions',
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
const emailsConfig = await pluginStore.get({ key: 'email' });
|
|
852
|
+
|
|
853
|
+
// Convert {{ }} back to <%= %> for Strapi
|
|
854
|
+
emailsConfig[pluginStoreEmailKey] = {
|
|
855
|
+
...emailsConfig[pluginStoreEmailKey],
|
|
856
|
+
options: {
|
|
857
|
+
...(emailsConfig[pluginStoreEmailKey] ? emailsConfig[pluginStoreEmailKey].options : {}),
|
|
858
|
+
message: data.message.replace(/{{/g, '<%').replace(/}}/g, '%>'),
|
|
859
|
+
object: data.subject.replace(/{{/g, '<%').replace(/}}/g, '%>'),
|
|
860
|
+
},
|
|
861
|
+
design: data.design,
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
await pluginStore.set({ key: 'email', value: emailsConfig });
|
|
865
|
+
|
|
866
|
+
strapi.log.info(`[magic-mail] ✅ Core email template updated: ${pluginStoreEmailKey}`);
|
|
867
|
+
|
|
868
|
+
return { message: 'Saved' };
|
|
869
|
+
},
|
|
870
|
+
});
|