@jhits/plugin-newsletter 0.0.8 → 0.0.9

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 (75) hide show
  1. package/data/locales/en/common.json +12 -0
  2. package/data/locales/nl/common.json +12 -0
  3. package/data/locales/sv/common.json +12 -0
  4. package/dist/api/email-utils.d.ts +6 -0
  5. package/dist/api/email-utils.d.ts.map +1 -0
  6. package/dist/api/email-utils.js +134 -0
  7. package/dist/api/handler.d.ts +2 -47
  8. package/dist/api/handler.d.ts.map +1 -1
  9. package/dist/api/handler.js +2 -523
  10. package/dist/api/handlers/index.d.ts +12 -0
  11. package/dist/api/handlers/index.d.ts.map +1 -0
  12. package/dist/api/handlers/index.js +11 -0
  13. package/dist/api/handlers/newsletters.d.ts +11 -0
  14. package/dist/api/handlers/newsletters.d.ts.map +1 -0
  15. package/dist/api/handlers/newsletters.js +250 -0
  16. package/dist/api/handlers/send-newsletter.d.ts +8 -0
  17. package/dist/api/handlers/send-newsletter.d.ts.map +1 -0
  18. package/dist/api/handlers/send-newsletter.js +206 -0
  19. package/dist/api/handlers/settings.d.ts +11 -0
  20. package/dist/api/handlers/settings.d.ts.map +1 -0
  21. package/dist/api/handlers/settings.js +304 -0
  22. package/dist/api/handlers/subscribers.d.ts +10 -0
  23. package/dist/api/handlers/subscribers.d.ts.map +1 -0
  24. package/dist/api/handlers/subscribers.js +96 -0
  25. package/dist/api/handlers/upload.d.ts +7 -0
  26. package/dist/api/handlers/upload.d.ts.map +1 -0
  27. package/dist/api/handlers/upload.js +32 -0
  28. package/dist/api/handlers/welcome-email.d.ts +13 -0
  29. package/dist/api/handlers/welcome-email.d.ts.map +1 -0
  30. package/dist/api/handlers/welcome-email.js +142 -0
  31. package/dist/api/router.d.ts.map +1 -1
  32. package/dist/api/router.js +45 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +42 -16
  35. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  36. package/dist/lib/email/EmailRenderer.js +3 -7
  37. package/dist/lib/i18n.d.ts +16 -0
  38. package/dist/lib/i18n.d.ts.map +1 -0
  39. package/dist/lib/i18n.js +60 -0
  40. package/dist/state/EditorContext.d.ts +3 -1
  41. package/dist/state/EditorContext.d.ts.map +1 -1
  42. package/dist/state/EditorContext.js +1 -5
  43. package/dist/state/reducer.js +5 -5
  44. package/dist/types/newsletter.d.ts +1 -0
  45. package/dist/types/newsletter.d.ts.map +1 -1
  46. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +4 -2
  47. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  48. package/dist/views/CanvasEditor/CanvasEditorView.js +73 -10
  49. package/dist/views/CanvasEditor/EditorHeader.d.ts +6 -1
  50. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
  51. package/dist/views/CanvasEditor/EditorHeader.js +66 -11
  52. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +6 -2
  53. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  54. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  55. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts +1 -1
  56. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -1
  57. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +41 -5
  58. package/dist/views/NewsletterEditor.d.ts +4 -2
  59. package/dist/views/NewsletterEditor.d.ts.map +1 -1
  60. package/dist/views/NewsletterEditor.js +2 -2
  61. package/dist/views/NewsletterManager.d.ts.map +1 -1
  62. package/dist/views/NewsletterManager.js +137 -8
  63. package/dist/views/components/SendNewsletterModal.d.ts +18 -0
  64. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -0
  65. package/dist/views/components/SendNewsletterModal.js +107 -0
  66. package/dist/views/components/SmtpSettingsModal.d.ts +10 -0
  67. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -0
  68. package/dist/views/components/SmtpSettingsModal.js +145 -0
  69. package/dist/views/components/TestEmailModal.d.ts +10 -0
  70. package/dist/views/components/TestEmailModal.d.ts.map +1 -0
  71. package/dist/views/components/TestEmailModal.js +99 -0
  72. package/package.json +20 -9
  73. package/templates/logo.png +0 -0
  74. package/templates/test-email.hbs +221 -0
  75. package/templates/welcome-email-legacy.hbs +35 -0
