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
|
@@ -5,9 +5,33 @@ const { encryptCredentials, decryptCredentials } = require('../utils/encryption'
|
|
|
5
5
|
/**
|
|
6
6
|
* Account Manager Service
|
|
7
7
|
* Manages email accounts (create, update, test, delete)
|
|
8
|
+
* [SUCCESS] Migrated to strapi.documents() API (Strapi v5 Best Practice)
|
|
8
9
|
*/
|
|
9
10
|
|
|
11
|
+
const EMAIL_ACCOUNT_UID = 'plugin::magic-mail.email-account';
|
|
12
|
+
|
|
10
13
|
module.exports = ({ strapi }) => ({
|
|
14
|
+
/**
|
|
15
|
+
* Resolves account ID to documentId (handles both numeric id and documentId)
|
|
16
|
+
* @param {string|number} idOrDocumentId - Either numeric id or documentId
|
|
17
|
+
* @returns {Promise<string|null>} The documentId or null if not found
|
|
18
|
+
*/
|
|
19
|
+
async resolveDocumentId(idOrDocumentId) {
|
|
20
|
+
// If it looks like a documentId (not purely numeric), use directly
|
|
21
|
+
if (idOrDocumentId && !/^\d+$/.test(String(idOrDocumentId))) {
|
|
22
|
+
return String(idOrDocumentId);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Otherwise, find by numeric id
|
|
26
|
+
const accounts = await strapi.documents(EMAIL_ACCOUNT_UID).findMany({
|
|
27
|
+
filters: { id: Number(idOrDocumentId) },
|
|
28
|
+
fields: ['documentId'],
|
|
29
|
+
limit: 1,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return accounts.length > 0 ? accounts[0].documentId : null;
|
|
33
|
+
},
|
|
34
|
+
|
|
11
35
|
/**
|
|
12
36
|
* Create new email account
|
|
13
37
|
*/
|
|
@@ -35,7 +59,7 @@ module.exports = ({ strapi }) => ({
|
|
|
35
59
|
await this.unsetAllPrimary();
|
|
36
60
|
}
|
|
37
61
|
|
|
38
|
-
const account = await strapi.
|
|
62
|
+
const account = await strapi.documents(EMAIL_ACCOUNT_UID).create({
|
|
39
63
|
data: {
|
|
40
64
|
name,
|
|
41
65
|
provider,
|
|
@@ -54,7 +78,7 @@ module.exports = ({ strapi }) => ({
|
|
|
54
78
|
},
|
|
55
79
|
});
|
|
56
80
|
|
|
57
|
-
strapi.log.info(`[magic-mail]
|
|
81
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Email account created: ${name}`);
|
|
58
82
|
|
|
59
83
|
return account;
|
|
60
84
|
},
|
|
@@ -62,8 +86,15 @@ module.exports = ({ strapi }) => ({
|
|
|
62
86
|
/**
|
|
63
87
|
* Update email account
|
|
64
88
|
*/
|
|
65
|
-
async updateAccount(
|
|
66
|
-
const
|
|
89
|
+
async updateAccount(idOrDocumentId, accountData) {
|
|
90
|
+
const documentId = await this.resolveDocumentId(idOrDocumentId);
|
|
91
|
+
if (!documentId) {
|
|
92
|
+
throw new Error('Account not found');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const existingAccount = await strapi.documents(EMAIL_ACCOUNT_UID).findOne({
|
|
96
|
+
documentId,
|
|
97
|
+
});
|
|
67
98
|
|
|
68
99
|
if (!existingAccount) {
|
|
69
100
|
throw new Error('Account not found');
|
|
@@ -92,7 +123,8 @@ module.exports = ({ strapi }) => ({
|
|
|
92
123
|
await this.unsetAllPrimary();
|
|
93
124
|
}
|
|
94
125
|
|
|
95
|
-
const updatedAccount = await strapi.
|
|
126
|
+
const updatedAccount = await strapi.documents(EMAIL_ACCOUNT_UID).update({
|
|
127
|
+
documentId,
|
|
96
128
|
data: {
|
|
97
129
|
name,
|
|
98
130
|
description,
|
|
@@ -109,7 +141,7 @@ module.exports = ({ strapi }) => ({
|
|
|
109
141
|
},
|
|
110
142
|
});
|
|
111
143
|
|
|
112
|
-
strapi.log.info(`[magic-mail]
|
|
144
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Email account updated: ${name} (Active: ${isActive})`);
|
|
113
145
|
|
|
114
146
|
return updatedAccount;
|
|
115
147
|
},
|
|
@@ -117,8 +149,15 @@ module.exports = ({ strapi }) => ({
|
|
|
117
149
|
/**
|
|
118
150
|
* Test email account
|
|
119
151
|
*/
|
|
120
|
-
async testAccount(
|
|
121
|
-
const
|
|
152
|
+
async testAccount(idOrDocumentId, testEmail, testOptions = {}) {
|
|
153
|
+
const documentId = await this.resolveDocumentId(idOrDocumentId);
|
|
154
|
+
if (!documentId) {
|
|
155
|
+
throw new Error('Account not found');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const account = await strapi.documents(EMAIL_ACCOUNT_UID).findOne({
|
|
159
|
+
documentId,
|
|
160
|
+
});
|
|
122
161
|
|
|
123
162
|
if (!account) {
|
|
124
163
|
throw new Error('Account not found');
|
|
@@ -198,8 +237,8 @@ module.exports = ({ strapi }) => ({
|
|
|
198
237
|
* Get all accounts
|
|
199
238
|
*/
|
|
200
239
|
async getAllAccounts() {
|
|
201
|
-
const accounts = await strapi.
|
|
202
|
-
sort: { priority: 'desc' },
|
|
240
|
+
const accounts = await strapi.documents(EMAIL_ACCOUNT_UID).findMany({
|
|
241
|
+
sort: [{ priority: 'desc' }],
|
|
203
242
|
});
|
|
204
243
|
|
|
205
244
|
// Don't return encrypted config in list
|
|
@@ -212,8 +251,15 @@ module.exports = ({ strapi }) => ({
|
|
|
212
251
|
/**
|
|
213
252
|
* Get single account with decrypted config (for editing)
|
|
214
253
|
*/
|
|
215
|
-
async getAccountWithDecryptedConfig(
|
|
216
|
-
const
|
|
254
|
+
async getAccountWithDecryptedConfig(idOrDocumentId) {
|
|
255
|
+
const documentId = await this.resolveDocumentId(idOrDocumentId);
|
|
256
|
+
if (!documentId) {
|
|
257
|
+
throw new Error('Account not found');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const account = await strapi.documents(EMAIL_ACCOUNT_UID).findOne({
|
|
261
|
+
documentId,
|
|
262
|
+
});
|
|
217
263
|
|
|
218
264
|
if (!account) {
|
|
219
265
|
throw new Error('Account not found');
|
|
@@ -231,21 +277,27 @@ module.exports = ({ strapi }) => ({
|
|
|
231
277
|
/**
|
|
232
278
|
* Delete account
|
|
233
279
|
*/
|
|
234
|
-
async deleteAccount(
|
|
235
|
-
await
|
|
236
|
-
|
|
280
|
+
async deleteAccount(idOrDocumentId) {
|
|
281
|
+
const documentId = await this.resolveDocumentId(idOrDocumentId);
|
|
282
|
+
if (!documentId) {
|
|
283
|
+
throw new Error('Account not found');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
await strapi.documents(EMAIL_ACCOUNT_UID).delete({ documentId });
|
|
287
|
+
strapi.log.info(`[magic-mail] Account deleted: ${documentId}`);
|
|
237
288
|
},
|
|
238
289
|
|
|
239
290
|
/**
|
|
240
291
|
* Unset all primary flags
|
|
241
292
|
*/
|
|
242
293
|
async unsetAllPrimary() {
|
|
243
|
-
const accounts = await strapi.
|
|
294
|
+
const accounts = await strapi.documents(EMAIL_ACCOUNT_UID).findMany({
|
|
244
295
|
filters: { isPrimary: true },
|
|
245
296
|
});
|
|
246
297
|
|
|
247
298
|
for (const account of accounts) {
|
|
248
|
-
await strapi.
|
|
299
|
+
await strapi.documents(EMAIL_ACCOUNT_UID).update({
|
|
300
|
+
documentId: account.documentId,
|
|
249
301
|
data: { isPrimary: false },
|
|
250
302
|
});
|
|
251
303
|
}
|
|
@@ -255,7 +307,7 @@ module.exports = ({ strapi }) => ({
|
|
|
255
307
|
* Reset daily/hourly counters (called by cron)
|
|
256
308
|
*/
|
|
257
309
|
async resetCounters(type = 'daily') {
|
|
258
|
-
const accounts = await strapi.
|
|
310
|
+
const accounts = await strapi.documents(EMAIL_ACCOUNT_UID).findMany({});
|
|
259
311
|
|
|
260
312
|
for (const account of accounts) {
|
|
261
313
|
const updateData = {};
|
|
@@ -266,12 +318,12 @@ module.exports = ({ strapi }) => ({
|
|
|
266
318
|
updateData.emailsSentThisHour = 0;
|
|
267
319
|
}
|
|
268
320
|
|
|
269
|
-
await strapi.
|
|
321
|
+
await strapi.documents(EMAIL_ACCOUNT_UID).update({
|
|
322
|
+
documentId: account.documentId,
|
|
270
323
|
data: updateData,
|
|
271
324
|
});
|
|
272
325
|
}
|
|
273
326
|
|
|
274
|
-
strapi.log.info(`[magic-mail]
|
|
327
|
+
strapi.log.info(`[magic-mail] [SUCCESS] ${type} counters reset`);
|
|
275
328
|
},
|
|
276
329
|
});
|
|
277
|
-
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Analytics Service
|
|
3
3
|
* Handles email tracking, statistics, and user activity
|
|
4
|
+
*
|
|
5
|
+
* [SUCCESS] Migrated to strapi.documents() API (Strapi v5 Best Practice)
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
'use strict';
|
|
7
9
|
|
|
8
10
|
const crypto = require('crypto');
|
|
9
11
|
|
|
12
|
+
// Content Type UIDs
|
|
13
|
+
const EMAIL_LOG_UID = 'plugin::magic-mail.email-log';
|
|
14
|
+
const EMAIL_EVENT_UID = 'plugin::magic-mail.email-event';
|
|
15
|
+
const EMAIL_LINK_UID = 'plugin::magic-mail.email-link';
|
|
16
|
+
|
|
10
17
|
module.exports = ({ strapi }) => ({
|
|
11
18
|
/**
|
|
12
19
|
* Generate unique email ID for tracking
|
|
@@ -32,7 +39,7 @@ module.exports = ({ strapi }) => ({
|
|
|
32
39
|
async createEmailLog(data) {
|
|
33
40
|
const emailId = this.generateEmailId();
|
|
34
41
|
|
|
35
|
-
const logEntry = await strapi.
|
|
42
|
+
const logEntry = await strapi.documents(EMAIL_LOG_UID).create({
|
|
36
43
|
data: {
|
|
37
44
|
emailId,
|
|
38
45
|
user: data.userId || null,
|
|
@@ -48,9 +55,9 @@ module.exports = ({ strapi }) => ({
|
|
|
48
55
|
},
|
|
49
56
|
});
|
|
50
57
|
|
|
51
|
-
strapi.log.info(`[magic-mail]
|
|
58
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Email log created: ${emailId}`);
|
|
52
59
|
if (data.templateId) {
|
|
53
|
-
strapi.log.info(`[magic-mail]
|
|
60
|
+
strapi.log.info(`[magic-mail] [INFO] Template tracked: ${data.templateName || 'Unknown'} (ID: ${data.templateId})`);
|
|
54
61
|
}
|
|
55
62
|
return logEntry;
|
|
56
63
|
},
|
|
@@ -60,9 +67,9 @@ module.exports = ({ strapi }) => ({
|
|
|
60
67
|
*/
|
|
61
68
|
async recordOpen(emailId, recipientHash, req) {
|
|
62
69
|
try {
|
|
63
|
-
// Find email log
|
|
64
|
-
const emailLog = await strapi.
|
|
65
|
-
|
|
70
|
+
// Find email log using Document Service
|
|
71
|
+
const emailLog = await strapi.documents(EMAIL_LOG_UID).findFirst({
|
|
72
|
+
filters: { emailId },
|
|
66
73
|
});
|
|
67
74
|
|
|
68
75
|
if (!emailLog) {
|
|
@@ -79,20 +86,20 @@ module.exports = ({ strapi }) => ({
|
|
|
79
86
|
|
|
80
87
|
const now = new Date();
|
|
81
88
|
|
|
82
|
-
// Update email log counters
|
|
83
|
-
await strapi.
|
|
84
|
-
|
|
89
|
+
// Update email log counters using Document Service
|
|
90
|
+
await strapi.documents(EMAIL_LOG_UID).update({
|
|
91
|
+
documentId: emailLog.documentId,
|
|
85
92
|
data: {
|
|
86
|
-
openCount: emailLog.openCount + 1,
|
|
93
|
+
openCount: (emailLog.openCount || 0) + 1,
|
|
87
94
|
firstOpenedAt: emailLog.firstOpenedAt || now,
|
|
88
95
|
lastOpenedAt: now,
|
|
89
96
|
},
|
|
90
97
|
});
|
|
91
98
|
|
|
92
99
|
// Create event record
|
|
93
|
-
const event = await strapi.
|
|
100
|
+
const event = await strapi.documents(EMAIL_EVENT_UID).create({
|
|
94
101
|
data: {
|
|
95
|
-
emailLog: emailLog.
|
|
102
|
+
emailLog: emailLog.documentId,
|
|
96
103
|
type: 'open',
|
|
97
104
|
timestamp: now,
|
|
98
105
|
ipAddress: req.ip || req.headers['x-forwarded-for'] || null,
|
|
@@ -101,7 +108,7 @@ module.exports = ({ strapi }) => ({
|
|
|
101
108
|
},
|
|
102
109
|
});
|
|
103
110
|
|
|
104
|
-
strapi.log.info(`[magic-mail]
|
|
111
|
+
strapi.log.info(`[magic-mail] [EMAIL] Email opened: ${emailId} (count: ${(emailLog.openCount || 0) + 1})`);
|
|
105
112
|
return event;
|
|
106
113
|
} catch (error) {
|
|
107
114
|
strapi.log.error('[magic-mail] Error recording open:', error);
|
|
@@ -114,9 +121,9 @@ module.exports = ({ strapi }) => ({
|
|
|
114
121
|
*/
|
|
115
122
|
async recordClick(emailId, linkHash, recipientHash, targetUrl, req) {
|
|
116
123
|
try {
|
|
117
|
-
// Find email log
|
|
118
|
-
const emailLog = await strapi.
|
|
119
|
-
|
|
124
|
+
// Find email log using Document Service
|
|
125
|
+
const emailLog = await strapi.documents(EMAIL_LOG_UID).findFirst({
|
|
126
|
+
filters: { emailId },
|
|
120
127
|
});
|
|
121
128
|
|
|
122
129
|
if (!emailLog) {
|
|
@@ -132,17 +139,17 @@ module.exports = ({ strapi }) => ({
|
|
|
132
139
|
const now = new Date();
|
|
133
140
|
|
|
134
141
|
// Update click count
|
|
135
|
-
await strapi.
|
|
136
|
-
|
|
142
|
+
await strapi.documents(EMAIL_LOG_UID).update({
|
|
143
|
+
documentId: emailLog.documentId,
|
|
137
144
|
data: {
|
|
138
|
-
clickCount: emailLog.clickCount + 1,
|
|
145
|
+
clickCount: (emailLog.clickCount || 0) + 1,
|
|
139
146
|
},
|
|
140
147
|
});
|
|
141
148
|
|
|
142
149
|
// Create event record
|
|
143
|
-
const event = await strapi.
|
|
150
|
+
const event = await strapi.documents(EMAIL_EVENT_UID).create({
|
|
144
151
|
data: {
|
|
145
|
-
emailLog: emailLog.
|
|
152
|
+
emailLog: emailLog.documentId,
|
|
146
153
|
type: 'click',
|
|
147
154
|
timestamp: now,
|
|
148
155
|
ipAddress: req.ip || req.headers['x-forwarded-for'] || null,
|
|
@@ -152,7 +159,7 @@ module.exports = ({ strapi }) => ({
|
|
|
152
159
|
},
|
|
153
160
|
});
|
|
154
161
|
|
|
155
|
-
strapi.log.info(`[magic-mail]
|
|
162
|
+
strapi.log.info(`[magic-mail] [CLICK] Link clicked: ${emailId} -> ${targetUrl}`);
|
|
156
163
|
return event;
|
|
157
164
|
} catch (error) {
|
|
158
165
|
strapi.log.error('[magic-mail] Error recording click:', error);
|
|
@@ -162,36 +169,41 @@ module.exports = ({ strapi }) => ({
|
|
|
162
169
|
|
|
163
170
|
/**
|
|
164
171
|
* Get analytics statistics
|
|
172
|
+
* Note: Document Service doesn't have count() - using findMany for counting
|
|
165
173
|
*/
|
|
166
174
|
async getStats(filters = {}) {
|
|
167
|
-
const
|
|
175
|
+
const baseFilters = {};
|
|
168
176
|
|
|
177
|
+
// Filter by user relation - use documentId for Strapi v5
|
|
169
178
|
if (filters.userId) {
|
|
170
|
-
|
|
179
|
+
baseFilters.user = { documentId: filters.userId };
|
|
171
180
|
}
|
|
172
181
|
if (filters.templateId) {
|
|
173
|
-
|
|
182
|
+
baseFilters.templateId = filters.templateId;
|
|
174
183
|
}
|
|
175
184
|
if (filters.accountId) {
|
|
176
|
-
|
|
185
|
+
baseFilters.accountId = filters.accountId;
|
|
177
186
|
}
|
|
178
187
|
if (filters.dateFrom) {
|
|
179
|
-
|
|
188
|
+
baseFilters.sentAt = { $gte: new Date(filters.dateFrom) };
|
|
180
189
|
}
|
|
181
190
|
if (filters.dateTo) {
|
|
182
|
-
|
|
191
|
+
baseFilters.sentAt = { ...baseFilters.sentAt, $lte: new Date(filters.dateTo) };
|
|
183
192
|
}
|
|
184
193
|
|
|
194
|
+
// Use native count() method for efficient counting with filters
|
|
185
195
|
const [totalSent, totalOpened, totalClicked, totalBounced] = await Promise.all([
|
|
186
|
-
strapi.
|
|
187
|
-
|
|
188
|
-
where: { ...where, openCount: { $gt: 0 } },
|
|
196
|
+
strapi.documents(EMAIL_LOG_UID).count({
|
|
197
|
+
filters: baseFilters,
|
|
189
198
|
}),
|
|
190
|
-
strapi.
|
|
191
|
-
|
|
199
|
+
strapi.documents(EMAIL_LOG_UID).count({
|
|
200
|
+
filters: { ...baseFilters, openCount: { $gt: 0 } },
|
|
192
201
|
}),
|
|
193
|
-
strapi.
|
|
194
|
-
|
|
202
|
+
strapi.documents(EMAIL_LOG_UID).count({
|
|
203
|
+
filters: { ...baseFilters, clickCount: { $gt: 0 } },
|
|
204
|
+
}),
|
|
205
|
+
strapi.documents(EMAIL_LOG_UID).count({
|
|
206
|
+
filters: { ...baseFilters, bounced: true },
|
|
195
207
|
}),
|
|
196
208
|
]);
|
|
197
209
|
|
|
@@ -216,8 +228,9 @@ module.exports = ({ strapi }) => ({
|
|
|
216
228
|
async getEmailLogs(filters = {}, pagination = {}) {
|
|
217
229
|
const where = {};
|
|
218
230
|
|
|
231
|
+
// Filter by user relation - use documentId for Strapi v5
|
|
219
232
|
if (filters.userId) {
|
|
220
|
-
where.user = filters.userId;
|
|
233
|
+
where.user = { documentId: filters.userId };
|
|
221
234
|
}
|
|
222
235
|
if (filters.templateId) {
|
|
223
236
|
where.templateId = filters.templateId;
|
|
@@ -234,14 +247,17 @@ module.exports = ({ strapi }) => ({
|
|
|
234
247
|
const pageSize = pagination.pageSize || 25;
|
|
235
248
|
|
|
236
249
|
const [logs, total] = await Promise.all([
|
|
237
|
-
strapi.
|
|
238
|
-
where,
|
|
239
|
-
|
|
250
|
+
strapi.documents(EMAIL_LOG_UID).findMany({
|
|
251
|
+
filters: where,
|
|
252
|
+
sort: [{ sentAt: 'desc' }],
|
|
240
253
|
limit: pageSize,
|
|
241
254
|
offset: (page - 1) * pageSize,
|
|
242
255
|
populate: ['user'],
|
|
243
256
|
}),
|
|
244
|
-
|
|
257
|
+
// Get total count using native count() method
|
|
258
|
+
strapi.documents(EMAIL_LOG_UID).count({
|
|
259
|
+
filters: where,
|
|
260
|
+
}),
|
|
245
261
|
]);
|
|
246
262
|
|
|
247
263
|
return {
|
|
@@ -259,8 +275,8 @@ module.exports = ({ strapi }) => ({
|
|
|
259
275
|
* Get email log details with events
|
|
260
276
|
*/
|
|
261
277
|
async getEmailLogDetails(emailId) {
|
|
262
|
-
const emailLog = await strapi.
|
|
263
|
-
|
|
278
|
+
const emailLog = await strapi.documents(EMAIL_LOG_UID).findFirst({
|
|
279
|
+
filters: { emailId },
|
|
264
280
|
populate: ['user', 'events'],
|
|
265
281
|
});
|
|
266
282
|
|
|
@@ -271,9 +287,10 @@ module.exports = ({ strapi }) => ({
|
|
|
271
287
|
* Get user email activity
|
|
272
288
|
*/
|
|
273
289
|
async getUserActivity(userId) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
290
|
+
// Filter by user relation - use documentId for Strapi v5
|
|
291
|
+
const emailLogs = await strapi.documents(EMAIL_LOG_UID).findMany({
|
|
292
|
+
filters: { user: { documentId: userId } },
|
|
293
|
+
sort: [{ sentAt: 'desc' }],
|
|
277
294
|
limit: 50,
|
|
278
295
|
});
|
|
279
296
|
|
|
@@ -326,8 +343,8 @@ module.exports = ({ strapi }) => ({
|
|
|
326
343
|
const baseUrl = strapi.config.get('server.url') || 'http://localhost:1337';
|
|
327
344
|
|
|
328
345
|
// Get the email log for storing link associations
|
|
329
|
-
const emailLog = await strapi.
|
|
330
|
-
|
|
346
|
+
const emailLog = await strapi.documents(EMAIL_LOG_UID).findFirst({
|
|
347
|
+
filters: { emailId },
|
|
331
348
|
});
|
|
332
349
|
|
|
333
350
|
if (!emailLog) {
|
|
@@ -351,17 +368,17 @@ module.exports = ({ strapi }) => ({
|
|
|
351
368
|
const originalUrl = match[1];
|
|
352
369
|
|
|
353
370
|
// Debug: Log what we found
|
|
354
|
-
strapi.log.debug(`[magic-mail]
|
|
371
|
+
strapi.log.debug(`[magic-mail] [CHECK] Found link: ${originalUrl.substring(0, 100)}${originalUrl.length > 100 ? '...' : ''}`);
|
|
355
372
|
|
|
356
373
|
// Skip if already a tracking link or anchor
|
|
357
374
|
if (originalUrl.startsWith('#') || originalUrl.includes('/track/click/')) {
|
|
358
|
-
strapi.log.debug(`[magic-mail]
|
|
375
|
+
strapi.log.debug(`[magic-mail] [SKIP] Skipping (anchor or already tracked)`);
|
|
359
376
|
continue;
|
|
360
377
|
}
|
|
361
378
|
|
|
362
379
|
// Skip relative URLs without protocol (internal anchors, relative paths)
|
|
363
380
|
if (!originalUrl.match(/^https?:\/\//i) && !originalUrl.startsWith('/')) {
|
|
364
|
-
strapi.log.debug(`[magic-mail]
|
|
381
|
+
strapi.log.debug(`[magic-mail] [SKIP] Skipping relative URL: ${originalUrl}`);
|
|
365
382
|
continue;
|
|
366
383
|
}
|
|
367
384
|
|
|
@@ -378,7 +395,7 @@ module.exports = ({ strapi }) => ({
|
|
|
378
395
|
const trackingUrl = `${baseUrl}/api/magic-mail/track/click/${emailId}/${linkHash}/${recipientHash}`;
|
|
379
396
|
|
|
380
397
|
linkCount++;
|
|
381
|
-
strapi.log.info(`[magic-mail]
|
|
398
|
+
strapi.log.info(`[magic-mail] [LINK] Link ${linkCount}: ${originalUrl} → ${trackingUrl}`);
|
|
382
399
|
|
|
383
400
|
// Store replacement info
|
|
384
401
|
replacements.push({
|
|
@@ -390,7 +407,7 @@ module.exports = ({ strapi }) => ({
|
|
|
390
407
|
// Store all link mappings in database
|
|
391
408
|
for (const mapping of linkMappings) {
|
|
392
409
|
try {
|
|
393
|
-
await this.storeLinkMapping(emailLog.
|
|
410
|
+
await this.storeLinkMapping(emailLog.documentId, mapping.linkHash, mapping.originalUrl);
|
|
394
411
|
} catch (err) {
|
|
395
412
|
strapi.log.error('[magic-mail] Error storing link mapping:', err);
|
|
396
413
|
}
|
|
@@ -403,9 +420,9 @@ module.exports = ({ strapi }) => ({
|
|
|
403
420
|
}
|
|
404
421
|
|
|
405
422
|
if (linkCount > 0) {
|
|
406
|
-
strapi.log.info(`[magic-mail]
|
|
423
|
+
strapi.log.info(`[magic-mail] [SUCCESS] Rewrote ${linkCount} links for click tracking`);
|
|
407
424
|
} else {
|
|
408
|
-
strapi.log.warn(`[magic-mail]
|
|
425
|
+
strapi.log.warn(`[magic-mail] [WARNING] No links found in email HTML for tracking!`);
|
|
409
426
|
}
|
|
410
427
|
|
|
411
428
|
return result;
|
|
@@ -414,12 +431,12 @@ module.exports = ({ strapi }) => ({
|
|
|
414
431
|
/**
|
|
415
432
|
* Store link mapping in database
|
|
416
433
|
*/
|
|
417
|
-
async storeLinkMapping(
|
|
434
|
+
async storeLinkMapping(emailLogDocId, linkHash, originalUrl) {
|
|
418
435
|
try {
|
|
419
|
-
// Check if link already exists
|
|
420
|
-
const existing = await strapi.
|
|
421
|
-
|
|
422
|
-
emailLog:
|
|
436
|
+
// Check if link already exists - filter relation with documentId object
|
|
437
|
+
const existing = await strapi.documents(EMAIL_LINK_UID).findFirst({
|
|
438
|
+
filters: {
|
|
439
|
+
emailLog: { documentId: emailLogDocId },
|
|
423
440
|
linkHash,
|
|
424
441
|
},
|
|
425
442
|
});
|
|
@@ -430,16 +447,16 @@ module.exports = ({ strapi }) => ({
|
|
|
430
447
|
}
|
|
431
448
|
|
|
432
449
|
// Create new link mapping
|
|
433
|
-
const linkMapping = await strapi.
|
|
450
|
+
const linkMapping = await strapi.documents(EMAIL_LINK_UID).create({
|
|
434
451
|
data: {
|
|
435
|
-
emailLog:
|
|
452
|
+
emailLog: emailLogDocId,
|
|
436
453
|
linkHash,
|
|
437
454
|
originalUrl,
|
|
438
455
|
clickCount: 0,
|
|
439
456
|
},
|
|
440
457
|
});
|
|
441
458
|
|
|
442
|
-
strapi.log.debug(`[magic-mail]
|
|
459
|
+
strapi.log.debug(`[magic-mail] [SAVE] Stored link mapping: ${linkHash} → ${originalUrl}`);
|
|
443
460
|
return linkMapping;
|
|
444
461
|
} catch (error) {
|
|
445
462
|
strapi.log.error('[magic-mail] Error storing link mapping:', error);
|
|
@@ -453,8 +470,8 @@ module.exports = ({ strapi }) => ({
|
|
|
453
470
|
async getOriginalUrlFromHash(emailId, linkHash) {
|
|
454
471
|
try {
|
|
455
472
|
// Find the email log
|
|
456
|
-
const emailLog = await strapi.
|
|
457
|
-
|
|
473
|
+
const emailLog = await strapi.documents(EMAIL_LOG_UID).findFirst({
|
|
474
|
+
filters: { emailId },
|
|
458
475
|
});
|
|
459
476
|
|
|
460
477
|
if (!emailLog) {
|
|
@@ -462,10 +479,10 @@ module.exports = ({ strapi }) => ({
|
|
|
462
479
|
return null;
|
|
463
480
|
}
|
|
464
481
|
|
|
465
|
-
// Find the link mapping
|
|
466
|
-
const linkMapping = await strapi.
|
|
467
|
-
|
|
468
|
-
emailLog: emailLog.
|
|
482
|
+
// Find the link mapping - filter relation with documentId object (Strapi v5)
|
|
483
|
+
const linkMapping = await strapi.documents(EMAIL_LINK_UID).findFirst({
|
|
484
|
+
filters: {
|
|
485
|
+
emailLog: { documentId: emailLog.documentId },
|
|
469
486
|
linkHash,
|
|
470
487
|
},
|
|
471
488
|
});
|
|
@@ -477,10 +494,10 @@ module.exports = ({ strapi }) => ({
|
|
|
477
494
|
|
|
478
495
|
// Update click tracking on the link itself
|
|
479
496
|
const now = new Date();
|
|
480
|
-
await strapi.
|
|
481
|
-
|
|
497
|
+
await strapi.documents(EMAIL_LINK_UID).update({
|
|
498
|
+
documentId: linkMapping.documentId,
|
|
482
499
|
data: {
|
|
483
|
-
clickCount: linkMapping.clickCount + 1,
|
|
500
|
+
clickCount: (linkMapping.clickCount || 0) + 1,
|
|
484
501
|
firstClickedAt: linkMapping.firstClickedAt || now,
|
|
485
502
|
lastClickedAt: now,
|
|
486
503
|
},
|
|
@@ -493,4 +510,3 @@ module.exports = ({ strapi }) => ({
|
|
|
493
510
|
}
|
|
494
511
|
},
|
|
495
512
|
});
|
|
496
|
-
|