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.
Files changed (91) hide show
  1. package/COPYRIGHT_NOTICE.txt +13 -0
  2. package/LICENSE +22 -0
  3. package/README.md +1420 -0
  4. package/admin/jsconfig.json +10 -0
  5. package/admin/src/components/AddAccountModal.jsx +1943 -0
  6. package/admin/src/components/Initializer.jsx +14 -0
  7. package/admin/src/components/LicenseGuard.jsx +475 -0
  8. package/admin/src/components/PluginIcon.jsx +5 -0
  9. package/admin/src/hooks/useAuthRefresh.js +44 -0
  10. package/admin/src/hooks/useLicense.js +158 -0
  11. package/admin/src/index.js +86 -0
  12. package/admin/src/pages/Analytics.jsx +762 -0
  13. package/admin/src/pages/App.jsx +111 -0
  14. package/admin/src/pages/EmailDesigner/EditorPage.jsx +1405 -0
  15. package/admin/src/pages/EmailDesigner/TemplateList.jsx +1807 -0
  16. package/admin/src/pages/HomePage.jsx +1233 -0
  17. package/admin/src/pages/LicensePage.jsx +424 -0
  18. package/admin/src/pages/RoutingRules.jsx +1141 -0
  19. package/admin/src/pages/Settings.jsx +603 -0
  20. package/admin/src/pluginId.js +3 -0
  21. package/admin/src/translations/de.json +71 -0
  22. package/admin/src/translations/en.json +70 -0
  23. package/admin/src/translations/es.json +71 -0
  24. package/admin/src/translations/fr.json +71 -0
  25. package/admin/src/translations/pt.json +71 -0
  26. package/admin/src/utils/fetchWithRetry.js +123 -0
  27. package/admin/src/utils/getTranslation.js +5 -0
  28. package/dist/_chunks/App-B-Gp4Vbr.js +7568 -0
  29. package/dist/_chunks/App-BymMjoGM.mjs +7543 -0
  30. package/dist/_chunks/LicensePage-Bl02myMx.mjs +342 -0
  31. package/dist/_chunks/LicensePage-CJXwPnEe.js +344 -0
  32. package/dist/_chunks/Settings-C_TmKwcz.mjs +400 -0
  33. package/dist/_chunks/Settings-zuFQ3pnn.js +402 -0
  34. package/dist/_chunks/de-CN-G9j1S.js +64 -0
  35. package/dist/_chunks/de-DS04rP54.mjs +64 -0
  36. package/dist/_chunks/en-BDc7Jk8u.js +64 -0
  37. package/dist/_chunks/en-BEFQJXvR.mjs +64 -0
  38. package/dist/_chunks/es-BpV1MIdm.js +64 -0
  39. package/dist/_chunks/es-DQHwzPpP.mjs +64 -0
  40. package/dist/_chunks/fr-BG1WfEVm.mjs +64 -0
  41. package/dist/_chunks/fr-vpziIpRp.js +64 -0
  42. package/dist/_chunks/pt-CMoGrOib.mjs +64 -0
  43. package/dist/_chunks/pt-ODpAhDNa.js +64 -0
  44. package/dist/admin/index.js +89 -0
  45. package/dist/admin/index.mjs +90 -0
  46. package/dist/server/index.js +6214 -0
  47. package/dist/server/index.mjs +6208 -0
  48. package/package.json +113 -0
  49. package/server/jsconfig.json +10 -0
  50. package/server/src/bootstrap.js +153 -0
  51. package/server/src/config/features.js +260 -0
  52. package/server/src/config/index.js +6 -0
  53. package/server/src/content-types/email-account/schema.json +93 -0
  54. package/server/src/content-types/email-event/index.js +8 -0
  55. package/server/src/content-types/email-event/schema.json +57 -0
  56. package/server/src/content-types/email-link/index.js +8 -0
  57. package/server/src/content-types/email-link/schema.json +49 -0
  58. package/server/src/content-types/email-log/index.js +8 -0
  59. package/server/src/content-types/email-log/schema.json +106 -0
  60. package/server/src/content-types/email-template/schema.json +74 -0
  61. package/server/src/content-types/email-template-version/schema.json +60 -0
  62. package/server/src/content-types/index.js +33 -0
  63. package/server/src/content-types/routing-rule/schema.json +59 -0
  64. package/server/src/controllers/accounts.js +220 -0
  65. package/server/src/controllers/analytics.js +347 -0
  66. package/server/src/controllers/controller.js +26 -0
  67. package/server/src/controllers/email-designer.js +474 -0
  68. package/server/src/controllers/index.js +21 -0
  69. package/server/src/controllers/license.js +267 -0
  70. package/server/src/controllers/oauth.js +474 -0
  71. package/server/src/controllers/routing-rules.js +122 -0
  72. package/server/src/controllers/test.js +383 -0
  73. package/server/src/destroy.js +23 -0
  74. package/server/src/index.js +25 -0
  75. package/server/src/middlewares/index.js +3 -0
  76. package/server/src/policies/index.js +3 -0
  77. package/server/src/register.js +5 -0
  78. package/server/src/routes/admin.js +469 -0
  79. package/server/src/routes/content-api.js +37 -0
  80. package/server/src/routes/index.js +9 -0
  81. package/server/src/services/account-manager.js +277 -0
  82. package/server/src/services/analytics.js +496 -0
  83. package/server/src/services/email-designer.js +870 -0
  84. package/server/src/services/email-router.js +1420 -0
  85. package/server/src/services/index.js +17 -0
  86. package/server/src/services/license-guard.js +418 -0
  87. package/server/src/services/oauth.js +515 -0
  88. package/server/src/services/service.js +7 -0
  89. package/server/src/utils/encryption.js +81 -0
  90. package/strapi-admin.js +4 -0
  91. package/strapi-server.js +4 -0