@@ -1,526 +1,5 @@
1
1
  /**
2
2
  * Newsletter API Handler
3
- * Handles all newsletter-related API requests
3
+ * Re-exports all handler functions from handlers folder
4
4
  */
5
- 'use server';
6
- import { NextResponse } from 'next/server';
7
- import { generateSlugFromTitle } from '../lib/utils/slugify';
8
- import nodemailer from 'nodemailer';
9
- /**
10
- * GET /api/plugin-newsletter/subscribers - List all subscribers
11
- */
12
- export async function GET_SUBSCRIBERS(req, config) {
13
- try {
14
- const userId = await config.getUserId?.(req);
15
- if (!userId) {
16
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
17
- }
18
- const dbConnection = await config.getDb();
19
- const db = dbConnection.db();
20
- const subscribers = db.collection('subscribers');
21
- const subscriberList = await subscribers
22
- .find({})
23
- .sort({ subscribedAt: -1 })
24
- .toArray();
25
- return NextResponse.json(subscriberList);
26
- }
27
- catch (error) {
28
- console.error('[NewsletterAPI] GET_SUBSCRIBERS error:', error);
29
- return NextResponse.json({ error: 'Failed to fetch subscribers', detail: error.message }, { status: 500 });
30
- }
31
- }
32
- /**
33
- * POST /api/plugin-newsletter/subscribers - Subscribe new email
34
- */
35
- export async function POST_SUBSCRIBE(req, config) {
36
- try {
37
- const body = await req.json();
38
- const { email, language } = body;
39
- if (!email || !email.includes('@')) {
40
- return NextResponse.json({ error: 'Invalid email address' }, { status: 400 });
41
- }
42
- const dbConnection = await config.getDb();
43
- const db = dbConnection.db();
44
- const subscribers = db.collection('subscribers');
45
- // Check if already subscribed
46
- const existing = await subscribers.findOne({ email: email.toLowerCase() });
47
- if (existing) {
48
- return NextResponse.json({ error: 'You are already subscribed!' }, { status: 409 });
49
- }
50
- // Add subscriber
51
- await subscribers.insertOne({
52
- email: email.toLowerCase(),
53
- language: language || 'en',
54
- subscribedAt: new Date(),
55
- status: 'active',
56
- });
57
- // Send welcome email if configured
58
- if (config.emailConfig) {
59
- const headers = await req.headers;
60
- const host = headers.get('host') || undefined;
61
- await sendWelcomeEmail(config, email, language || 'en', host);
62
- }
63
- return NextResponse.json({ message: 'Successfully subscribed' }, { status: 201 });
64
- }
65
- catch (error) {
66
- console.error('[NewsletterAPI] POST_SUBSCRIBE error:', error);
67
- return NextResponse.json({ error: 'Failed to subscribe', detail: error.message }, { status: 500 });
68
- }
69
- }
70
- /**
71
- * GET /api/plugin-newsletter/subscribers/[email] - Get specific subscriber
72
- */
73
- export async function GET_SUBSCRIBER(req, email, config) {
74
- try {
75
- const userId = await config.getUserId?.(req);
76
- if (!userId) {
77
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
78
- }
79
- const dbConnection = await config.getDb();
80
- const db = dbConnection.db();
81
- const subscribers = db.collection('subscribers');
82
- const subscriber = await subscribers.findOne({ email: email.toLowerCase() });
83
- if (!subscriber) {
84
- return NextResponse.json({ error: 'Subscriber not found' }, { status: 404 });
85
- }
86
- return NextResponse.json(subscriber);
87
- }
88
- catch (error) {
89
- console.error('[NewsletterAPI] GET_SUBSCRIBER error:', error);
90
- return NextResponse.json({ error: 'Failed to fetch subscriber', detail: error.message }, { status: 500 });
91
- }
92
- }
93
- /**
94
- * DELETE /api/plugin-newsletter/subscribers/[email] - Unsubscribe/delete subscriber
95
- */
96
- export async function DELETE_SUBSCRIBER(req, email, config) {
97
- try {
98
- const userId = await config.getUserId?.(req);
99
- if (!userId) {
100
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
101
- }
102
- const dbConnection = await config.getDb();
103
- const db = dbConnection.db();
104
- const subscribers = db.collection('subscribers');
105
- const result = await subscribers.deleteOne({ email: email.toLowerCase() });
106
- if (result.deletedCount === 0) {
107
- return NextResponse.json({ error: 'Subscriber not found' }, { status: 404 });
108
- }
109
- return NextResponse.json({ message: 'Subscriber successfully removed' });
110
- }
111
- catch (error) {
112
- console.error('[NewsletterAPI] DELETE_SUBSCRIBER error:', error);
113
- return NextResponse.json({ error: 'Failed to delete subscriber', detail: error.message }, { status: 500 });
114
- }
115
- }
116
- /**
117
- * GET /api/plugin-newsletter/settings - Get newsletter settings
118
- */
119
- export async function GET_SETTINGS(req, config) {
120
- try {
121
- const userId = await config.getUserId?.(req);
122
- if (!userId) {
123
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
124
- }
125
- const dbConnection = await config.getDb();
126
- const db = dbConnection.db();
127
- const newsletters = db.collection('newsletters');
128
- const settings = await newsletters.findOne({ id: 'welcome_automation' });
129
- return NextResponse.json(settings || {
130
- id: 'welcome_automation',
131
- languages: {
132
- nl: { title: '', message: '' },
133
- en: { title: '', message: '' },
134
- sv: { title: '', message: '' },
135
- },
136
- });
137
- }
138
- catch (error) {
139
- console.error('[NewsletterAPI] GET_SETTINGS error:', error);
140
- return NextResponse.json({ error: 'Failed to fetch settings', detail: error.message }, { status: 500 });
141
- }
142
- }
143
- /**
144
- * POST /api/plugin-newsletter/settings - Update newsletter settings
145
- */
146
- export async function POST_SETTINGS(req, config) {
147
- try {
148
- const userId = await config.getUserId?.(req);
149
- if (!userId) {
150
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
151
- }
152
- const body = await req.json();
153
- const dbConnection = await config.getDb();
154
- const db = dbConnection.db();
155
- const newsletters = db.collection('newsletters');
156
- await newsletters.updateOne({ id: 'welcome_automation' }, {
157
- $set: {
158
- id: 'welcome_automation',
159
- languages: body.languages || {},
160
- updatedAt: new Date(),
161
- },
162
- }, { upsert: true });
163
- return NextResponse.json({ success: true, message: 'Settings updated successfully' });
164
- }
165
- catch (error) {
166
- console.error('[NewsletterAPI] POST_SETTINGS error:', error);
167
- return NextResponse.json({ error: 'Failed to update settings', detail: error.message }, { status: 500 });
168
- }
169
- }
170
- /**
171
- * Send welcome email to new subscriber
172
- */
173
- async function sendWelcomeEmail(config, email, language, host) {
174
- if (!config.emailConfig)
175
- return;
176
- try {
177
- const transporter = nodemailer.createTransport({
178
- host: config.emailConfig.host,
179
- port: config.emailConfig.port,
180
- secure: true,
181
- auth: {
182
- user: config.emailConfig.user,
183
- pass: config.emailConfig.password,
184
- },
185
- connectionTimeout: 10000,
186
- });
187
- const baseUrl = host
188
- ? (host.includes('localhost') ? 'http' : 'https') + '://' + host
189
- : config.baseUrl || 'https://bya.jorishummel.com';
190
- const slugs = {
191
- sv: '/avmälla',
192
- nl: '/afmelden',
193
- en: '/unsubscribe',
194
- };
195
- const slug = slugs[language] || slugs.en;
196
- const unsubscribeUrl = `${baseUrl}${slug}?email=${encodeURIComponent(email)}`;
197
- const isDutch = language === 'nl';
198
- const message = isDutch
199
- ? `Bedankt dat je deel uitmaakt van de **Botanics & You** community.\n\n` +
200
- `Wij geloven dat de natuur alles biedt wat we werkelijk nodig hebben. Terwijl we achter de schermen hard werken aan de lancering, nemen we je graag mee op reis door de wereld van kruiden en natuurlijke vitaliteit.\n\n` +
201
- `**Wat kun je van ons verwachten:**\n` +
202
- `• Exclusieve updates over onze lancering\n` +
203
- `• Inzichten in de kracht van lokale kruiden\n` +
204
- `• Tips voor een diepere verbinding met de natuur\n\n` +
205
- `Bedankt voor je geduld en interesse in een natuurlijke manier van leven. We spreken je snel!`
206
- : `Thank you for joining the **Botanics & You** community.\n\n` +
207
- `We believe that nature provides everything we truly need. While we work hard behind the scenes for our launch, we look forward to taking you on a journey through the world of herbs and natural vitality.\n\n` +
208
- `**What to expect from us:**\n` +
209
- `• Exclusive updates on our upcoming launch\n` +
210
- `• Insights into the healing properties of local herbs\n` +
211
- `• Tips for restoring your connection with nature\n\n` +
212
- `Thank you for your patience and your interest in a more natural way of living. We'll be in touch soon!`;
213
- const formattedMessage = message
214
- .replace(/\n/g, '<br/>')
215
- .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
216
- .replace(/• (.*?)(<br\/>|$)/g, '<div style="margin-left: 10px; margin-bottom: 5px;">• $1</div>');
217
- const html = `
218
- <!DOCTYPE html>
219
- <html>
220
- <head>
221
- <meta charset="utf-8">
222
- <style>
223
- body { background-color: #faf9f6; margin: 0; padding: 0; font-family: 'Georgia', serif; }
224
- .container { max-width: 600px; margin: 20px auto; background-color: #ffffff; border-radius: 40px; border: 1px solid #1a2e260d; overflow: hidden; }
225
- .header { padding: 40px 0 20px 0; text-align: center; }
226
- .logo { width: 180px; height: auto; }
227
- .content { padding: 0 50px 40px 50px; color: #1a2e26; line-height: 1.8; font-size: 15px; }
228
- .footer { padding: 40px 50px; text-align: center; font-family: sans-serif; font-size: 10px; color: #a1a1aa; letter-spacing: 1px; border-top: 1px solid #faf9f6; }
229
- h1 { font-weight: normal; font-style: italic; font-size: 30px; margin-bottom: 30px; color: #1a2e26; text-align: center; }
230
- .divider { height: 1px; width: 40px; background-color: #1a2e2620; margin: 30px auto; }
231
- </style>
232
- </head>
233
- <body>
234
- <div class="container">
235
- <div class="header">
236
- <img src="cid:botanics-logo" alt="Botanics & You" class="logo">
237
- </div>
238
- <div class="content">
239
- <h1>${isDutch ? 'Welkom!' : 'Welcome!'}</h1>
240
- ${formattedMessage}
241
- <div class="divider"></div>
242
- <p style="text-align: center; font-size: 12px; color: #a1a1aa;">
243
- <a href="${unsubscribeUrl}" style="color: #a1a1aa; text-decoration: none;">
244
- ${isDutch ? 'Afmelden' : 'Unsubscribe'}
245
- </a>
246
- </p>
247
- </div>
248
- <div class="footer">
249
- © ${new Date().getFullYear()} Botanics & You. All rights reserved.
250
- </div>
251
- </div>
252
- </body>
253
- </html>
254
- `;
255
- await transporter.sendMail({
256
- from: config.emailConfig.from,
257
- to: email,
258
- subject: isDutch ? 'Welkom bij Botanics & You!' : 'Welcome to Botanics & You!',
259
- html,
260
- });
261
- }
262
- catch (error) {
263
- console.error('[NewsletterAPI] Failed to send welcome email:', error);
264
- // Don't throw - subscription should still succeed even if email fails
265
- }
266
- }
267
- /**
268
- * GET /api/plugin-newsletter/newsletters - List all newsletters
269
- */
270
- export async function GET_NEWSLETTERS(req, config) {
271
- try {
272
- const userId = await config.getUserId?.(req);
273
- if (!userId) {
274
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
275
- }
276
- const dbConnection = await config.getDb();
277
- const db = dbConnection.db();
278
- const collectionName = config.collectionName || 'newsletters';
279
- const newsletters = db.collection(collectionName);
280
- // Get query parameters
281
- const { searchParams } = new URL(req.url);
282
- const status = searchParams.get('status');
283
- const limit = parseInt(searchParams.get('limit') || '50', 10);
284
- const skip = parseInt(searchParams.get('skip') || '0', 10);
285
- const sortBy = searchParams.get('sortBy') || 'updatedAt';
286
- const sortOrder = searchParams.get('sortOrder') || 'desc';
287
- // Build query
288
- const query = {};
289
- if (status) {
290
- query['publication.status'] = status;
291
- }
292
- // Build sort
293
- const sort = {};
294
- sort[sortBy] = sortOrder === 'asc' ? 1 : -1;
295
- const newsletterList = await newsletters
296
- .find(query)
297
- .sort(sort)
298
- .limit(limit)
299
- .skip(skip)
300
- .toArray();
301
- // Convert to list items
302
- const listItems = newsletterList.map((newsletter) => ({
303
- id: newsletter._id?.toString() || newsletter.id,
304
- title: newsletter.title,
305
- slug: newsletter.slug,
306
- status: newsletter.publication?.status || 'draft',
307
- subject: newsletter.metadata?.subject || '',
308
- scheduledDate: newsletter.publication?.scheduledDate,
309
- sentDate: newsletter.publication?.sentDate,
310
- authorId: newsletter.publication?.authorId,
311
- updatedAt: newsletter.updatedAt || newsletter.createdAt,
312
- recipientCount: newsletter.recipientCount,
313
- }));
314
- return NextResponse.json(listItems);
315
- }
316
- catch (error) {
317
- console.error('[NewsletterAPI] GET_NEWSLETTERS error:', error);
318
- return NextResponse.json({ error: 'Failed to fetch newsletters', detail: error.message }, { status: 500 });
319
- }
320
- }
321
- /**
322
- * GET /api/plugin-newsletter/newsletters/[slug] - Get specific newsletter
323
- */
324
- export async function GET_NEWSLETTER(req, slug, config) {
325
- try {
326
- const userId = await config.getUserId?.(req);
327
- if (!userId) {
328
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
329
- }
330
- const dbConnection = await config.getDb();
331
- const db = dbConnection.db();
332
- const collectionName = config.collectionName || 'newsletters';
333
- const newsletters = db.collection(collectionName);
334
- const newsletter = await newsletters.findOne({ slug });
335
- if (!newsletter) {
336
- return NextResponse.json({ error: 'Newsletter not found' }, { status: 404 });
337
- }
338
- // Convert MongoDB document to Newsletter format
339
- const result = {
340
- id: newsletter._id?.toString() || newsletter.id,
341
- title: newsletter.title,
342
- slug: newsletter.slug,
343
- blocks: newsletter.blocks || [],
344
- metadata: newsletter.metadata || {
345
- subject: '',
346
- previewText: '',
347
- lang: 'en',
348
- recipientFilter: { type: 'all' },
349
- },
350
- publication: newsletter.publication || {
351
- status: 'draft',
352
- updatedAt: new Date().toISOString(),
353
- },
354
- createdAt: newsletter.createdAt || new Date().toISOString(),
355
- updatedAt: newsletter.updatedAt || new Date().toISOString(),
356
- version: newsletter.version,
357
- };
358
- return NextResponse.json(result);
359
- }
360
- catch (error) {
361
- console.error('[NewsletterAPI] GET_NEWSLETTER error:', error);
362
- return NextResponse.json({ error: 'Failed to fetch newsletter', detail: error.message }, { status: 500 });
363
- }
364
- }
365
- /**
366
- * POST /api/plugin-newsletter/newsletters/new - Create new newsletter
367
- */
368
- export async function POST_NEWSLETTER(req, config) {
369
- try {
370
- const userId = await config.getUserId?.(req);
371
- if (!userId) {
372
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
373
- }
374
- const body = await req.json();
375
- const { title, blocks, metadata, publication } = body;
376
- // Validation
377
- const errors = [];
378
- if (!title || typeof title !== 'string' || title.trim().length === 0) {
379
- errors.push('Title is required');
380
- }
381
- if (!metadata?.subject || typeof metadata.subject !== 'string' || metadata.subject.trim().length === 0) {
382
- errors.push('Subject is required');
383
- }
384
- if (errors.length > 0) {
385
- return NextResponse.json({ message: errors[0], allErrors: errors }, { status: 400 });
386
- }
387
- const dbConnection = await config.getDb();
388
- const db = dbConnection.db();
389
- const collectionName = config.collectionName || 'newsletters';
390
- const newsletters = db.collection(collectionName);
391
- // Get existing slugs for collision check
392
- const existingNewsletters = await newsletters.find({}, { projection: { slug: 1 } }).toArray();
393
- const existingSlugs = existingNewsletters.map((n) => n.slug).filter(Boolean);
394
- // Use subject as title if title is empty
395
- const finalTitle = (title?.trim() || metadata?.subject?.trim() || '').trim();
396
- // Generate slug
397
- const slug = generateSlugFromTitle(finalTitle, existingSlugs);
398
- const newsletterDocument = {
399
- title: finalTitle,
400
- slug,
401
- blocks: blocks || [],
402
- metadata: {
403
- subject: metadata.subject.trim(),
404
- previewText: metadata.previewText?.trim() || '',
405
- lang: metadata.lang || 'en',
406
- recipientFilter: metadata.recipientFilter || { type: 'all' },
407
- },
408
- publication: {
409
- status: publication?.status || 'draft',
410
- scheduledDate: publication?.scheduledDate,
411
- authorId: userId,
412
- updatedAt: new Date().toISOString(),
413
- },
414
- createdAt: new Date(),
415
- updatedAt: new Date(),
416
- version: 1,
417
- };
418
- const result = await newsletters.insertOne(newsletterDocument);
419
- return NextResponse.json({
420
- message: 'Newsletter created successfully',
421
- id: result.insertedId.toString(),
422
- slug,
423
- }, { status: 201 });
424
- }
425
- catch (error) {
426
- console.error('[NewsletterAPI] POST_NEWSLETTER error:', error);
427
- return NextResponse.json({ error: 'Failed to create newsletter', detail: error.message }, { status: 500 });
428
- }
429
- }
430
- /**
431
- * PUT /api/plugin-newsletter/newsletters/[slug] - Update existing newsletter
432
- */
433
- export async function PUT_NEWSLETTER(req, slug, config) {
434
- try {
435
- const userId = await config.getUserId?.(req);
436
- if (!userId) {
437
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
438
- }
439
- const body = await req.json();
440
- const { title, blocks, metadata, publication } = body;
441
- // Validation
442
- // For newsletters, subject is required and can serve as title
443
- const errors = [];
444
- if (!metadata?.subject || typeof metadata.subject !== 'string' || metadata.subject.trim().length === 0) {
445
- errors.push('Subject is required');
446
- }
447
- // Use subject as title if title is empty (for newsletters, subject is the primary identifier)
448
- const finalTitle = (title?.trim() || metadata?.subject?.trim() || '').trim();
449
- if (!finalTitle) {
450
- errors.push('Title is required (use subject if no title is provided)');
451
- }
452
- if (errors.length > 0) {
453
- return NextResponse.json({ message: errors[0], allErrors: errors }, { status: 400 });
454
- }
455
- const dbConnection = await config.getDb();
456
- const db = dbConnection.db();
457
- const collectionName = config.collectionName || 'newsletters';
458
- const newsletters = db.collection(collectionName);
459
- // Check if newsletter exists
460
- const existing = await newsletters.findOne({ slug });
461
- if (!existing) {
462
- return NextResponse.json({ error: 'Newsletter not found' }, { status: 404 });
463
- }
464
- // Generate new slug if title changed
465
- let newSlug = slug;
466
- if (finalTitle !== existing.title) {
467
- const existingNewsletters = await newsletters.find({ _id: { $ne: existing._id } }, { projection: { slug: 1 } }).toArray();
468
- const existingSlugs = existingNewsletters.map((n) => n.slug).filter(Boolean);
469
- newSlug = generateSlugFromTitle(finalTitle, existingSlugs);
470
- }
471
- // Update newsletter
472
- const updateData = {
473
- title: finalTitle,
474
- slug: newSlug,
475
- blocks: blocks || [],
476
- metadata: {
477
- subject: metadata.subject.trim(),
478
- previewText: metadata.previewText?.trim() || '',
479
- lang: metadata.lang || 'en',
480
- recipientFilter: metadata.recipientFilter || { type: 'all' },
481
- },
482
- publication: {
483
- ...existing.publication,
484
- status: publication?.status || existing.publication?.status || 'draft',
485
- scheduledDate: publication?.scheduledDate,
486
- authorId: userId,
487
- updatedAt: new Date().toISOString(),
488
- },
489
- updatedAt: new Date(),
490
- version: (existing.version || 1) + 1,
491
- };
492
- await newsletters.updateOne({ slug }, { $set: updateData });
493
- return NextResponse.json({
494
- message: 'Newsletter updated successfully',
495
- slug: newSlug,
496
- });
497
- }
498
- catch (error) {
499
- console.error('[NewsletterAPI] PUT_NEWSLETTER error:', error);
500
- return NextResponse.json({ error: 'Failed to update newsletter', detail: error.message }, { status: 500 });
501
- }
502
- }
503
- /**
504
- * DELETE /api/plugin-newsletter/newsletters/[slug] - Delete newsletter
505
- */
506
- export async function DELETE_NEWSLETTER(req, slug, config) {
507
- try {
508
- const userId = await config.getUserId?.(req);
509
- if (!userId) {
510
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
511
- }
512
- const dbConnection = await config.getDb();
513
- const db = dbConnection.db();
514
- const collectionName = config.collectionName || 'newsletters';
515
- const newsletters = db.collection(collectionName);
516
- const result = await newsletters.deleteOne({ slug });
517
- if (result.deletedCount === 0) {
518
- return NextResponse.json({ error: 'Newsletter not found' }, { status: 404 });
519
- }
520
- return NextResponse.json({ message: 'Newsletter deleted successfully' });
521
- }
522
- catch (error) {
523
- console.error('[NewsletterAPI] DELETE_NEWSLETTER error:', error);
524
- return NextResponse.json({ error: 'Failed to delete newsletter', detail: error.message }, { status: 500 });
525
- }
526
- }
5
+ export { GET_SUBSCRIBERS, POST_SUBSCRIBE, GET_SUBSCRIBER, DELETE_SUBSCRIBER, GET_SETTINGS, POST_SETTINGS, GET_NEWSLETTERS, GET_NEWSLETTER, POST_NEWSLETTER, PUT_NEWSLETTER, DELETE_NEWSLETTER, GET_WELCOME_EMAIL_STATUS, GET_WELCOME_EMAIL, POST_WELCOME_EMAIL, GET_SMTP_SETTINGS, POST_SMTP_SETTINGS, POST_TEST_EMAIL, POST_LOGO_UPLOAD, POST_SEND_NEWSLETTER, GET_NEWSLETTER_FOR_SEND, sendWelcomeEmail, } from './handlers';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * API Handlers Index
3
+ * Re-exports all handler functions
4
+ */
5
+ export { GET_SUBSCRIBERS, POST_SUBSCRIBE, GET_SUBSCRIBER, DELETE_SUBSCRIBER, } from './subscribers';
6
+ export { GET_SETTINGS, POST_SETTINGS, GET_SMTP_SETTINGS, POST_SMTP_SETTINGS, POST_TEST_EMAIL, } from './settings';
7
+ export { POST_LOGO_UPLOAD, } from './upload';
8
+ export { GET_NEWSLETTERS, GET_NEWSLETTER, POST_NEWSLETTER, PUT_NEWSLETTER, DELETE_NEWSLETTER, } from './newsletters';
9
+ export { POST_SEND_NEWSLETTER, GET_NEWSLETTER_FOR_SEND, } from './send-newsletter';
10
+ export { GET_WELCOME_EMAIL_STATUS, GET_WELCOME_EMAIL, POST_WELCOME_EMAIL, } from './welcome-email';
11
+ export { sendWelcomeEmail } from '../email-utils';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACH,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,GACpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACH,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACH,gBAAgB,GACnB,MAAM,UAAU,CAAC;AAElB,OAAO,EACH,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,iBAAiB,GACpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACH,oBAAoB,EACpB,uBAAuB,GAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACH,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * API Handlers Index
3
+ * Re-exports all handler functions
4
+ */
5
+ export { GET_SUBSCRIBERS, POST_SUBSCRIBE, GET_SUBSCRIBER, DELETE_SUBSCRIBER, } from './subscribers';
6
+ export { GET_SETTINGS, POST_SETTINGS, GET_SMTP_SETTINGS, POST_SMTP_SETTINGS, POST_TEST_EMAIL, } from './settings';
7
+ export { POST_LOGO_UPLOAD, } from './upload';
8
+ export { GET_NEWSLETTERS, GET_NEWSLETTER, POST_NEWSLETTER, PUT_NEWSLETTER, DELETE_NEWSLETTER, } from './newsletters';
9
+ export { POST_SEND_NEWSLETTER, GET_NEWSLETTER_FOR_SEND, } from './send-newsletter';
10
+ export { GET_WELCOME_EMAIL_STATUS, GET_WELCOME_EMAIL, POST_WELCOME_EMAIL, } from './welcome-email';
11
+ export { sendWelcomeEmail } from '../email-utils';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Newsletter API Handler
3
+ */
4
+ import { NextRequest, NextResponse } from 'next/server';
5
+ import { NewsletterApiConfig } from '../../types/newsletter';
6
+ export declare function GET_NEWSLETTERS(req: NextRequest, config: NewsletterApiConfig): Promise<NextResponse>;
7
+ export declare function GET_NEWSLETTER(req: NextRequest, idOrSlug: string, config: NewsletterApiConfig): Promise<NextResponse>;
8
+ export declare function POST_NEWSLETTER(req: NextRequest, config: NewsletterApiConfig): Promise<NextResponse>;
9
+ export declare function PUT_NEWSLETTER(req: NextRequest, idOrSlug: string, config: NewsletterApiConfig): Promise<NextResponse>;
10
+ export declare function DELETE_NEWSLETTER(req: NextRequest, idOrSlug: string, config: NewsletterApiConfig): Promise<NextResponse>;
11
+ //# sourceMappingURL=newsletters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"newsletters.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/newsletters.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAkC,MAAM,wBAAwB,CAAC;AAc7F,wBAAsB,eAAe,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAgEvB;AAED,wBAAsB,cAAc,CAChC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAkDvB;AAED,wBAAsB,eAAe,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAqEvB;AAED,wBAAsB,cAAc,CAChC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAuEvB;AAED,wBAAsB,iBAAiB,CACnC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA8BvB"}