@jhits/plugin-blog 0.0.18 → 0.0.20
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/categories.d.ts.map +1 -1
- package/dist/api/categories.js +42 -38
- package/dist/api/handler.d.ts +1 -26
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +81 -500
- package/dist/api/router.d.ts +0 -5
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +8 -35
- package/dist/api/service.d.ts +80 -0
- package/dist/api/service.d.ts.map +1 -0
- package/dist/api/service.js +219 -0
- package/dist/hooks/useAutoSave.d.ts +10 -0
- package/dist/hooks/useAutoSave.d.ts.map +1 -0
- package/dist/hooks/useAutoSave.js +57 -0
- package/dist/hooks/useCategories.d.ts +1 -1
- package/dist/hooks/useCategories.d.ts.map +1 -1
- package/dist/hooks/useCategories.js +15 -46
- package/dist/index.d.ts +24 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -201
- package/dist/init.d.ts +20 -7
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +8 -7
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/ColumnsBlock.js +30 -113
- package/dist/lib/layouts/blocks/SectionBlock.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/SectionBlock.js +9 -21
- package/dist/lib/layouts/index.d.ts +3 -3
- package/dist/lib/layouts/index.js +4 -4
- package/dist/lib/mappers/apiMapper.d.ts +10 -0
- package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
- package/dist/lib/mappers/apiMapper.js +47 -32
- package/dist/lib/rich-text/RichTextEditor.d.ts +4 -2
- package/dist/lib/rich-text/RichTextEditor.d.ts.map +1 -1
- package/dist/lib/rich-text/RichTextEditor.js +12 -9
- package/dist/lib/utils/config-resolver.d.ts +28 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +46 -0
- package/dist/lib/utils/tree.d.ts +29 -0
- package/dist/lib/utils/tree.d.ts.map +1 -0
- package/dist/lib/utils/tree.js +129 -0
- package/dist/state/EditorContext.d.ts +3 -25
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +124 -174
- package/dist/state/reducer.d.ts +1 -5
- package/dist/state/reducer.d.ts.map +1 -1
- package/dist/state/reducer.js +128 -521
- package/dist/state/types.d.ts +12 -1
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/post.d.ts +17 -1
- package/dist/types/post.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts +5 -6
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +56 -264
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts +5 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +55 -315
- package/dist/views/CanvasEditor/EditorBody.d.ts +6 -8
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +34 -482
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorHeader.js +27 -63
- package/dist/views/CanvasEditor/LayoutContainer.d.ts.map +1 -1
- package/dist/views/CanvasEditor/LayoutContainer.js +49 -70
- package/dist/views/CanvasEditor/components/CustomBlockItem.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +15 -3
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +40 -18
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +5 -1
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorLibrary.js +11 -7
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +32 -14
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -6
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.js +17 -128
- package/dist/views/CanvasEditor/components/JSONInspector.d.ts +9 -0
- package/dist/views/CanvasEditor/components/JSONInspector.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/JSONInspector.js +56 -0
- package/dist/views/CanvasEditor/components/LibraryItem.js +2 -2
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -4
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.js +6 -28
- package/dist/views/CanvasEditor/components/index.d.ts +2 -0
- package/dist/views/CanvasEditor/components/index.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/index.js +1 -0
- package/dist/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useHeroBlock.js +15 -18
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +3 -0
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/usePostLoader.js +12 -13
- package/dist/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -4
- package/dist/views/PostManager/EmptyState.d.ts +1 -1
- package/dist/views/PostManager/EmptyState.js +4 -4
- package/dist/views/PostManager/FilterDropdown.d.ts +21 -0
- package/dist/views/PostManager/FilterDropdown.d.ts.map +1 -0
- package/dist/views/PostManager/FilterDropdown.js +28 -0
- package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -1
- package/dist/views/PostManager/LanguageFlags.js +4 -1
- package/dist/views/PostManager/PostCards.d.ts.map +1 -1
- package/dist/views/PostManager/PostCards.js +23 -40
- package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
- package/dist/views/PostManager/PostFilters.js +34 -3
- package/dist/views/PostManager/PostManagerView.d.ts +1 -2
- package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
- package/dist/views/PostManager/PostManagerView.js +30 -96
- package/dist/views/PostManager/PostStats.d.ts.map +1 -1
- package/dist/views/PostManager/PostStats.js +10 -10
- package/dist/views/PostManager/PostTable.d.ts.map +1 -1
- package/dist/views/PostManager/PostTable.js +23 -40
- package/dist/views/Settings/SettingsView.d.ts +1 -1
- package/dist/views/Settings/SettingsView.d.ts.map +1 -1
- package/dist/views/Settings/SettingsView.js +12 -39
- package/dist/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -1
- package/dist/views/SlugSEO/SlugSEOManagerView.js +2 -2
- package/package.json +42 -6
- package/src/api/categories.ts +48 -52
- package/src/api/handler.ts +87 -604
- package/src/api/router.ts +15 -65
- package/src/api/service.ts +241 -0
- package/src/hooks/useAutoSave.ts +64 -0
- package/src/hooks/useCategories.ts +19 -47
- package/src/index.tsx +79 -293
- package/src/init.tsx +24 -11
- package/src/lib/blocks/BlockRenderer.tsx +1 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx +60 -173
- package/src/lib/layouts/blocks/SectionBlock.tsx +22 -26
- package/src/lib/layouts/index.ts +4 -4
- package/src/lib/mappers/apiMapper.ts +63 -32
- package/src/lib/rich-text/RichTextEditor.tsx +16 -9
- package/src/lib/utils/config-resolver.ts +64 -0
- package/src/lib/utils/tree.ts +150 -0
- package/src/state/EditorContext.tsx +153 -232
- package/src/state/reducer.ts +141 -606
- package/src/state/types.ts +14 -1
- package/src/types/block.ts +10 -0
- package/src/types/post.ts +19 -1
- package/src/views/CanvasEditor/BlockWrapper.tsx +130 -460
- package/src/views/CanvasEditor/CanvasEditorView.tsx +145 -420
- package/src/views/CanvasEditor/EditorBody.tsx +98 -610
- package/src/views/CanvasEditor/EditorHeader.tsx +176 -196
- package/src/views/CanvasEditor/LayoutContainer.tsx +74 -89
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +7 -8
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +139 -84
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +25 -10
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +196 -127
- package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +78 -210
- package/src/views/CanvasEditor/components/JSONInspector.tsx +125 -0
- package/src/views/CanvasEditor/components/LibraryItem.tsx +5 -6
- package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +73 -124
- package/src/views/CanvasEditor/components/index.ts +2 -1
- package/src/views/CanvasEditor/hooks/useHeroBlock.ts +15 -18
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +21 -13
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +4 -4
- package/src/views/PostManager/EmptyState.tsx +9 -10
- package/src/views/PostManager/FilterDropdown.tsx +95 -0
- package/src/views/PostManager/LanguageFlags.tsx +6 -2
- package/src/views/PostManager/PostCards.tsx +127 -133
- package/src/views/PostManager/PostFilters.tsx +73 -68
- package/src/views/PostManager/PostManagerView.tsx +132 -179
- package/src/views/PostManager/PostStats.tsx +21 -20
- package/src/views/PostManager/PostTable.tsx +137 -165
- package/src/views/Settings/SettingsView.tsx +64 -180
- package/src/views/SlugSEO/SlugSEOManagerView.tsx +59 -44
- package/src/hooks/index.d.ts +0 -8
- package/src/hooks/index.d.ts.map +0 -1
- package/src/hooks/useBlog.d.ts +0 -31
- package/src/hooks/useBlog.d.ts.map +0 -1
- package/src/hooks/useBlogs.d.ts +0 -39
- package/src/hooks/useBlogs.d.ts.map +0 -1
- package/src/hooks/useCategories.d.ts +0 -9
- package/src/hooks/useCategories.d.ts.map +0 -1
- package/src/lib/blocks/BlockRenderer.d.ts +0 -54
- package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
- package/src/lib/config-storage.d.ts +0 -30
- package/src/lib/config-storage.d.ts.map +0 -1
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
- package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
- package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
- package/src/lib/layouts/index.d.ts +0 -23
- package/src/lib/layouts/index.d.ts.map +0 -1
- package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
- package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
- package/src/lib/mappers/apiMapper.d.ts +0 -66
- package/src/lib/mappers/apiMapper.d.ts.map +0 -1
- package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
- package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
- package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
- package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
- package/src/lib/rich-text/index.d.ts +0 -9
- package/src/lib/rich-text/index.d.ts.map +0 -1
- package/src/lib/utils/blockHelpers.d.ts +0 -23
- package/src/lib/utils/blockHelpers.d.ts.map +0 -1
- package/src/lib/utils/configValidation.d.ts +0 -23
- package/src/lib/utils/configValidation.d.ts.map +0 -1
- package/src/registry/BlockRegistry.d.ts +0 -62
- package/src/registry/BlockRegistry.d.ts.map +0 -1
- package/src/registry/index.d.ts +0 -6
- package/src/registry/index.d.ts.map +0 -1
- package/src/state/EditorContext.d.ts +0 -45
- package/src/state/EditorContext.d.ts.map +0 -1
- package/src/state/index.d.ts +0 -7
- package/src/state/index.d.ts.map +0 -1
- package/src/state/reducer.d.ts +0 -11
- package/src/state/reducer.d.ts.map +0 -1
- package/src/state/types.d.ts +0 -162
- package/src/state/types.d.ts.map +0 -1
- package/src/types/block.d.ts +0 -221
- package/src/types/block.d.ts.map +0 -1
- package/src/types/index.d.ts +0 -8
- package/src/types/index.d.ts.map +0 -1
- package/src/types/post.d.ts +0 -136
- package/src/types/post.d.ts.map +0 -1
- package/src/utils/client.d.ts +0 -48
- package/src/utils/client.d.ts.map +0 -1
- package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
- package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
- package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
- package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
- package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
- package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
- package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
- package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
- package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
- package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
- package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/index.d.ts +0 -21
- package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
- package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
- package/src/views/CanvasEditor/index.d.ts +0 -16
- package/src/views/CanvasEditor/index.d.ts.map +0 -1
- package/src/views/PostManager/EmptyState.d.ts +0 -10
- package/src/views/PostManager/EmptyState.d.ts.map +0 -1
- package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
- package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
- package/src/views/PostManager/PostCards.d.ts +0 -15
- package/src/views/PostManager/PostCards.d.ts.map +0 -1
- package/src/views/PostManager/PostFilters.d.ts +0 -16
- package/src/views/PostManager/PostFilters.d.ts.map +0 -1
- package/src/views/PostManager/PostManagerView.d.ts +0 -11
- package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
- package/src/views/PostManager/PostStats.d.ts +0 -11
- package/src/views/PostManager/PostStats.d.ts.map +0 -1
- package/src/views/PostManager/PostTable.d.ts +0 -15
- package/src/views/PostManager/PostTable.d.ts.map +0 -1
- package/src/views/PostManager/index.d.ts +0 -12
- package/src/views/PostManager/index.d.ts.map +0 -1
- package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
- package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
- package/src/views/Preview/index.d.ts +0 -6
- package/src/views/Preview/index.d.ts.map +0 -1
- package/src/views/Settings/SettingsView.d.ts +0 -10
- package/src/views/Settings/SettingsView.d.ts.map +0 -1
- package/src/views/Settings/index.d.ts +0 -6
- package/src/views/Settings/index.d.ts.map +0 -1
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
- package/src/views/SlugSEO/index.d.ts +0 -6
- package/src/views/SlugSEO/index.d.ts.map +0 -1
package/dist/api/handler.js
CHANGED
|
@@ -1,558 +1,139 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Blog API Handler
|
|
3
|
-
* RESTful API handler
|
|
4
|
-
* Compatible with Next.js API routes
|
|
5
|
-
*
|
|
6
|
-
* IMPORTANT: This file should ONLY be imported in server-side API routes.
|
|
7
|
-
* Do NOT import this in client-side code.
|
|
3
|
+
* Simplified RESTful API handler using BlogService
|
|
8
4
|
*/
|
|
9
5
|
import { NextResponse } from 'next/server';
|
|
6
|
+
import { BlogService } from './service';
|
|
10
7
|
import { slugify } from '../lib/utils/slugify';
|
|
11
8
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* GET /api/blogs?status=published - Filter by status
|
|
15
|
-
* GET /api/blogs?language=en - Filter by language (falls back to nl if not found)
|
|
9
|
+
* Generic helper to find a blog post by slug across all languages
|
|
10
|
+
* Avoids hardcoding language codes
|
|
16
11
|
*/
|
|
12
|
+
async function findPostBySlug(collection, slug) {
|
|
13
|
+
// 1. Try root slug (fastest, indexed)
|
|
14
|
+
const rootMatch = await collection.findOne({ slug });
|
|
15
|
+
if (rootMatch)
|
|
16
|
+
return rootMatch;
|
|
17
|
+
// 2. Generic search across all localized slugs using aggregation
|
|
18
|
+
// This works for ANY language key (nl, en, sv, de, fr, es, etc.)
|
|
19
|
+
const results = await collection.aggregate([
|
|
20
|
+
// Convert the 'languages' object into an array of { k, v } entries
|
|
21
|
+
{ $addFields: { _langEntries: { $objectToArray: { $ifNull: ["$languages", {}] } } } },
|
|
22
|
+
// Match if any entry's slug matches our target
|
|
23
|
+
{ $match: { "_langEntries.v.metadata.slug": slug } },
|
|
24
|
+
// Remove the temporary field
|
|
25
|
+
{ $project: { _langEntries: 0 } },
|
|
26
|
+
{ $limit: 1 }
|
|
27
|
+
]).toArray();
|
|
28
|
+
return results[0] || null;
|
|
29
|
+
}
|
|
17
30
|
export async function GET(req, config) {
|
|
18
31
|
try {
|
|
19
32
|
const url = new URL(req.url);
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const blogs = db.collection(config.collectionName || 'blogs');
|
|
29
|
-
// Build query
|
|
30
|
-
let query = {};
|
|
31
|
-
if (isAdminView && userId) {
|
|
32
|
-
// Admin view: show all posts owned by user
|
|
33
|
-
if (statusFilter) {
|
|
34
|
-
query = {
|
|
35
|
-
'publicationData.status': statusFilter,
|
|
36
|
-
authorId: userId,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
query = { authorId: userId };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// Public view: only published posts in the SPECIFIC language
|
|
45
|
-
// This ensures we only fetch blogs that actually have content for this language
|
|
46
|
-
query = {
|
|
47
|
-
[`languages.${requestedLanguage}.status`]: 'published',
|
|
48
|
-
'publicationData.date': { $lte: new Date() },
|
|
49
|
-
};
|
|
50
|
-
if (statusFilter && statusFilter !== 'published') {
|
|
51
|
-
// Non-admin can't filter by non-published status
|
|
52
|
-
return NextResponse.json({ error: 'Invalid status filter' }, { status: 400 });
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const [data, totalCount] = await Promise.all([
|
|
56
|
-
blogs
|
|
57
|
-
.find(query)
|
|
58
|
-
.sort({ 'publicationData.date': -1 })
|
|
59
|
-
.skip(skip)
|
|
60
|
-
.limit(isAdminView ? 0 : limit)
|
|
61
|
-
.toArray(),
|
|
62
|
-
blogs.countDocuments(query),
|
|
63
|
-
]);
|
|
64
|
-
// Supported languages for fallback (in order of preference)
|
|
65
|
-
const fallbackLanguages = [requestedLanguage, 'nl', 'en'];
|
|
66
|
-
const formatted = data.map((doc) => {
|
|
67
|
-
const languages = doc.languages || {};
|
|
68
|
-
// Only use exact language match public views - no fallback for
|
|
69
|
-
// This ensures visitors see content in their language only
|
|
70
|
-
const hasExactLanguage = !!languages[requestedLanguage];
|
|
71
|
-
// Skip this post if no exact language match (for public non-admin views)
|
|
72
|
-
if (!isAdminView && !hasExactLanguage) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
const postPrimaryLang = doc.metadata?.lang || 'nl';
|
|
76
|
-
// Find the best available language for this post
|
|
77
|
-
let bestLanguage = requestedLanguage;
|
|
78
|
-
let isMissingTranslation = false;
|
|
79
|
-
if (!languages[requestedLanguage]) {
|
|
80
|
-
// For admin view, if specific language is requested, show it as missing instead of falling back
|
|
81
|
-
// This ensures the UI is "synced" with the selected language
|
|
82
|
-
if (isAdminView) {
|
|
83
|
-
isMissingTranslation = true;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// Public view still uses fallback or filtering
|
|
87
|
-
const fallbackLanguages = [postPrimaryLang, 'nl', 'en'];
|
|
88
|
-
for (const lang of fallbackLanguages) {
|
|
89
|
-
if (languages[lang]) {
|
|
90
|
-
bestLanguage = lang;
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const langContent = languages[bestLanguage] || {};
|
|
97
|
-
const meta = langContent.metadata || {};
|
|
98
|
-
// Ensure all languages in doc have a status and updatedAt for the dashboard
|
|
99
|
-
const enrichedLanguages = { ...languages };
|
|
100
|
-
Object.keys(enrichedLanguages).forEach(lang => {
|
|
101
|
-
if (!enrichedLanguages[lang].status) {
|
|
102
|
-
enrichedLanguages[lang].status = doc.publicationData?.status === 'concept' ? 'draft' : (doc.publicationData?.status || 'draft');
|
|
103
|
-
}
|
|
104
|
-
if (!enrichedLanguages[lang].updatedAt) {
|
|
105
|
-
enrichedLanguages[lang].updatedAt = doc.updatedAt;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
// Language display names for placeholders
|
|
109
|
-
const langNames = {
|
|
110
|
-
nl: 'Dutch',
|
|
111
|
-
en: 'English',
|
|
112
|
-
sv: 'Swedish',
|
|
113
|
-
de: 'German',
|
|
114
|
-
fr: 'French',
|
|
115
|
-
es: 'Spanish'
|
|
116
|
-
};
|
|
117
|
-
const displayTitle = isMissingTranslation
|
|
118
|
-
? `(No ${langNames[requestedLanguage] || requestedLanguage.toUpperCase()} translation)`
|
|
119
|
-
: (meta.title || doc.title || '');
|
|
120
|
-
const displayStatus = isMissingTranslation
|
|
121
|
-
? 'not-translated'
|
|
122
|
-
: (langContent.status || doc.publicationData?.status || 'concept');
|
|
123
|
-
return {
|
|
124
|
-
...doc,
|
|
125
|
-
_id: doc._id.toString(),
|
|
126
|
-
title: displayTitle,
|
|
127
|
-
summary: isMissingTranslation ? '' : (meta.excerpt || doc.summary || ''),
|
|
128
|
-
contentBlocks: isMissingTranslation ? [] : (langContent.blocks || doc.contentBlocks || doc.blocks || []),
|
|
129
|
-
image: isMissingTranslation ? null : (meta.featuredImage || doc.image),
|
|
130
|
-
categoryTags: isMissingTranslation ? { category: '', tags: [] } : (meta.categories ? {
|
|
131
|
-
category: meta.categories[0] || '',
|
|
132
|
-
tags: meta.tags || doc.categoryTags?.tags || []
|
|
133
|
-
} : (doc.categoryTags || { category: '', tags: [] })),
|
|
134
|
-
publicationData: {
|
|
135
|
-
...doc.publicationData,
|
|
136
|
-
status: displayStatus,
|
|
137
|
-
},
|
|
138
|
-
seo: isMissingTranslation ? {} : (meta.seo || doc.seo || {}),
|
|
139
|
-
lang: bestLanguage,
|
|
140
|
-
isMissingTranslation,
|
|
141
|
-
requestedLanguage,
|
|
142
|
-
availableLanguages: Object.keys(languages),
|
|
143
|
-
languages: enrichedLanguages,
|
|
144
|
-
updatedAt: isMissingTranslation ? doc.updatedAt : (langContent.updatedAt || doc.updatedAt),
|
|
145
|
-
status: displayStatus,
|
|
146
|
-
};
|
|
147
|
-
}).filter(Boolean); // Remove null entries
|
|
148
|
-
// Sort by updatedAt descending so the UI order makes sense
|
|
149
|
-
formatted.sort((a, b) => {
|
|
150
|
-
const dateA = new Date(a.updatedAt).getTime();
|
|
151
|
-
const dateB = new Date(b.updatedAt).getTime();
|
|
152
|
-
return dateB - dateA;
|
|
153
|
-
});
|
|
154
|
-
return NextResponse.json({
|
|
155
|
-
blogs: formatted,
|
|
156
|
-
total: formatted.length,
|
|
33
|
+
const service = new BlogService(config);
|
|
34
|
+
const results = await service.listBlogs({
|
|
35
|
+
limit: Number(url.searchParams.get('limit') ?? 10),
|
|
36
|
+
skip: Number(url.searchParams.get('skip') ?? 0),
|
|
37
|
+
status: url.searchParams.get('status') || undefined,
|
|
38
|
+
isAdmin: url.searchParams.get('admin') === 'true',
|
|
39
|
+
userId: await config.getUserId(req),
|
|
40
|
+
requestedLanguage: url.searchParams.get('language') || 'nl',
|
|
157
41
|
});
|
|
42
|
+
return NextResponse.json(results);
|
|
158
43
|
}
|
|
159
44
|
catch (err) {
|
|
160
|
-
|
|
161
|
-
return NextResponse.json({ error: 'Failed to fetch blogs', detail: err.message }, { status: 500 });
|
|
45
|
+
return NextResponse.json({ error: 'Fetch failed', detail: err.message }, { status: 500 });
|
|
162
46
|
}
|
|
163
47
|
}
|
|
164
|
-
/**
|
|
165
|
-
* POST /api/blogs - Create new blog post
|
|
166
|
-
*/
|
|
167
48
|
export async function POST(req, config) {
|
|
168
49
|
try {
|
|
169
50
|
const url = new URL(req.url);
|
|
170
51
|
const language = url.searchParams.get('language') || 'nl';
|
|
171
52
|
const userId = await config.getUserId(req);
|
|
172
|
-
if (!userId)
|
|
53
|
+
if (!userId)
|
|
173
54
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
174
|
-
}
|
|
175
55
|
const body = await req.json();
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (isPublishing) {
|
|
185
|
-
// Publishing requires all fields
|
|
186
|
-
if (!summary?.trim())
|
|
187
|
-
errors.push('Summary is required for publishing');
|
|
188
|
-
if (!image?.id?.trim())
|
|
189
|
-
errors.push('Featured image is required for publishing');
|
|
190
|
-
// Only require category if it's explicitly provided and empty
|
|
191
|
-
// If categoryTags is undefined or category is undefined, that's also missing
|
|
192
|
-
if (!categoryTags || !categoryTags.category || !categoryTags.category.trim()) {
|
|
193
|
-
errors.push('Category is required for publishing');
|
|
194
|
-
}
|
|
195
|
-
const hasContent = (contentBlocks && Array.isArray(contentBlocks) && contentBlocks.length > 0) ||
|
|
196
|
-
(content && Array.isArray(content) && content.length > 0);
|
|
197
|
-
if (!hasContent) {
|
|
198
|
-
errors.push('Content is required for publishing');
|
|
199
|
-
}
|
|
200
|
-
if (!publicationData?.date)
|
|
201
|
-
errors.push('Publication date is required');
|
|
202
|
-
}
|
|
203
|
-
if (errors.length > 0) {
|
|
204
|
-
return NextResponse.json({ message: errors[0], allErrors: errors }, { status: 400 });
|
|
205
|
-
}
|
|
206
|
-
// Create slug
|
|
207
|
-
let baseSlug = slugify(title);
|
|
208
|
-
const slug = isPublishing
|
|
209
|
-
? baseSlug
|
|
210
|
-
: `${baseSlug}-draft-${Date.now().toString().slice(-4)}`;
|
|
211
|
-
const dbConnection = await config.getDb();
|
|
212
|
-
const db = dbConnection.db();
|
|
213
|
-
const blogs = db.collection(config.collectionName || 'blogs');
|
|
214
|
-
// Determine the final status: if publishing, set to 'published', otherwise convert draft to concept
|
|
215
|
-
let finalStatus = publicationData?.status;
|
|
216
|
-
if (isPublishing) {
|
|
217
|
-
finalStatus = 'published';
|
|
218
|
-
}
|
|
219
|
-
else if (publicationData?.status === 'draft') {
|
|
220
|
-
finalStatus = 'concept';
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
finalStatus = publicationData?.status || 'concept';
|
|
224
|
-
}
|
|
225
|
-
const blogDocument = {
|
|
226
|
-
title: title.trim(),
|
|
227
|
-
summary: (summary || '').trim(),
|
|
228
|
-
contentBlocks: contentBlocks || [],
|
|
229
|
-
content: content || [],
|
|
230
|
-
image: image || { id: '', alt: '' }, // Only store id and alt - plugin-images handles transforms
|
|
231
|
-
categoryTags: {
|
|
232
|
-
category: categoryTags?.category?.trim() || '',
|
|
233
|
-
tags: categoryTags?.tags || [],
|
|
234
|
-
},
|
|
235
|
-
publicationData: {
|
|
236
|
-
...publicationData,
|
|
237
|
-
status: finalStatus,
|
|
238
|
-
date: publicationData?.date ? new Date(publicationData.date) : new Date(),
|
|
239
|
-
},
|
|
240
|
-
seo: seo || { title: '', description: '' },
|
|
56
|
+
if (!body.title?.trim())
|
|
57
|
+
return NextResponse.json({ message: 'Title is required' }, { status: 400 });
|
|
58
|
+
const service = new BlogService(config);
|
|
59
|
+
const isPublishing = body.publicationData?.status === 'published';
|
|
60
|
+
const slug = isPublishing ? slugify(body.title) : `${slugify(body.title)}-draft-${Date.now().toString().slice(-4)}`;
|
|
61
|
+
const updateData = service.prepareUpdateData(body, null, language);
|
|
62
|
+
const finalDoc = {
|
|
63
|
+
...updateData,
|
|
241
64
|
slug,
|
|
242
65
|
authorId: userId,
|
|
243
|
-
|
|
244
|
-
[language]: {
|
|
245
|
-
blocks: contentBlocks || [],
|
|
246
|
-
metadata: {
|
|
247
|
-
title: title.trim(),
|
|
248
|
-
excerpt: (summary || '').trim(),
|
|
249
|
-
featuredImage: image,
|
|
250
|
-
categories: categoryTags?.category ? [categoryTags.category] : [],
|
|
251
|
-
tags: categoryTags?.tags || [],
|
|
252
|
-
seo: seo || {},
|
|
253
|
-
},
|
|
254
|
-
updatedAt: new Date().toISOString(),
|
|
255
|
-
status: finalStatus,
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
metadata: {
|
|
259
|
-
lang: language,
|
|
260
|
-
},
|
|
66
|
+
metadata: { lang: language },
|
|
261
67
|
createdAt: new Date(),
|
|
262
|
-
updatedAt: new Date(),
|
|
263
68
|
};
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
blogId: result.insertedId,
|
|
268
|
-
slug,
|
|
269
|
-
});
|
|
69
|
+
const dbConn = await config.getDb();
|
|
70
|
+
const result = await dbConn.db().collection(config.collectionName || 'blogs').insertOne(finalDoc);
|
|
71
|
+
return NextResponse.json({ message: 'Success', blogId: result.insertedId, slug });
|
|
270
72
|
}
|
|
271
73
|
catch (err) {
|
|
272
|
-
|
|
273
|
-
return NextResponse.json({ error: 'Failed to create blog', detail: err.message }, { status: 500 });
|
|
74
|
+
return NextResponse.json({ error: 'Create failed', detail: err.message }, { status: 500 });
|
|
274
75
|
}
|
|
275
76
|
}
|
|
276
|
-
/**
|
|
277
|
-
* GET /api/blogs/[slug] - Get single blog post by slug
|
|
278
|
-
*/
|
|
279
77
|
export async function GET_BY_SLUG(req, slug, config) {
|
|
280
78
|
try {
|
|
281
79
|
const url = new URL(req.url);
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
if (!blog) {
|
|
80
|
+
const service = new BlogService(config);
|
|
81
|
+
const lang = url.searchParams.get('language') || 'nl';
|
|
82
|
+
const dbConn = await config.getDb();
|
|
83
|
+
const collection = dbConn.db().collection(config.collectionName || 'blogs');
|
|
84
|
+
const blogDoc = await findPostBySlug(collection, slug);
|
|
85
|
+
if (!blogDoc) {
|
|
289
86
|
return NextResponse.json({ error: 'Blog not found' }, { status: 404 });
|
|
290
87
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const isAdminView = url.searchParams.get('admin') === 'true';
|
|
294
|
-
// For public view, we must have a published version in the requested language
|
|
295
|
-
if (!isAdminView && !isAuthor) {
|
|
296
|
-
const langData = blog.languages?.[requestedLanguage];
|
|
297
|
-
if (!langData || langData.status !== 'published') {
|
|
298
|
-
return NextResponse.json({ error: 'Blog post not available in this language' }, { status: 404 });
|
|
299
|
-
}
|
|
300
|
-
// Also check publication date
|
|
301
|
-
if (blog.publicationData?.date && new Date(blog.publicationData.date) > new Date()) {
|
|
302
|
-
return NextResponse.json({ error: 'Blog post not yet published' }, { status: 404 });
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const languages = blog.languages || {};
|
|
306
|
-
const postPrimaryLang = blog.metadata?.lang || 'nl';
|
|
307
|
-
// Find the best available language for this post
|
|
308
|
-
let bestLanguage = requestedLanguage;
|
|
309
|
-
let isMissingTranslation = false;
|
|
310
|
-
if (!languages[requestedLanguage]) {
|
|
311
|
-
if (isAdminView) {
|
|
312
|
-
isMissingTranslation = true;
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
// Try fallback languages in order
|
|
316
|
-
const fallbackLanguages = [requestedLanguage, 'nl', 'en'];
|
|
317
|
-
for (const lang of fallbackLanguages) {
|
|
318
|
-
if (languages[lang]) {
|
|
319
|
-
bestLanguage = lang;
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
// If still no match, use primary language from metadata
|
|
324
|
-
if (!languages[bestLanguage] && languages[postPrimaryLang]) {
|
|
325
|
-
bestLanguage = postPrimaryLang;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
const langContent = languages[bestLanguage] || {};
|
|
330
|
-
const meta = langContent.metadata || {};
|
|
331
|
-
// Language display names for placeholders
|
|
332
|
-
const langNames = {
|
|
333
|
-
nl: 'Dutch',
|
|
334
|
-
en: 'English',
|
|
335
|
-
sv: 'Swedish',
|
|
336
|
-
de: 'German',
|
|
337
|
-
fr: 'French',
|
|
338
|
-
es: 'Spanish'
|
|
339
|
-
};
|
|
340
|
-
let title = isMissingTranslation
|
|
341
|
-
? `(No ${langNames[requestedLanguage] || requestedLanguage.toUpperCase()} translation)`
|
|
342
|
-
: (meta.title || blog.title || '');
|
|
343
|
-
let summary = isMissingTranslation ? '' : (meta.excerpt || blog.summary || '');
|
|
344
|
-
let contentBlocks = isMissingTranslation ? [] : (langContent.blocks || blog.contentBlocks || blog.blocks || []);
|
|
345
|
-
let image = isMissingTranslation ? null : (meta.featuredImage || blog.image);
|
|
346
|
-
let categoryTags = isMissingTranslation ? { category: '', tags: [] } : (meta.categories ? {
|
|
347
|
-
category: meta.categories[0] || '',
|
|
348
|
-
tags: meta.tags || blog.categoryTags?.tags || []
|
|
349
|
-
} : (blog.categoryTags || { category: '', tags: [] }));
|
|
350
|
-
let seo = isMissingTranslation ? {} : (meta.seo || blog.seo || {});
|
|
351
|
-
let metadata = { ...blog.metadata, ...meta, lang: bestLanguage };
|
|
352
|
-
// Ensure all languages in doc have a status and updatedAt for the dashboard
|
|
353
|
-
const enrichedLanguages = { ...languages };
|
|
354
|
-
Object.keys(enrichedLanguages).forEach(lang => {
|
|
355
|
-
if (!enrichedLanguages[lang].status) {
|
|
356
|
-
enrichedLanguages[lang].status = blog.publicationData?.status === 'concept' ? 'draft' : (blog.publicationData?.status || 'draft');
|
|
357
|
-
}
|
|
358
|
-
if (!enrichedLanguages[lang].updatedAt) {
|
|
359
|
-
enrichedLanguages[lang].updatedAt = blog.updatedAt;
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
const displayStatus = isMissingTranslation
|
|
363
|
-
? 'not-translated'
|
|
364
|
-
: (langContent.status || blog.publicationData?.status || 'concept');
|
|
365
|
-
return NextResponse.json({
|
|
366
|
-
...blog,
|
|
367
|
-
_id: blog._id.toString(),
|
|
368
|
-
title,
|
|
369
|
-
summary,
|
|
370
|
-
contentBlocks,
|
|
371
|
-
image,
|
|
372
|
-
categoryTags,
|
|
373
|
-
publicationData: {
|
|
374
|
-
...blog.publicationData,
|
|
375
|
-
status: displayStatus,
|
|
376
|
-
},
|
|
377
|
-
seo,
|
|
378
|
-
metadata,
|
|
379
|
-
languages: enrichedLanguages,
|
|
380
|
-
availableLanguages: Object.keys(languages),
|
|
381
|
-
lang: bestLanguage,
|
|
382
|
-
isMissingTranslation,
|
|
383
|
-
requestedLanguage,
|
|
384
|
-
updatedAt: isMissingTranslation ? blog.updatedAt : (langContent.updatedAt || blog.updatedAt),
|
|
385
|
-
status: displayStatus,
|
|
386
|
-
});
|
|
88
|
+
const formatted = service.formatWithLanguage(blogDoc, lang, url.searchParams.get('admin') === 'true');
|
|
89
|
+
return NextResponse.json(formatted);
|
|
387
90
|
}
|
|
388
91
|
catch (err) {
|
|
389
92
|
console.error('[BlogAPI] GET_BY_SLUG error:', err);
|
|
390
|
-
return NextResponse.json({ error: '
|
|
93
|
+
return NextResponse.json({ error: 'Fetch failed', detail: err.message }, { status: 500 });
|
|
391
94
|
}
|
|
392
95
|
}
|
|
393
|
-
/**
|
|
394
|
-
* PUT /api/blogs/[slug] - Update blog post by slug
|
|
395
|
-
*/
|
|
396
96
|
export async function PUT_BY_SLUG(req, slug, config) {
|
|
397
97
|
try {
|
|
398
98
|
const url = new URL(req.url);
|
|
399
99
|
const language = url.searchParams.get('language') || 'nl';
|
|
400
100
|
const userId = await config.getUserId(req);
|
|
401
|
-
if (!userId)
|
|
101
|
+
if (!userId)
|
|
402
102
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
// Validation
|
|
418
|
-
const isPublishing = publicationData?.status === 'published';
|
|
419
|
-
if (!title?.trim()) {
|
|
420
|
-
return NextResponse.json({ message: 'Title is required' }, { status: 400 });
|
|
421
|
-
}
|
|
422
|
-
if (isPublishing) {
|
|
423
|
-
const hasContent = (contentBlocks && Array.isArray(contentBlocks) && contentBlocks.length > 0) ||
|
|
424
|
-
(content && Array.isArray(content) && content.length > 0);
|
|
425
|
-
// Collect all missing fields for better error messages
|
|
426
|
-
const missingFields = [];
|
|
427
|
-
if (!summary?.trim())
|
|
428
|
-
missingFields.push('summary');
|
|
429
|
-
if (!image?.id?.trim())
|
|
430
|
-
missingFields.push('featured image');
|
|
431
|
-
// Only require category if it's explicitly provided and empty
|
|
432
|
-
// If categoryTags is undefined or category is undefined, that's also missing
|
|
433
|
-
if (!categoryTags || !categoryTags.category || !categoryTags.category.trim()) {
|
|
434
|
-
missingFields.push('category');
|
|
435
|
-
}
|
|
436
|
-
if (!hasContent)
|
|
437
|
-
missingFields.push('content');
|
|
438
|
-
if (missingFields.length > 0) {
|
|
439
|
-
console.log('[BlogAPI] PUT_BY_SLUG validation failed:', {
|
|
440
|
-
isPublishing,
|
|
441
|
-
missingFields,
|
|
442
|
-
summary: summary?.trim() || 'missing',
|
|
443
|
-
imageId: image?.id?.trim() || 'missing',
|
|
444
|
-
category: categoryTags?.category?.trim() || 'missing',
|
|
445
|
-
hasContent,
|
|
446
|
-
contentBlocksLength: contentBlocks?.length || 0,
|
|
447
|
-
contentLength: content?.length || 0,
|
|
448
|
-
});
|
|
449
|
-
return NextResponse.json({
|
|
450
|
-
message: `Missing required fields for publishing: ${missingFields.join(', ')}`,
|
|
451
|
-
missingFields
|
|
452
|
-
}, { status: 400 });
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
// Slug logic - DON'T change slug for existing posts, keep original
|
|
456
|
-
// The slug should remain constant across all language versions
|
|
457
|
-
const finalSlug = slug;
|
|
458
|
-
// Determine the final status: if publishing, set to 'published', otherwise preserve or convert draft to concept
|
|
459
|
-
let finalStatus = publicationData?.status;
|
|
460
|
-
if (isPublishing) {
|
|
461
|
-
finalStatus = 'published';
|
|
462
|
-
}
|
|
463
|
-
else if (publicationData?.status === 'draft') {
|
|
464
|
-
finalStatus = 'concept';
|
|
465
|
-
}
|
|
466
|
-
// Preserve existing languages or initialize
|
|
467
|
-
const existingLanguages = existingBlog.languages || {};
|
|
468
|
-
const primaryLanguage = existingBlog.metadata?.lang || 'nl';
|
|
469
|
-
// Update the specific language content
|
|
470
|
-
// Only the language being saved gets a new updatedAt timestamp
|
|
471
|
-
const now = new Date();
|
|
472
|
-
const updatedLanguages = {
|
|
473
|
-
...existingLanguages,
|
|
474
|
-
[language]: {
|
|
475
|
-
blocks: contentBlocks || [],
|
|
476
|
-
metadata: {
|
|
477
|
-
title: title.trim(),
|
|
478
|
-
excerpt: (summary || '').trim(),
|
|
479
|
-
featuredImage: image,
|
|
480
|
-
categories: categoryTags?.category ? [categoryTags.category] : [],
|
|
481
|
-
tags: categoryTags?.tags || [],
|
|
482
|
-
seo: seo || {},
|
|
483
|
-
},
|
|
484
|
-
updatedAt: now.toISOString(),
|
|
485
|
-
status: finalStatus,
|
|
486
|
-
},
|
|
487
|
-
};
|
|
488
|
-
// For root-level fields, only update if this is the primary language
|
|
489
|
-
// Otherwise preserve existing root values to maintain backward compatibility
|
|
490
|
-
const isPrimaryLanguage = language === primaryLanguage || !existingLanguages[primaryLanguage];
|
|
491
|
-
const updateData = {
|
|
492
|
-
// Only update root-level title/summary if saving in primary language
|
|
493
|
-
// This maintains backward compatibility
|
|
494
|
-
...(isPrimaryLanguage ? {
|
|
495
|
-
title: title.trim(),
|
|
496
|
-
summary: (summary || '').trim(),
|
|
497
|
-
contentBlocks: contentBlocks || [],
|
|
498
|
-
content: content || [],
|
|
499
|
-
image: image || {},
|
|
500
|
-
categoryTags: {
|
|
501
|
-
category: categoryTags?.category?.trim() || '',
|
|
502
|
-
tags: categoryTags?.tags || [],
|
|
503
|
-
},
|
|
504
|
-
seo: seo || {},
|
|
505
|
-
} : {}),
|
|
506
|
-
publicationData: {
|
|
507
|
-
...existingBlog.publicationData,
|
|
508
|
-
...publicationData,
|
|
509
|
-
status: finalStatus,
|
|
510
|
-
date: publicationData?.date ? new Date(publicationData.date) : existingBlog.publicationData?.date || new Date(),
|
|
511
|
-
},
|
|
512
|
-
authorId: userId,
|
|
513
|
-
languages: updatedLanguages,
|
|
514
|
-
metadata: {
|
|
515
|
-
...existingBlog.metadata,
|
|
516
|
-
lang: primaryLanguage,
|
|
517
|
-
},
|
|
518
|
-
updatedAt: new Date(),
|
|
519
|
-
};
|
|
520
|
-
await blogs.updateOne({ slug }, { $set: updateData });
|
|
521
|
-
return NextResponse.json({
|
|
522
|
-
message: 'Blog updated successfully',
|
|
523
|
-
slug: slug, // Return original slug, not finalSlug
|
|
524
|
-
});
|
|
103
|
+
const dbConn = await config.getDb();
|
|
104
|
+
const collection = dbConn.db().collection(config.collectionName || 'blogs');
|
|
105
|
+
const existing = await findPostBySlug(collection, slug);
|
|
106
|
+
if (!existing)
|
|
107
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
108
|
+
if (existing.authorId !== userId)
|
|
109
|
+
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
110
|
+
const service = new BlogService(config);
|
|
111
|
+
const updateData = service.prepareUpdateData(await req.json(), existing, language);
|
|
112
|
+
await collection.updateOne({ _id: existing._id }, { $set: updateData });
|
|
113
|
+
// Return the potentially NEW localized slug so the editor can redirect if needed
|
|
114
|
+
const newSlug = updateData.languages[language].metadata.slug || existing.slug;
|
|
115
|
+
return NextResponse.json({ message: 'Updated', slug: newSlug });
|
|
525
116
|
}
|
|
526
117
|
catch (err) {
|
|
527
|
-
|
|
528
|
-
return NextResponse.json({ error: 'Failed to update blog', detail: err.message }, { status: 500 });
|
|
118
|
+
return NextResponse.json({ error: 'Update failed', detail: err.message }, { status: 500 });
|
|
529
119
|
}
|
|
530
120
|
}
|
|
531
|
-
/**
|
|
532
|
-
* DELETE /api/blogs/[slug] - Delete blog post by slug
|
|
533
|
-
*/
|
|
534
121
|
export async function DELETE_BY_SLUG(req, slug, config) {
|
|
535
122
|
try {
|
|
536
123
|
const userId = await config.getUserId(req);
|
|
537
|
-
if (!userId)
|
|
124
|
+
if (!userId)
|
|
538
125
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
if (blog.authorId !== userId) {
|
|
549
|
-
return NextResponse.json({ error: 'Forbidden: Not the author' }, { status: 403 });
|
|
550
|
-
}
|
|
551
|
-
await blogs.deleteOne({ slug });
|
|
552
|
-
return NextResponse.json({ message: 'Blog deleted successfully' });
|
|
126
|
+
const dbConn = await config.getDb();
|
|
127
|
+
const collection = dbConn.db().collection(config.collectionName || 'blogs');
|
|
128
|
+
const blog = await findPostBySlug(collection, slug);
|
|
129
|
+
if (!blog)
|
|
130
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
131
|
+
if (blog.authorId !== userId)
|
|
132
|
+
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
133
|
+
await collection.deleteOne({ _id: blog._id });
|
|
134
|
+
return NextResponse.json({ message: 'Deleted' });
|
|
553
135
|
}
|
|
554
136
|
catch (err) {
|
|
555
|
-
|
|
556
|
-
return NextResponse.json({ error: 'Failed to delete blog', detail: err.message }, { status: 500 });
|
|
137
|
+
return NextResponse.json({ error: 'Delete failed', detail: err.message }, { status: 500 });
|
|
557
138
|
}
|
|
558
139
|
}
|
package/dist/api/router.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin Blog API Router
|
|
3
3
|
* Centralized API handler for all blog plugin routes
|
|
4
|
-
*
|
|
5
|
-
* This router handles requests to /api/plugin-blog/*
|
|
6
|
-
* and routes them to the appropriate handler
|
|
7
4
|
*/
|
|
8
5
|
import { NextRequest, NextResponse } from 'next/server';
|
|
9
6
|
export interface BlogApiRouterConfig {
|
|
@@ -20,8 +17,6 @@ export interface BlogApiRouterConfig {
|
|
|
20
17
|
}
|
|
21
18
|
/**
|
|
22
19
|
* Handle blog API requests
|
|
23
|
-
* Routes requests to appropriate handlers based on path
|
|
24
|
-
* Similar to plugin-dep, accepts config directly instead of requiring initialization
|
|
25
20
|
*/
|
|
26
21
|
export declare function handleBlogApi(req: NextRequest, path: string[], config: BlogApiRouterConfig): Promise<NextResponse>;
|
|
27
22
|
//# sourceMappingURL=router.d.ts.map
|
package/dist/api/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKxD,MAAM,WAAW,mBAAmB;IAChC,oEAAoE;IACpE,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,yDAAyD;IACzD,SAAS,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAC/B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA+CvB"}
|