@@ -0,0 +1,474 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * OAuth Controller
5
+ * Handles OAuth authentication flows
6
+ */
7
+
8
+ module.exports = {
9
+ /**
10
+ * Initiate Gmail OAuth flow
11
+ */
12
+ async gmailAuth(ctx) {
13
+ try {
14
+ const { clientId } = ctx.query;
15
+
16
+ if (!clientId) {
17
+ return ctx.badRequest('Client ID is required');
18
+ }
19
+
20
+ const oauthService = strapi.plugin('magic-mail').service('oauth');
21
+ const state = Buffer.from(JSON.stringify({
22
+ timestamp: Date.now(),
23
+ clientId,
24
+ })).toString('base64');
25
+
26
+ const authUrl = oauthService.getGmailAuthUrl(clientId, state);
27
+
28
+ ctx.body = {
29
+ authUrl,
30
+ message: 'Redirect user to this URL to authorize',
31
+ };
32
+ } catch (err) {
33
+ strapi.log.error('[magic-mail] Gmail OAuth init error:', err);
34
+ ctx.throw(500, err.message);
35
+ }
36
+ },
37
+
38
+ /**
39
+ * Handle Gmail OAuth callback
40
+ */
41
+ async gmailCallback(ctx) {
42
+ try {
43
+ const { code, state, error } = ctx.query;
44
+
45
+ if (error) {
46
+ // OAuth was denied or failed
47
+ ctx.type = 'html';
48
+ ctx.body = `
49
+ <!DOCTYPE html>
50
+ <html>
51
+ <head>
52
+ <title>OAuth Failed</title>
53
+ <style>
54
+ body { font-family: system-ui; text-align: center; padding: 50px; }
55
+ .error { color: #ef4444; font-size: 24px; margin: 20px 0; }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="error">❌ OAuth Authorization Failed</div>
60
+ <p>Error: ${error}</p>
61
+ <p>You can close this window and try again.</p>
62
+ <script>
63
+ setTimeout(() => window.close(), 3000);
64
+ </script>
65
+ </body>
66
+ </html>
67
+ `;
68
+ return;
69
+ }
70
+
71
+ if (!code) {
72
+ return ctx.badRequest('No authorization code received');
73
+ }
74
+
75
+ // Success - send code to parent window and close popup
76
+ ctx.type = 'html';
77
+ ctx.body = `
78
+ <!DOCTYPE html>
79
+ <html>
80
+ <head>
81
+ <title>OAuth Success</title>
82
+ <style>
83
+ body {
84
+ font-family: system-ui;
85
+ text-align: center;
86
+ padding: 50px;
87
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
88
+ color: white;
89
+ }
90
+ .success { font-size: 72px; margin: 20px 0; }
91
+ .message { font-size: 24px; font-weight: 600; }
92
+ .note { font-size: 14px; opacity: 0.9; margin-top: 20px; }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <div class="success">✅</div>
97
+ <div class="message">Gmail OAuth Authorized!</div>
98
+ <div class="note">Closing window...</div>
99
+ <script>
100
+ if (window.opener) {
101
+ // Send data to parent window
102
+ window.opener.postMessage({
103
+ type: 'gmail-oauth-success',
104
+ code: '${code}',
105
+ state: '${state}'
106
+ }, window.location.origin);
107
+
108
+ setTimeout(() => window.close(), 1500);
109
+ } else {
110
+ // Fallback: redirect to admin panel
111
+ setTimeout(() => {
112
+ window.location.href = '/admin/plugins/magic-mail?oauth_code=${code}&oauth_state=${state}';
113
+ }, 2000);
114
+ }
115
+ </script>
116
+ </body>
117
+ </html>
118
+ `;
119
+ } catch (err) {
120
+ strapi.log.error('[magic-mail] Gmail OAuth callback error:', err);
121
+ ctx.throw(500, err.message);
122
+ }
123
+ },
124
+
125
+ /**
126
+ * Initiate Microsoft OAuth flow
127
+ */
128
+ async microsoftAuth(ctx) {
129
+ try {
130
+ const { clientId, tenantId } = ctx.query;
131
+
132
+ if (!clientId) {
133
+ return ctx.badRequest('Client ID is required');
134
+ }
135
+
136
+ if (!tenantId) {
137
+ return ctx.badRequest('Tenant ID is required');
138
+ }
139
+
140
+ const oauthService = strapi.plugin('magic-mail').service('oauth');
141
+ const state = Buffer.from(JSON.stringify({
142
+ timestamp: Date.now(),
143
+ clientId,
144
+ tenantId,
145
+ })).toString('base64');
146
+
147
+ const authUrl = oauthService.getMicrosoftAuthUrl(clientId, tenantId, state);
148
+
149
+ ctx.body = {
150
+ authUrl,
151
+ message: 'Redirect user to this URL to authorize',
152
+ };
153
+ } catch (err) {
154
+ strapi.log.error('[magic-mail] Microsoft OAuth init error:', err);
155
+ ctx.throw(500, err.message);
156
+ }
157
+ },
158
+
159
+ /**
160
+ * Handle Microsoft OAuth callback
161
+ */
162
+ async microsoftCallback(ctx) {
163
+ try {
164
+ const { code, state, error } = ctx.query;
165
+
166
+ if (error) {
167
+ // OAuth was denied or failed
168
+ ctx.type = 'html';
169
+ ctx.body = `
170
+ <!DOCTYPE html>
171
+ <html>
172
+ <head>
173
+ <title>OAuth Failed</title>
174
+ <style>
175
+ body { font-family: system-ui; text-align: center; padding: 50px; }
176
+ .error { color: #ef4444; font-size: 24px; margin: 20px 0; }
177
+ </style>
178
+ </head>
179
+ <body>
180
+ <div class="error">❌ OAuth Authorization Failed</div>
181
+ <p>Error: ${error}</p>
182
+ <p>You can close this window and try again.</p>
183
+ <script>
184
+ setTimeout(() => window.close(), 3000);
185
+ </script>
186
+ </body>
187
+ </html>
188
+ `;
189
+ return;
190
+ }
191
+
192
+ if (!code) {
193
+ return ctx.badRequest('No authorization code received');
194
+ }
195
+
196
+ // Success - send code to parent window and close popup
197
+ ctx.type = 'html';
198
+ ctx.body = `
199
+ <!DOCTYPE html>
200
+ <html>
201
+ <head>
202
+ <title>OAuth Success</title>
203
+ <style>
204
+ body {
205
+ font-family: system-ui;
206
+ text-align: center;
207
+ padding: 50px;
208
+ background: linear-gradient(135deg, #00A4EF 0%, #0078D4 100%);
209
+ color: white;
210
+ }
211
+ .success { font-size: 72px; margin: 20px 0; }
212
+ .message { font-size: 24px; font-weight: 600; }
213
+ .note { font-size: 14px; opacity: 0.9; margin-top: 20px; }
214
+ </style>
215
+ </head>
216
+ <body>
217
+ <div class="success">✅</div>
218
+ <div class="message">Microsoft OAuth Authorized!</div>
219
+ <div class="note">Closing window...</div>
220
+ <script>
221
+ if (window.opener) {
222
+ // Send data to parent window
223
+ window.opener.postMessage({
224
+ type: 'microsoft-oauth-success',
225
+ code: '${code}',
226
+ state: '${state}'
227
+ }, window.location.origin);
228
+
229
+ setTimeout(() => window.close(), 1500);
230
+ } else {
231
+ // Fallback: redirect to admin panel
232
+ setTimeout(() => {
233
+ window.location.href = '/admin/plugins/magic-mail?oauth_code=${code}&oauth_state=${state}';
234
+ }, 2000);
235
+ }
236
+ </script>
237
+ </body>
238
+ </html>
239
+ `;
240
+ } catch (err) {
241
+ strapi.log.error('[magic-mail] Microsoft OAuth callback error:', err);
242
+ ctx.throw(500, err.message);
243
+ }
244
+ },
245
+
246
+ /**
247
+ * Initiate Yahoo OAuth flow
248
+ */
249
+ async yahooAuth(ctx) {
250
+ try {
251
+ const { clientId } = ctx.query;
252
+
253
+ if (!clientId) {
254
+ return ctx.badRequest('Client ID is required');
255
+ }
256
+
257
+ const oauthService = strapi.plugin('magic-mail').service('oauth');
258
+ const state = Buffer.from(JSON.stringify({
259
+ timestamp: Date.now(),
260
+ clientId,
261
+ })).toString('base64');
262
+
263
+ const authUrl = oauthService.getYahooAuthUrl(clientId, state);
264
+
265
+ ctx.body = {
266
+ authUrl,
267
+ message: 'Redirect user to this URL to authorize',
268
+ };
269
+ } catch (err) {
270
+ strapi.log.error('[magic-mail] Yahoo OAuth init error:', err);
271
+ ctx.throw(500, err.message);
272
+ }
273
+ },
274
+
275
+ /**
276
+ * Handle Yahoo OAuth callback
277
+ */
278
+ async yahooCallback(ctx) {
279
+ try {
280
+ const { code, state, error } = ctx.query;
281
+
282
+ if (error) {
283
+ // OAuth was denied or failed
284
+ ctx.type = 'html';
285
+ ctx.body = `
286
+ <!DOCTYPE html>
287
+ <html>
288
+ <head>
289
+ <title>OAuth Failed</title>
290
+ <style>
291
+ body { font-family: system-ui; text-align: center; padding: 50px; }
292
+ .error { color: #ef4444; font-size: 24px; margin: 20px 0; }
293
+ </style>
294
+ </head>
295
+ <body>
296
+ <div class="error">❌ OAuth Authorization Failed</div>
297
+ <p>Error: ${error}</p>
298
+ <p>You can close this window and try again.</p>
299
+ <script>
300
+ setTimeout(() => window.close(), 3000);
301
+ </script>
302
+ </body>
303
+ </html>
304
+ `;
305
+ return;
306
+ }
307
+
308
+ if (!code) {
309
+ return ctx.badRequest('No authorization code received');
310
+ }
311
+
312
+ // Success - send code to parent window and close popup
313
+ ctx.type = 'html';
314
+ ctx.body = `
315
+ <!DOCTYPE html>
316
+ <html>
317
+ <head>
318
+ <title>OAuth Success</title>
319
+ <style>
320
+ body {
321
+ font-family: system-ui;
322
+ text-align: center;
323
+ padding: 50px;
324
+ background: linear-gradient(135deg, #6001D2 0%, #410096 100%);
325
+ color: white;
326
+ }
327
+ .success { font-size: 72px; margin: 20px 0; }
328
+ .message { font-size: 24px; font-weight: 600; }
329
+ .note { font-size: 14px; opacity: 0.9; margin-top: 20px; }
330
+ </style>
331
+ </head>
332
+ <body>
333
+ <div class="success">✅</div>
334
+ <div class="message">Yahoo Mail OAuth Authorized!</div>
335
+ <div class="note">Closing window...</div>
336
+ <script>
337
+ if (window.opener) {
338
+ // Send data to parent window
339
+ window.opener.postMessage({
340
+ type: 'yahoo-oauth-success',
341
+ code: '${code}',
342
+ state: '${state}'
343
+ }, window.location.origin);
344
+
345
+ setTimeout(() => window.close(), 1500);
346
+ } else {
347
+ // Fallback: redirect to admin panel
348
+ setTimeout(() => {
349
+ window.location.href = '/admin/plugins/magic-mail?oauth_code=${code}&oauth_state=${state}';
350
+ }, 2000);
351
+ }
352
+ </script>
353
+ </body>
354
+ </html>
355
+ `;
356
+ } catch (err) {
357
+ strapi.log.error('[magic-mail] Yahoo OAuth callback error:', err);
358
+ ctx.throw(500, err.message);
359
+ }
360
+ },
361
+
362
+ /**
363
+ * Create account from OAuth tokens
364
+ */
365
+ async createOAuthAccount(ctx) {
366
+ try {
367
+ const { provider, code, state, accountDetails } = ctx.request.body;
368
+
369
+ strapi.log.info('[magic-mail] Creating OAuth account...');
370
+ strapi.log.info('[magic-mail] Provider:', provider);
371
+ strapi.log.info('[magic-mail] Account name:', accountDetails?.name);
372
+
373
+ if (provider !== 'gmail' && provider !== 'microsoft' && provider !== 'yahoo') {
374
+ return ctx.badRequest('Only Gmail, Microsoft and Yahoo OAuth supported');
375
+ }
376
+
377
+ if (!code) {
378
+ return ctx.badRequest('OAuth code is required');
379
+ }
380
+
381
+ // License check for OAuth provider
382
+ const licenseGuard = strapi.plugin('magic-mail').service('license-guard');
383
+ const providerKey = `${provider}-oauth`;
384
+ const providerAllowed = await licenseGuard.isProviderAllowed(providerKey);
385
+
386
+ if (!providerAllowed) {
387
+ ctx.throw(403, `OAuth provider "${provider}" requires a Premium license or higher. Please upgrade your license.`);
388
+ return;
389
+ }
390
+
391
+ // Check account limit
392
+ const currentAccounts = await strapi.entityService.count('plugin::magic-mail.email-account');
393
+ const maxAccounts = await licenseGuard.getMaxAccounts();
394
+
395
+ if (maxAccounts !== -1 && currentAccounts >= maxAccounts) {
396
+ ctx.throw(403, `Account limit reached (${maxAccounts}). Upgrade your license to add more accounts.`);
397
+ return;
398
+ }
399
+
400
+ // Decode state to get clientId
401
+ const stateData = JSON.parse(Buffer.from(state, 'base64').toString());
402
+
403
+ if (!accountDetails.config?.clientId || !accountDetails.config?.clientSecret) {
404
+ return ctx.badRequest('Client ID and Secret are required');
405
+ }
406
+
407
+ const oauthService = strapi.plugin('magic-mail').service('oauth');
408
+
409
+ // Exchange code for tokens
410
+ let tokenData;
411
+ if (provider === 'gmail') {
412
+ strapi.log.info('[magic-mail] Calling exchangeGoogleCode...');
413
+ tokenData = await oauthService.exchangeGoogleCode(
414
+ code,
415
+ accountDetails.config.clientId,
416
+ accountDetails.config.clientSecret
417
+ );
418
+ } else if (provider === 'microsoft') {
419
+ strapi.log.info('[magic-mail] Calling exchangeMicrosoftCode...');
420
+
421
+ if (!accountDetails.config.tenantId) {
422
+ throw new Error('Tenant ID is required for Microsoft OAuth');
423
+ }
424
+
425
+ tokenData = await oauthService.exchangeMicrosoftCode(
426
+ code,
427
+ accountDetails.config.clientId,
428
+ accountDetails.config.clientSecret,
429
+ accountDetails.config.tenantId
430
+ );
431
+ } else if (provider === 'yahoo') {
432
+ strapi.log.info('[magic-mail] Calling exchangeYahooCode...');
433
+ tokenData = await oauthService.exchangeYahooCode(
434
+ code,
435
+ accountDetails.config.clientId,
436
+ accountDetails.config.clientSecret
437
+ );
438
+ }
439
+
440
+ strapi.log.info('[magic-mail] Token data received:', {
441
+ email: tokenData.email,
442
+ hasAccessToken: !!tokenData.accessToken,
443
+ hasRefreshToken: !!tokenData.refreshToken,
444
+ });
445
+
446
+ if (!tokenData.email) {
447
+ strapi.log.error('[magic-mail] No email in tokenData!');
448
+ throw new Error(`Failed to get email from ${provider} OAuth`);
449
+ }
450
+
451
+ // Store account
452
+ strapi.log.info('[magic-mail] Calling storeOAuthAccount...');
453
+ const account = await oauthService.storeOAuthAccount(
454
+ provider,
455
+ tokenData,
456
+ accountDetails,
457
+ accountDetails.config // contains clientId and clientSecret
458
+ );
459
+
460
+ strapi.log.info('[magic-mail] ✅ OAuth account created successfully');
461
+
462
+ ctx.body = {
463
+ success: true,
464
+ data: account,
465
+ message: 'OAuth account created successfully',
466
+ };
467
+ } catch (err) {
468
+ strapi.log.error('[magic-mail] Create OAuth account error:', err);
469
+ strapi.log.error('[magic-mail] Error stack:', err.stack);
470
+ ctx.throw(500, err.message);
471
+ }
472
+ },
473
+ };
474
+
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Routing Rules Controller
5
+ * Manages email routing rules CRUD operations
6
+ */
7
+
8
+ module.exports = {
9
+ /**
10
+ * Get all routing rules
11
+ */
12
+ async getAll(ctx) {
13
+ try {
14
+ const rules = await strapi.entityService.findMany('plugin::magic-mail.routing-rule', {
15
+ sort: { priority: 'desc' },
16
+ });
17
+
18
+ ctx.body = {
19
+ data: rules,
20
+ meta: { count: rules.length },
21
+ };
22
+ } catch (err) {
23
+ strapi.log.error('[magic-mail] Error getting routing rules:', err);
24
+ ctx.throw(500, 'Error fetching routing rules');
25
+ }
26
+ },
27
+
28
+ /**
29
+ * Get single routing rule
30
+ */
31
+ async getOne(ctx) {
32
+ try {
33
+ const { ruleId } = ctx.params;
34
+ const rule = await strapi.entityService.findOne('plugin::magic-mail.routing-rule', ruleId);
35
+
36
+ if (!rule) {
37
+ ctx.throw(404, 'Routing rule not found');
38
+ }
39
+
40
+ ctx.body = {
41
+ data: rule,
42
+ };
43
+ } catch (err) {
44
+ strapi.log.error('[magic-mail] Error getting routing rule:', err);
45
+ ctx.throw(500, 'Error fetching routing rule');
46
+ }
47
+ },
48
+
49
+ /**
50
+ * Create new routing rule
51
+ */
52
+ async create(ctx) {
53
+ try {
54
+ const licenseGuard = strapi.plugin('magic-mail').service('license-guard');
55
+
56
+ // Check routing rule limit
57
+ const currentRules = await strapi.entityService.count('plugin::magic-mail.routing-rule');
58
+ const maxRules = await licenseGuard.getMaxRoutingRules();
59
+
60
+ if (maxRules !== -1 && currentRules >= maxRules) {
61
+ ctx.throw(403, `Routing rule limit reached (${maxRules}). Upgrade to Advanced license for unlimited rules.`);
62
+ return;
63
+ }
64
+
65
+ const rule = await strapi.entityService.create('plugin::magic-mail.routing-rule', {
66
+ data: ctx.request.body,
67
+ });
68
+
69
+ ctx.body = {
70
+ data: rule,
71
+ message: 'Routing rule created successfully',
72
+ };
73
+
74
+ strapi.log.info(`[magic-mail] ✅ Routing rule created: ${rule.name}`);
75
+ } catch (err) {
76
+ strapi.log.error('[magic-mail] Error creating routing rule:', err);
77
+ ctx.throw(err.status || 500, err.message || 'Error creating routing rule');
78
+ }
79
+ },
80
+
81
+ /**
82
+ * Update routing rule
83
+ */
84
+ async update(ctx) {
85
+ try {
86
+ const { ruleId } = ctx.params;
87
+ const rule = await strapi.entityService.update('plugin::magic-mail.routing-rule', ruleId, {
88
+ data: ctx.request.body,
89
+ });
90
+
91
+ ctx.body = {
92
+ data: rule,
93
+ message: 'Routing rule updated successfully',
94
+ };
95
+
96
+ strapi.log.info(`[magic-mail] ✅ Routing rule updated: ${rule.name}`);
97
+ } catch (err) {
98
+ strapi.log.error('[magic-mail] Error updating routing rule:', err);
99
+ ctx.throw(500, err.message || 'Error updating routing rule');
100
+ }
101
+ },
102
+
103
+ /**
104
+ * Delete routing rule
105
+ */
106
+ async delete(ctx) {
107
+ try {
108
+ const { ruleId } = ctx.params;
109
+ await strapi.entityService.delete('plugin::magic-mail.routing-rule', ruleId);
110
+
111
+ ctx.body = {
112
+ message: 'Routing rule deleted successfully',
113
+ };
114
+
115
+ strapi.log.info(`[magic-mail] Routing rule deleted: ${ruleId}`);
116
+ } catch (err) {
117
+ strapi.log.error('[magic-mail] Error deleting routing rule:', err);
118
+ ctx.throw(500, 'Error deleting routing rule');
119
+ }
120
+ },
121
+ };
122
+