@jhits/plugin-newsletter 0.0.4 → 0.0.6
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/dist/api/handler.d.ts +51 -0
- package/dist/api/handler.d.ts.map +1 -0
- package/dist/api/handler.js +526 -0
- package/dist/api/router.d.ts +11 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +82 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +222 -0
- package/dist/index.server.d.ts +10 -0
- package/dist/index.server.d.ts.map +1 -0
- package/dist/index.server.js +8 -0
- package/dist/init.d.ts +49 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +42 -0
- package/dist/lib/blocks/BlockRenderer.d.ts +43 -0
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -0
- package/dist/lib/blocks/BlockRenderer.js +48 -0
- package/dist/lib/email/EmailRenderer.d.ts +47 -0
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -0
- package/dist/lib/email/EmailRenderer.js +359 -0
- package/dist/lib/email/index.d.ts +6 -0
- package/dist/lib/email/index.d.ts.map +1 -0
- package/dist/lib/email/index.js +4 -0
- package/dist/lib/mappers/apiMapper.d.ts +30 -0
- package/dist/lib/mappers/apiMapper.d.ts.map +1 -0
- package/dist/lib/mappers/apiMapper.js +36 -0
- package/dist/lib/utils/blockHelpers.d.ts +23 -0
- package/dist/lib/utils/blockHelpers.d.ts.map +1 -0
- package/dist/lib/utils/blockHelpers.js +65 -0
- package/dist/lib/utils/slugify.d.ts +14 -0
- package/dist/lib/utils/slugify.d.ts.map +1 -0
- package/dist/lib/utils/slugify.js +37 -0
- package/dist/registry/BlockRegistry.d.ts +31 -0
- package/dist/registry/BlockRegistry.d.ts.map +1 -0
- package/dist/registry/BlockRegistry.js +34 -0
- package/dist/registry/index.d.ts +5 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/state/EditorContext.d.ts +44 -0
- package/dist/state/EditorContext.d.ts.map +1 -0
- package/dist/state/EditorContext.js +212 -0
- package/dist/state/index.d.ts +10 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +6 -0
- package/dist/state/reducer.d.ts +11 -0
- package/dist/state/reducer.d.ts.map +1 -0
- package/dist/state/reducer.js +488 -0
- package/dist/state/types.d.ts +157 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +26 -0
- package/dist/types/block.d.ts +230 -0
- package/dist/types/block.d.ts.map +1 -0
- package/dist/types/block.js +8 -0
- package/dist/types/newsletter.d.ts +129 -0
- package/dist/types/newsletter.d.ts.map +1 -0
- package/dist/types/newsletter.js +4 -0
- package/dist/types/registry.d.ts +13 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/registry.js +4 -0
- package/dist/views/CanvasEditor/BlockWrapper.d.ts +23 -0
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
- package/dist/views/CanvasEditor/BlockWrapper.js +44 -0
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
- package/dist/views/CanvasEditor/CanvasEditorView.js +139 -0
- package/dist/views/CanvasEditor/EditorBody.d.ts +24 -0
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -0
- package/dist/views/CanvasEditor/EditorBody.js +21 -0
- package/dist/views/CanvasEditor/EditorHeader.d.ts +12 -0
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
- package/dist/views/CanvasEditor/EditorHeader.js +47 -0
- package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts +10 -0
- package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/CustomBlockItem.js +36 -0
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +25 -0
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/EditorCanvas.js +397 -0
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/EditorLibrary.js +25 -0
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +9 -0
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/EditorSidebar.js +16 -0
- package/dist/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
- package/dist/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/ErrorBanner.js +8 -0
- package/dist/views/CanvasEditor/components/LibraryItem.d.ts +10 -0
- package/dist/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/LibraryItem.js +35 -0
- package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts +18 -0
- package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/SlashCommandDetector.js +164 -0
- package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts +22 -0
- package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/SlashCommandMenu.js +57 -0
- package/dist/views/CanvasEditor/components/index.d.ts +16 -0
- package/dist/views/CanvasEditor/components/index.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/index.js +9 -0
- package/dist/views/CanvasEditor/hooks/index.d.ts +7 -0
- package/dist/views/CanvasEditor/hooks/index.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/index.js +6 -0
- package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
- package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.js +114 -0
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts +5 -0
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +28 -0
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +46 -0
- package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts +31 -0
- package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useSlashCommand.js +87 -0
- package/dist/views/CanvasEditor/index.d.ts +12 -0
- package/dist/views/CanvasEditor/index.d.ts.map +1 -0
- package/dist/views/CanvasEditor/index.js +7 -0
- package/dist/views/NewsletterEditor.d.ts +16 -0
- package/dist/views/NewsletterEditor.d.ts.map +1 -0
- package/dist/views/NewsletterEditor.js +10 -0
- package/dist/views/NewsletterManager.d.ts +10 -0
- package/dist/views/NewsletterManager.d.ts.map +1 -0
- package/dist/views/NewsletterManager.js +95 -0
- package/dist/views/SettingsView.d.ts +10 -0
- package/dist/views/SettingsView.d.ts.map +1 -0
- package/dist/views/SettingsView.js +103 -0
- package/dist/views/SubscribersView.d.ts +10 -0
- package/dist/views/SubscribersView.d.ts.map +1 -0
- package/dist/views/SubscribersView.js +94 -0
- package/package.json +24 -23
- package/src/api/handler.ts +340 -1
- package/src/api/router.ts +35 -0
- package/src/index.tsx +284 -4
- package/src/index.tsx.patch +98 -0
- package/src/init.tsx +72 -0
- package/src/lib/blocks/BlockRenderer.tsx +125 -0
- package/src/lib/email/EmailRenderer.tsx +425 -0
- package/src/lib/email/index.ts +6 -0
- package/src/lib/mappers/apiMapper.ts +57 -0
- package/src/lib/utils/blockHelpers.ts +71 -0
- package/src/lib/utils/slugify.ts +43 -0
- package/src/registry/BlockRegistry.ts +53 -0
- package/src/registry/index.ts +5 -0
- package/src/state/EditorContext.tsx +279 -0
- package/src/state/index.ts +10 -0
- package/src/state/reducer.ts +561 -0
- package/src/state/types.ts +154 -0
- package/src/types/block.ts +275 -0
- package/src/types/newsletter.ts +114 -1
- package/src/types/registry.ts +14 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +143 -0
- package/src/views/CanvasEditor/CanvasEditorView.tsx +249 -0
- package/src/views/CanvasEditor/EditorBody.tsx +95 -0
- package/src/views/CanvasEditor/EditorHeader.tsx +139 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +83 -0
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +674 -0
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +120 -0
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +156 -0
- package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
- package/src/views/CanvasEditor/components/LibraryItem.tsx +71 -0
- package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +196 -0
- package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +131 -0
- package/src/views/CanvasEditor/components/index.ts +16 -0
- package/src/views/CanvasEditor/hooks/index.ts +7 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +136 -0
- package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +34 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +54 -0
- package/src/views/CanvasEditor/hooks/useSlashCommand.ts +106 -0
- package/src/views/CanvasEditor/index.ts +12 -0
- package/src/views/NewsletterEditor.tsx +38 -0
- package/src/views/NewsletterManager.tsx +240 -0
- package/src/views/SettingsView.tsx +14 -14
- package/src/views/SubscribersView.tsx +20 -20
package/package.json
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhits/plugin-newsletter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Newsletter management and email delivery plugin for the JHITS ecosystem",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"main": "./
|
|
9
|
-
"types": "./
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types": "./
|
|
13
|
-
"default": "./
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
15
|
"./server": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"default": "./
|
|
16
|
+
"types": "./dist/index.server.d.ts",
|
|
17
|
+
"default": "./dist/index.server.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc"
|
|
22
|
+
},
|
|
20
23
|
"dependencies": {
|
|
21
|
-
"@jhits/plugin-core": "^0.0.
|
|
22
|
-
"lucide-react": "^0.
|
|
23
|
-
"mongodb": "^7.
|
|
24
|
+
"@jhits/plugin-core": "^0.0.2",
|
|
25
|
+
"lucide-react": "^0.564.0",
|
|
26
|
+
"mongodb": "^7.1.0",
|
|
24
27
|
"next-auth": "^4.24.13",
|
|
25
|
-
"nodemailer": "^
|
|
26
|
-
"server-only": "^0.0.1"
|
|
28
|
+
"nodemailer": "^8.0.1"
|
|
27
29
|
},
|
|
28
30
|
"peerDependencies": {
|
|
29
31
|
"next": ">=15.0.0",
|
|
@@ -31,19 +33,18 @@
|
|
|
31
33
|
"react-dom": ">=18.0.0"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^
|
|
35
|
-
"@types/nodemailer": "^7.0.
|
|
36
|
-
"@types/react": "^19",
|
|
37
|
-
"@types/react-dom": "^19",
|
|
38
|
-
"next": "16.1.
|
|
39
|
-
"react": "19.2.
|
|
40
|
-
"react-dom": "19.2.
|
|
41
|
-
"typescript": "^5"
|
|
36
|
+
"@types/node": "^25.2.3",
|
|
37
|
+
"@types/nodemailer": "^7.0.9",
|
|
38
|
+
"@types/react": "^19.2.14",
|
|
39
|
+
"@types/react-dom": "^19.2.3",
|
|
40
|
+
"next": "16.1.6",
|
|
41
|
+
"react": "19.2.4",
|
|
42
|
+
"react-dom": "19.2.4",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
42
44
|
},
|
|
43
45
|
"files": [
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"!src/**/README.md",
|
|
46
|
+
"dist",
|
|
47
|
+
"src",
|
|
47
48
|
"package.json"
|
|
48
49
|
]
|
|
49
50
|
}
|
package/src/api/handler.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
'use server';
|
|
7
7
|
|
|
8
8
|
import { NextRequest, NextResponse } from 'next/server';
|
|
9
|
-
import { NewsletterApiConfig } from '../types/newsletter';
|
|
9
|
+
import { NewsletterApiConfig, Newsletter, NewsletterListItem } from '../types/newsletter';
|
|
10
|
+
import { generateSlugFromTitle } from '../lib/utils/slugify';
|
|
10
11
|
import nodemailer from 'nodemailer';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -352,3 +353,341 @@ async function sendWelcomeEmail(
|
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
356
|
+
/**
|
|
357
|
+
* GET /api/plugin-newsletter/newsletters - List all newsletters
|
|
358
|
+
*/
|
|
359
|
+
export async function GET_NEWSLETTERS(
|
|
360
|
+
req: NextRequest,
|
|
361
|
+
config: NewsletterApiConfig
|
|
362
|
+
): Promise<NextResponse> {
|
|
363
|
+
try {
|
|
364
|
+
const userId = await config.getUserId?.(req);
|
|
365
|
+
if (!userId) {
|
|
366
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const dbConnection = await config.getDb();
|
|
370
|
+
const db = dbConnection.db();
|
|
371
|
+
const collectionName = config.collectionName || 'newsletters';
|
|
372
|
+
const newsletters = db.collection(collectionName);
|
|
373
|
+
|
|
374
|
+
// Get query parameters
|
|
375
|
+
const { searchParams } = new URL(req.url);
|
|
376
|
+
const status = searchParams.get('status');
|
|
377
|
+
const limit = parseInt(searchParams.get('limit') || '50', 10);
|
|
378
|
+
const skip = parseInt(searchParams.get('skip') || '0', 10);
|
|
379
|
+
const sortBy = searchParams.get('sortBy') || 'updatedAt';
|
|
380
|
+
const sortOrder = searchParams.get('sortOrder') || 'desc';
|
|
381
|
+
|
|
382
|
+
// Build query
|
|
383
|
+
const query: any = {};
|
|
384
|
+
if (status) {
|
|
385
|
+
query['publication.status'] = status;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Build sort
|
|
389
|
+
const sort: any = {};
|
|
390
|
+
sort[sortBy] = sortOrder === 'asc' ? 1 : -1;
|
|
391
|
+
|
|
392
|
+
const newsletterList = await newsletters
|
|
393
|
+
.find(query)
|
|
394
|
+
.sort(sort)
|
|
395
|
+
.limit(limit)
|
|
396
|
+
.skip(skip)
|
|
397
|
+
.toArray();
|
|
398
|
+
|
|
399
|
+
// Convert to list items
|
|
400
|
+
const listItems: NewsletterListItem[] = newsletterList.map((newsletter: any) => ({
|
|
401
|
+
id: newsletter._id?.toString() || newsletter.id,
|
|
402
|
+
title: newsletter.title,
|
|
403
|
+
slug: newsletter.slug,
|
|
404
|
+
status: newsletter.publication?.status || 'draft',
|
|
405
|
+
subject: newsletter.metadata?.subject || '',
|
|
406
|
+
scheduledDate: newsletter.publication?.scheduledDate,
|
|
407
|
+
sentDate: newsletter.publication?.sentDate,
|
|
408
|
+
authorId: newsletter.publication?.authorId,
|
|
409
|
+
updatedAt: newsletter.updatedAt || newsletter.createdAt,
|
|
410
|
+
recipientCount: newsletter.recipientCount,
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
return NextResponse.json(listItems);
|
|
414
|
+
} catch (error: any) {
|
|
415
|
+
console.error('[NewsletterAPI] GET_NEWSLETTERS error:', error);
|
|
416
|
+
return NextResponse.json(
|
|
417
|
+
{ error: 'Failed to fetch newsletters', detail: error.message },
|
|
418
|
+
{ status: 500 }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* GET /api/plugin-newsletter/newsletters/[slug] - Get specific newsletter
|
|
425
|
+
*/
|
|
426
|
+
export async function GET_NEWSLETTER(
|
|
427
|
+
req: NextRequest,
|
|
428
|
+
slug: string,
|
|
429
|
+
config: NewsletterApiConfig
|
|
430
|
+
): Promise<NextResponse> {
|
|
431
|
+
try {
|
|
432
|
+
const userId = await config.getUserId?.(req);
|
|
433
|
+
if (!userId) {
|
|
434
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const dbConnection = await config.getDb();
|
|
438
|
+
const db = dbConnection.db();
|
|
439
|
+
const collectionName = config.collectionName || 'newsletters';
|
|
440
|
+
const newsletters = db.collection(collectionName);
|
|
441
|
+
|
|
442
|
+
const newsletter = await newsletters.findOne({ slug });
|
|
443
|
+
if (!newsletter) {
|
|
444
|
+
return NextResponse.json(
|
|
445
|
+
{ error: 'Newsletter not found' },
|
|
446
|
+
{ status: 404 }
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Convert MongoDB document to Newsletter format
|
|
451
|
+
const result: Newsletter = {
|
|
452
|
+
id: newsletter._id?.toString() || newsletter.id,
|
|
453
|
+
title: newsletter.title,
|
|
454
|
+
slug: newsletter.slug,
|
|
455
|
+
blocks: newsletter.blocks || [],
|
|
456
|
+
metadata: newsletter.metadata || {
|
|
457
|
+
subject: '',
|
|
458
|
+
previewText: '',
|
|
459
|
+
lang: 'en',
|
|
460
|
+
recipientFilter: { type: 'all' },
|
|
461
|
+
},
|
|
462
|
+
publication: newsletter.publication || {
|
|
463
|
+
status: 'draft',
|
|
464
|
+
updatedAt: new Date().toISOString(),
|
|
465
|
+
},
|
|
466
|
+
createdAt: newsletter.createdAt || new Date().toISOString(),
|
|
467
|
+
updatedAt: newsletter.updatedAt || new Date().toISOString(),
|
|
468
|
+
version: newsletter.version,
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
return NextResponse.json(result);
|
|
472
|
+
} catch (error: any) {
|
|
473
|
+
console.error('[NewsletterAPI] GET_NEWSLETTER error:', error);
|
|
474
|
+
return NextResponse.json(
|
|
475
|
+
{ error: 'Failed to fetch newsletter', detail: error.message },
|
|
476
|
+
{ status: 500 }
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* POST /api/plugin-newsletter/newsletters/new - Create new newsletter
|
|
483
|
+
*/
|
|
484
|
+
export async function POST_NEWSLETTER(
|
|
485
|
+
req: NextRequest,
|
|
486
|
+
config: NewsletterApiConfig
|
|
487
|
+
): Promise<NextResponse> {
|
|
488
|
+
try {
|
|
489
|
+
const userId = await config.getUserId?.(req);
|
|
490
|
+
if (!userId) {
|
|
491
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const body = await req.json();
|
|
495
|
+
const { title, blocks, metadata, publication } = body;
|
|
496
|
+
|
|
497
|
+
// Validation
|
|
498
|
+
const errors: string[] = [];
|
|
499
|
+
if (!title || typeof title !== 'string' || title.trim().length === 0) {
|
|
500
|
+
errors.push('Title is required');
|
|
501
|
+
}
|
|
502
|
+
if (!metadata?.subject || typeof metadata.subject !== 'string' || metadata.subject.trim().length === 0) {
|
|
503
|
+
errors.push('Subject is required');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (errors.length > 0) {
|
|
507
|
+
return NextResponse.json({ message: errors[0], allErrors: errors }, { status: 400 });
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const dbConnection = await config.getDb();
|
|
511
|
+
const db = dbConnection.db();
|
|
512
|
+
const collectionName = config.collectionName || 'newsletters';
|
|
513
|
+
const newsletters = db.collection(collectionName);
|
|
514
|
+
|
|
515
|
+
// Get existing slugs for collision check
|
|
516
|
+
const existingNewsletters = await newsletters.find({}, { projection: { slug: 1 } }).toArray();
|
|
517
|
+
const existingSlugs = existingNewsletters.map((n: any) => n.slug).filter(Boolean);
|
|
518
|
+
|
|
519
|
+
// Use subject as title if title is empty
|
|
520
|
+
const finalTitle = (title?.trim() || metadata?.subject?.trim() || '').trim();
|
|
521
|
+
|
|
522
|
+
// Generate slug
|
|
523
|
+
const slug = generateSlugFromTitle(finalTitle, existingSlugs);
|
|
524
|
+
|
|
525
|
+
const newsletterDocument = {
|
|
526
|
+
title: finalTitle,
|
|
527
|
+
slug,
|
|
528
|
+
blocks: blocks || [],
|
|
529
|
+
metadata: {
|
|
530
|
+
subject: metadata.subject.trim(),
|
|
531
|
+
previewText: metadata.previewText?.trim() || '',
|
|
532
|
+
lang: metadata.lang || 'en',
|
|
533
|
+
recipientFilter: metadata.recipientFilter || { type: 'all' },
|
|
534
|
+
},
|
|
535
|
+
publication: {
|
|
536
|
+
status: publication?.status || 'draft',
|
|
537
|
+
scheduledDate: publication?.scheduledDate,
|
|
538
|
+
authorId: userId,
|
|
539
|
+
updatedAt: new Date().toISOString(),
|
|
540
|
+
},
|
|
541
|
+
createdAt: new Date(),
|
|
542
|
+
updatedAt: new Date(),
|
|
543
|
+
version: 1,
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const result = await newsletters.insertOne(newsletterDocument);
|
|
547
|
+
|
|
548
|
+
return NextResponse.json({
|
|
549
|
+
message: 'Newsletter created successfully',
|
|
550
|
+
id: result.insertedId.toString(),
|
|
551
|
+
slug,
|
|
552
|
+
}, { status: 201 });
|
|
553
|
+
} catch (error: any) {
|
|
554
|
+
console.error('[NewsletterAPI] POST_NEWSLETTER error:', error);
|
|
555
|
+
return NextResponse.json(
|
|
556
|
+
{ error: 'Failed to create newsletter', detail: error.message },
|
|
557
|
+
{ status: 500 }
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* PUT /api/plugin-newsletter/newsletters/[slug] - Update existing newsletter
|
|
564
|
+
*/
|
|
565
|
+
export async function PUT_NEWSLETTER(
|
|
566
|
+
req: NextRequest,
|
|
567
|
+
slug: string,
|
|
568
|
+
config: NewsletterApiConfig
|
|
569
|
+
): Promise<NextResponse> {
|
|
570
|
+
try {
|
|
571
|
+
const userId = await config.getUserId?.(req);
|
|
572
|
+
if (!userId) {
|
|
573
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const body = await req.json();
|
|
577
|
+
const { title, blocks, metadata, publication } = body;
|
|
578
|
+
|
|
579
|
+
// Validation
|
|
580
|
+
// For newsletters, subject is required and can serve as title
|
|
581
|
+
const errors: string[] = [];
|
|
582
|
+
if (!metadata?.subject || typeof metadata.subject !== 'string' || metadata.subject.trim().length === 0) {
|
|
583
|
+
errors.push('Subject is required');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Use subject as title if title is empty (for newsletters, subject is the primary identifier)
|
|
587
|
+
const finalTitle = (title?.trim() || metadata?.subject?.trim() || '').trim();
|
|
588
|
+
if (!finalTitle) {
|
|
589
|
+
errors.push('Title is required (use subject if no title is provided)');
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (errors.length > 0) {
|
|
593
|
+
return NextResponse.json({ message: errors[0], allErrors: errors }, { status: 400 });
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const dbConnection = await config.getDb();
|
|
597
|
+
const db = dbConnection.db();
|
|
598
|
+
const collectionName = config.collectionName || 'newsletters';
|
|
599
|
+
const newsletters = db.collection(collectionName);
|
|
600
|
+
|
|
601
|
+
// Check if newsletter exists
|
|
602
|
+
const existing = await newsletters.findOne({ slug });
|
|
603
|
+
if (!existing) {
|
|
604
|
+
return NextResponse.json(
|
|
605
|
+
{ error: 'Newsletter not found' },
|
|
606
|
+
{ status: 404 }
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Generate new slug if title changed
|
|
611
|
+
let newSlug = slug;
|
|
612
|
+
if (finalTitle !== existing.title) {
|
|
613
|
+
const existingNewsletters = await newsletters.find({ _id: { $ne: existing._id } }, { projection: { slug: 1 } }).toArray();
|
|
614
|
+
const existingSlugs = existingNewsletters.map((n: any) => n.slug).filter(Boolean);
|
|
615
|
+
newSlug = generateSlugFromTitle(finalTitle, existingSlugs);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Update newsletter
|
|
619
|
+
const updateData: any = {
|
|
620
|
+
title: finalTitle,
|
|
621
|
+
slug: newSlug,
|
|
622
|
+
blocks: blocks || [],
|
|
623
|
+
metadata: {
|
|
624
|
+
subject: metadata.subject.trim(),
|
|
625
|
+
previewText: metadata.previewText?.trim() || '',
|
|
626
|
+
lang: metadata.lang || 'en',
|
|
627
|
+
recipientFilter: metadata.recipientFilter || { type: 'all' },
|
|
628
|
+
},
|
|
629
|
+
publication: {
|
|
630
|
+
...existing.publication,
|
|
631
|
+
status: publication?.status || existing.publication?.status || 'draft',
|
|
632
|
+
scheduledDate: publication?.scheduledDate,
|
|
633
|
+
authorId: userId,
|
|
634
|
+
updatedAt: new Date().toISOString(),
|
|
635
|
+
},
|
|
636
|
+
updatedAt: new Date(),
|
|
637
|
+
version: (existing.version || 1) + 1,
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
await newsletters.updateOne(
|
|
641
|
+
{ slug },
|
|
642
|
+
{ $set: updateData }
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
return NextResponse.json({
|
|
646
|
+
message: 'Newsletter updated successfully',
|
|
647
|
+
slug: newSlug,
|
|
648
|
+
});
|
|
649
|
+
} catch (error: any) {
|
|
650
|
+
console.error('[NewsletterAPI] PUT_NEWSLETTER error:', error);
|
|
651
|
+
return NextResponse.json(
|
|
652
|
+
{ error: 'Failed to update newsletter', detail: error.message },
|
|
653
|
+
{ status: 500 }
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* DELETE /api/plugin-newsletter/newsletters/[slug] - Delete newsletter
|
|
660
|
+
*/
|
|
661
|
+
export async function DELETE_NEWSLETTER(
|
|
662
|
+
req: NextRequest,
|
|
663
|
+
slug: string,
|
|
664
|
+
config: NewsletterApiConfig
|
|
665
|
+
): Promise<NextResponse> {
|
|
666
|
+
try {
|
|
667
|
+
const userId = await config.getUserId?.(req);
|
|
668
|
+
if (!userId) {
|
|
669
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const dbConnection = await config.getDb();
|
|
673
|
+
const db = dbConnection.db();
|
|
674
|
+
const collectionName = config.collectionName || 'newsletters';
|
|
675
|
+
const newsletters = db.collection(collectionName);
|
|
676
|
+
|
|
677
|
+
const result = await newsletters.deleteOne({ slug });
|
|
678
|
+
if (result.deletedCount === 0) {
|
|
679
|
+
return NextResponse.json(
|
|
680
|
+
{ error: 'Newsletter not found' },
|
|
681
|
+
{ status: 404 }
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return NextResponse.json({ message: 'Newsletter deleted successfully' });
|
|
686
|
+
} catch (error: any) {
|
|
687
|
+
console.error('[NewsletterAPI] DELETE_NEWSLETTER error:', error);
|
|
688
|
+
return NextResponse.json(
|
|
689
|
+
{ error: 'Failed to delete newsletter', detail: error.message },
|
|
690
|
+
{ status: 500 }
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
}
|
package/src/api/router.ts
CHANGED
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
DELETE_SUBSCRIBER,
|
|
15
15
|
GET_SETTINGS,
|
|
16
16
|
POST_SETTINGS,
|
|
17
|
+
GET_NEWSLETTERS,
|
|
18
|
+
GET_NEWSLETTER,
|
|
19
|
+
POST_NEWSLETTER,
|
|
20
|
+
PUT_NEWSLETTER,
|
|
21
|
+
DELETE_NEWSLETTER,
|
|
17
22
|
} from './handler';
|
|
18
23
|
|
|
19
24
|
/**
|
|
@@ -60,6 +65,36 @@ export async function handleNewsletterApi(
|
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
|
|
68
|
+
// Route: /api/plugin-newsletter/newsletters
|
|
69
|
+
if (route === 'newsletters') {
|
|
70
|
+
// Special route: /api/plugin-newsletter/newsletters/new
|
|
71
|
+
if (path[1] === 'new' && method === 'POST') {
|
|
72
|
+
return await POST_NEWSLETTER(req, config);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (path[1]) {
|
|
76
|
+
// /api/plugin-newsletter/newsletters/[slug]
|
|
77
|
+
const newsletterSlug = decodeURIComponent(path[1]);
|
|
78
|
+
if (method === 'GET') {
|
|
79
|
+
return await GET_NEWSLETTER(req, newsletterSlug, config);
|
|
80
|
+
}
|
|
81
|
+
if (method === 'PUT') {
|
|
82
|
+
return await PUT_NEWSLETTER(req, newsletterSlug, config);
|
|
83
|
+
}
|
|
84
|
+
if (method === 'DELETE') {
|
|
85
|
+
return await DELETE_NEWSLETTER(req, newsletterSlug, config);
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
// /api/plugin-newsletter/newsletters
|
|
89
|
+
if (method === 'GET') {
|
|
90
|
+
return await GET_NEWSLETTERS(req, config);
|
|
91
|
+
}
|
|
92
|
+
if (method === 'POST') {
|
|
93
|
+
return await POST_NEWSLETTER(req, config);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
63
98
|
// Method not allowed
|
|
64
99
|
return NextResponse.json(
|
|
65
100
|
{ error: `Method ${method} not allowed for route: ${route || '/'}` },
|