@jhits/plugin-blog 0.0.9 → 0.0.11

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 (276) hide show
  1. package/package.json +58 -59
  2. package/src/api/categories.ts +0 -43
  3. package/src/api/check-title.ts +0 -60
  4. package/src/api/config-handler.ts +0 -76
  5. package/src/api/handler.ts +0 -418
  6. package/src/api/index.ts +0 -33
  7. package/src/api/route.ts +0 -116
  8. package/src/api/router.ts +0 -128
  9. package/src/api-server.ts +0 -11
  10. package/src/config.ts +0 -161
  11. package/src/hooks/index.d.ts +0 -8
  12. package/src/hooks/index.d.ts.map +0 -1
  13. package/src/hooks/index.js +0 -7
  14. package/src/hooks/index.ts +0 -9
  15. package/src/hooks/useBlog.d.ts +0 -31
  16. package/src/hooks/useBlog.d.ts.map +0 -1
  17. package/src/hooks/useBlog.js +0 -57
  18. package/src/hooks/useBlog.ts +0 -85
  19. package/src/hooks/useBlogs.d.ts +0 -39
  20. package/src/hooks/useBlogs.d.ts.map +0 -1
  21. package/src/hooks/useBlogs.js +0 -82
  22. package/src/hooks/useBlogs.ts +0 -123
  23. package/src/hooks/useCategories.d.ts +0 -9
  24. package/src/hooks/useCategories.d.ts.map +0 -1
  25. package/src/hooks/useCategories.js +0 -70
  26. package/src/hooks/useCategories.ts +0 -76
  27. package/src/index.d.ts +0 -55
  28. package/src/index.d.ts.map +0 -1
  29. package/src/index.js +0 -228
  30. package/src/index.server.ts +0 -14
  31. package/src/index.tsx +0 -335
  32. package/src/init.d.ts +0 -40
  33. package/src/init.d.ts.map +0 -1
  34. package/src/init.js +0 -41
  35. package/src/init.tsx +0 -63
  36. package/src/lib/blocks/BlockRenderer.d.ts +0 -54
  37. package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
  38. package/src/lib/blocks/BlockRenderer.js +0 -54
  39. package/src/lib/blocks/BlockRenderer.tsx +0 -141
  40. package/src/lib/blocks/index.ts +0 -6
  41. package/src/lib/config-storage.d.ts +0 -30
  42. package/src/lib/config-storage.d.ts.map +0 -1
  43. package/src/lib/config-storage.js +0 -31
  44. package/src/lib/config-storage.ts +0 -65
  45. package/src/lib/index.ts +0 -9
  46. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
  47. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
  48. package/src/lib/layouts/blocks/ColumnsBlock.js +0 -182
  49. package/src/lib/layouts/blocks/ColumnsBlock.tsx +0 -298
  50. package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +0 -81
  51. package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
  52. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
  53. package/src/lib/layouts/blocks/SectionBlock.js +0 -44
  54. package/src/lib/layouts/blocks/SectionBlock.tsx +0 -104
  55. package/src/lib/layouts/blocks/index.ts +0 -8
  56. package/src/lib/layouts/index.d.ts +0 -23
  57. package/src/lib/layouts/index.d.ts.map +0 -1
  58. package/src/lib/layouts/index.js +0 -45
  59. package/src/lib/layouts/index.ts +0 -52
  60. package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
  61. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
  62. package/src/lib/layouts/registerLayoutBlocks.js +0 -60
  63. package/src/lib/layouts/registerLayoutBlocks.ts +0 -64
  64. package/src/lib/mappers/apiMapper.d.ts +0 -66
  65. package/src/lib/mappers/apiMapper.d.ts.map +0 -1
  66. package/src/lib/mappers/apiMapper.js +0 -191
  67. package/src/lib/mappers/apiMapper.ts +0 -254
  68. package/src/lib/migration/index.ts +0 -6
  69. package/src/lib/migration/mapper.ts +0 -140
  70. package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
  71. package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
  72. package/src/lib/rich-text/RichTextEditor.js +0 -564
  73. package/src/lib/rich-text/RichTextEditor.tsx +0 -826
  74. package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
  75. package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
  76. package/src/lib/rich-text/RichTextPreview.js +0 -144
  77. package/src/lib/rich-text/RichTextPreview.tsx +0 -210
  78. package/src/lib/rich-text/index.d.ts +0 -9
  79. package/src/lib/rich-text/index.d.ts.map +0 -1
  80. package/src/lib/rich-text/index.js +0 -6
  81. package/src/lib/rich-text/index.ts +0 -10
  82. package/src/lib/utils/blockHelpers.d.ts +0 -23
  83. package/src/lib/utils/blockHelpers.d.ts.map +0 -1
  84. package/src/lib/utils/blockHelpers.js +0 -65
  85. package/src/lib/utils/blockHelpers.ts +0 -72
  86. package/src/lib/utils/configValidation.d.ts +0 -23
  87. package/src/lib/utils/configValidation.d.ts.map +0 -1
  88. package/src/lib/utils/configValidation.js +0 -113
  89. package/src/lib/utils/configValidation.ts +0 -137
  90. package/src/lib/utils/index.ts +0 -8
  91. package/src/lib/utils/slugify.ts +0 -79
  92. package/src/registry/BlockRegistry.d.ts +0 -62
  93. package/src/registry/BlockRegistry.d.ts.map +0 -1
  94. package/src/registry/BlockRegistry.js +0 -112
  95. package/src/registry/BlockRegistry.ts +0 -139
  96. package/src/registry/index.d.ts +0 -6
  97. package/src/registry/index.d.ts.map +0 -1
  98. package/src/registry/index.js +0 -4
  99. package/src/registry/index.ts +0 -11
  100. package/src/state/EditorContext.d.ts +0 -45
  101. package/src/state/EditorContext.d.ts.map +0 -1
  102. package/src/state/EditorContext.js +0 -215
  103. package/src/state/EditorContext.tsx +0 -283
  104. package/src/state/index.d.ts +0 -7
  105. package/src/state/index.d.ts.map +0 -1
  106. package/src/state/index.js +0 -6
  107. package/src/state/index.ts +0 -8
  108. package/src/state/reducer.d.ts +0 -11
  109. package/src/state/reducer.d.ts.map +0 -1
  110. package/src/state/reducer.js +0 -443
  111. package/src/state/reducer.ts +0 -694
  112. package/src/state/types.d.ts +0 -162
  113. package/src/state/types.d.ts.map +0 -1
  114. package/src/state/types.js +0 -27
  115. package/src/state/types.ts +0 -160
  116. package/src/types/block.d.ts +0 -221
  117. package/src/types/block.d.ts.map +0 -1
  118. package/src/types/block.js +0 -6
  119. package/src/types/block.ts +0 -269
  120. package/src/types/index.d.ts +0 -8
  121. package/src/types/index.d.ts.map +0 -1
  122. package/src/types/index.js +0 -5
  123. package/src/types/index.ts +0 -17
  124. package/src/types/post.d.ts +0 -136
  125. package/src/types/post.d.ts.map +0 -1
  126. package/src/types/post.js +0 -5
  127. package/src/types/post.ts +0 -169
  128. package/src/utils/client.d.ts +0 -48
  129. package/src/utils/client.d.ts.map +0 -1
  130. package/src/utils/client.js +0 -77
  131. package/src/utils/client.ts +0 -122
  132. package/src/utils/index.ts +0 -7
  133. package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
  134. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
  135. package/src/views/CanvasEditor/BlockWrapper.js +0 -276
  136. package/src/views/CanvasEditor/BlockWrapper.tsx +0 -522
  137. package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
  138. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
  139. package/src/views/CanvasEditor/CanvasEditorView.js +0 -209
  140. package/src/views/CanvasEditor/CanvasEditorView.tsx +0 -337
  141. package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
  142. package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
  143. package/src/views/CanvasEditor/EditorBody.js +0 -505
  144. package/src/views/CanvasEditor/EditorBody.tsx +0 -665
  145. package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
  146. package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
  147. package/src/views/CanvasEditor/EditorHeader.js +0 -101
  148. package/src/views/CanvasEditor/EditorHeader.tsx +0 -268
  149. package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
  150. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
  151. package/src/views/CanvasEditor/LayoutContainer.js +0 -222
  152. package/src/views/CanvasEditor/LayoutContainer.tsx +0 -322
  153. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
  154. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
  155. package/src/views/CanvasEditor/SaveConfirmationModal.js +0 -78
  156. package/src/views/CanvasEditor/SaveConfirmationModal.tsx +0 -233
  157. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
  158. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
  159. package/src/views/CanvasEditor/components/CustomBlockItem.js +0 -44
  160. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +0 -92
  161. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
  162. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
  163. package/src/views/CanvasEditor/components/EditorCanvas.js +0 -32
  164. package/src/views/CanvasEditor/components/EditorCanvas.tsx +0 -160
  165. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
  166. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
  167. package/src/views/CanvasEditor/components/EditorLibrary.js +0 -25
  168. package/src/views/CanvasEditor/components/EditorLibrary.tsx +0 -122
  169. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
  170. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
  171. package/src/views/CanvasEditor/components/EditorSidebar.js +0 -20
  172. package/src/views/CanvasEditor/components/EditorSidebar.tsx +0 -181
  173. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
  174. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
  175. package/src/views/CanvasEditor/components/ErrorBanner.js +0 -8
  176. package/src/views/CanvasEditor/components/ErrorBanner.tsx +0 -31
  177. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
  178. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
  179. package/src/views/CanvasEditor/components/FeaturedMediaSection.js +0 -182
  180. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +0 -341
  181. package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
  182. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
  183. package/src/views/CanvasEditor/components/LibraryItem.js +0 -43
  184. package/src/views/CanvasEditor/components/LibraryItem.tsx +0 -80
  185. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
  186. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
  187. package/src/views/CanvasEditor/components/PrivacySettingsSection.js +0 -63
  188. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +0 -212
  189. package/src/views/CanvasEditor/components/index.d.ts +0 -21
  190. package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
  191. package/src/views/CanvasEditor/components/index.js +0 -12
  192. package/src/views/CanvasEditor/components/index.ts +0 -28
  193. package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
  194. package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
  195. package/src/views/CanvasEditor/hooks/index.js +0 -9
  196. package/src/views/CanvasEditor/hooks/index.ts +0 -10
  197. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
  198. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
  199. package/src/views/CanvasEditor/hooks/useHeroBlock.js +0 -79
  200. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +0 -103
  201. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
  202. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
  203. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.js +0 -114
  204. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +0 -142
  205. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
  206. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
  207. package/src/views/CanvasEditor/hooks/usePostLoader.js +0 -32
  208. package/src/views/CanvasEditor/hooks/usePostLoader.ts +0 -39
  209. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
  210. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
  211. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.js +0 -47
  212. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +0 -55
  213. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
  214. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
  215. package/src/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -285
  216. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +0 -339
  217. package/src/views/CanvasEditor/index.d.ts +0 -16
  218. package/src/views/CanvasEditor/index.d.ts.map +0 -1
  219. package/src/views/CanvasEditor/index.js +0 -9
  220. package/src/views/CanvasEditor/index.ts +0 -16
  221. package/src/views/PostManager/EmptyState.d.ts +0 -10
  222. package/src/views/PostManager/EmptyState.d.ts.map +0 -1
  223. package/src/views/PostManager/EmptyState.js +0 -12
  224. package/src/views/PostManager/EmptyState.tsx +0 -42
  225. package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
  226. package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
  227. package/src/views/PostManager/PostActionsMenu.js +0 -58
  228. package/src/views/PostManager/PostActionsMenu.tsx +0 -112
  229. package/src/views/PostManager/PostCards.d.ts +0 -15
  230. package/src/views/PostManager/PostCards.d.ts.map +0 -1
  231. package/src/views/PostManager/PostCards.js +0 -79
  232. package/src/views/PostManager/PostCards.tsx +0 -197
  233. package/src/views/PostManager/PostFilters.d.ts +0 -16
  234. package/src/views/PostManager/PostFilters.d.ts.map +0 -1
  235. package/src/views/PostManager/PostFilters.js +0 -10
  236. package/src/views/PostManager/PostFilters.tsx +0 -95
  237. package/src/views/PostManager/PostManagerView.d.ts +0 -11
  238. package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
  239. package/src/views/PostManager/PostManagerView.js +0 -174
  240. package/src/views/PostManager/PostManagerView.tsx +0 -289
  241. package/src/views/PostManager/PostStats.d.ts +0 -11
  242. package/src/views/PostManager/PostStats.d.ts.map +0 -1
  243. package/src/views/PostManager/PostStats.js +0 -46
  244. package/src/views/PostManager/PostStats.tsx +0 -81
  245. package/src/views/PostManager/PostTable.d.ts +0 -15
  246. package/src/views/PostManager/PostTable.d.ts.map +0 -1
  247. package/src/views/PostManager/PostTable.js +0 -79
  248. package/src/views/PostManager/PostTable.tsx +0 -230
  249. package/src/views/PostManager/index.d.ts +0 -12
  250. package/src/views/PostManager/index.d.ts.map +0 -1
  251. package/src/views/PostManager/index.js +0 -11
  252. package/src/views/PostManager/index.ts +0 -15
  253. package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
  254. package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
  255. package/src/views/Preview/PreviewBridgeView.js +0 -11
  256. package/src/views/Preview/PreviewBridgeView.tsx +0 -64
  257. package/src/views/Preview/index.d.ts +0 -6
  258. package/src/views/Preview/index.d.ts.map +0 -1
  259. package/src/views/Preview/index.js +0 -4
  260. package/src/views/Preview/index.ts +0 -7
  261. package/src/views/Settings/SettingsView.d.ts +0 -10
  262. package/src/views/Settings/SettingsView.d.ts.map +0 -1
  263. package/src/views/Settings/SettingsView.js +0 -111
  264. package/src/views/Settings/SettingsView.tsx +0 -298
  265. package/src/views/Settings/index.d.ts +0 -6
  266. package/src/views/Settings/index.d.ts.map +0 -1
  267. package/src/views/Settings/index.js +0 -4
  268. package/src/views/Settings/index.ts +0 -7
  269. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
  270. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
  271. package/src/views/SlugSEO/SlugSEOManagerView.js +0 -11
  272. package/src/views/SlugSEO/SlugSEOManagerView.tsx +0 -94
  273. package/src/views/SlugSEO/index.d.ts +0 -6
  274. package/src/views/SlugSEO/index.d.ts.map +0 -1
  275. package/src/views/SlugSEO/index.js +0 -4
  276. package/src/views/SlugSEO/index.ts +0 -7
@@ -1,418 +0,0 @@
1
- /**
2
- * Blog API Handler
3
- * RESTful API handler for blog posts
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.
8
- */
9
-
10
- import { NextRequest, NextResponse } from 'next/server';
11
- import { slugify } from '../lib/utils/slugify';
12
-
13
- export interface BlogApiConfig {
14
- /** MongoDB client promise (from clientPromise) - should return { db: () => Database } */
15
- getDb: () => Promise<{ db: () => any }>;
16
- /** Function to get authenticated user ID from request */
17
- getUserId: (req: NextRequest) => Promise<string | null>;
18
- /** Collection name (default: 'blogs') */
19
- collectionName?: string;
20
- }
21
-
22
- /**
23
- * GET /api/blogs - List all blog posts
24
- * GET /api/blogs?admin=true - List all posts for admin (includes drafts)
25
- * GET /api/blogs?status=published - Filter by status
26
- */
27
- export async function GET(req: NextRequest, config: BlogApiConfig): Promise<NextResponse> {
28
- try {
29
- const url = new URL(req.url);
30
- const limit = Number(url.searchParams.get('limit') ?? 10);
31
- const skip = Number(url.searchParams.get('skip') ?? 0);
32
- const statusFilter = url.searchParams.get('status');
33
- const isAdminView = url.searchParams.get('admin') === 'true';
34
-
35
- const userId = await config.getUserId(req);
36
- const dbConnection = await config.getDb();
37
- const db = dbConnection.db();
38
- const blogs = db.collection(config.collectionName || 'blogs');
39
-
40
- // Build query
41
- let query: any = {};
42
-
43
- if (isAdminView && userId) {
44
- // Admin view: show all posts owned by user
45
- if (statusFilter) {
46
- query = {
47
- 'publicationData.status': statusFilter,
48
- authorId: userId,
49
- };
50
- } else {
51
- query = { authorId: userId };
52
- }
53
- } else {
54
- // Public view: only published posts
55
- query = {
56
- 'publicationData.status': 'published',
57
- 'publicationData.date': { $lte: new Date() },
58
- };
59
- if (statusFilter && statusFilter !== 'published') {
60
- // Non-admin can't filter by non-published status
61
- return NextResponse.json({ error: 'Invalid status filter' }, { status: 400 });
62
- }
63
- }
64
-
65
- const [data, totalCount] = await Promise.all([
66
- blogs
67
- .find(query)
68
- .sort({ 'publicationData.date': -1 })
69
- .skip(skip)
70
- .limit(isAdminView ? 0 : limit)
71
- .toArray(),
72
- blogs.countDocuments(query),
73
- ]);
74
-
75
- const formatted = data.map((doc: any) => ({
76
- ...doc,
77
- _id: doc._id.toString(),
78
- }));
79
-
80
- return NextResponse.json({
81
- blogs: formatted,
82
- total: totalCount,
83
- });
84
- } catch (err: any) {
85
- console.error('[BlogAPI] GET error:', err);
86
- return NextResponse.json(
87
- { error: 'Failed to fetch blogs', detail: err.message },
88
- { status: 500 }
89
- );
90
- }
91
- }
92
-
93
- /**
94
- * POST /api/blogs - Create new blog post
95
- */
96
- export async function POST(req: NextRequest, config: BlogApiConfig): Promise<NextResponse> {
97
- try {
98
- const userId = await config.getUserId(req);
99
- if (!userId) {
100
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
101
- }
102
-
103
- const body = await req.json();
104
- const {
105
- title,
106
- summary,
107
- content,
108
- contentBlocks,
109
- image,
110
- categoryTags,
111
- publicationData,
112
- seo,
113
- } = body;
114
-
115
- const isPublishing = publicationData?.status === 'published';
116
- const isConcept = publicationData?.status === 'concept' || publicationData?.status === 'draft';
117
-
118
- // Validation
119
- const errors: string[] = [];
120
- if (!title?.trim()) {
121
- errors.push('Title is required');
122
- }
123
-
124
- if (isPublishing) {
125
- // Publishing requires all fields
126
- if (!summary?.trim()) errors.push('Summary is required for publishing');
127
- if (!image?.id?.trim()) errors.push('Featured image is required for publishing');
128
- // Only require category if it's explicitly provided and empty
129
- // If categoryTags is undefined or category is undefined, that's also missing
130
- if (!categoryTags || !categoryTags.category || !categoryTags.category.trim()) {
131
- errors.push('Category is required for publishing');
132
- }
133
- const hasContent =
134
- (contentBlocks && Array.isArray(contentBlocks) && contentBlocks.length > 0) ||
135
- (content && Array.isArray(content) && content.length > 0);
136
- if (!hasContent) {
137
- errors.push('Content is required for publishing');
138
- }
139
- if (!publicationData?.date) errors.push('Publication date is required');
140
- }
141
-
142
- if (errors.length > 0) {
143
- return NextResponse.json({ message: errors[0], allErrors: errors }, { status: 400 });
144
- }
145
-
146
- // Create slug
147
- let baseSlug = slugify(title);
148
- const slug = isPublishing
149
- ? baseSlug
150
- : `${baseSlug}-draft-${Date.now().toString().slice(-4)}`;
151
-
152
- const dbConnection = await config.getDb();
153
- const db = dbConnection.db();
154
- const blogs = db.collection(config.collectionName || 'blogs');
155
-
156
- // Determine the final status: if publishing, set to 'published', otherwise convert draft to concept
157
- let finalStatus = publicationData?.status;
158
- if (isPublishing) {
159
- finalStatus = 'published';
160
- } else if (publicationData?.status === 'draft') {
161
- finalStatus = 'concept';
162
- } else {
163
- finalStatus = publicationData?.status || 'concept';
164
- }
165
-
166
- const blogDocument = {
167
- title: title.trim(),
168
- summary: (summary || '').trim(),
169
- contentBlocks: contentBlocks || [],
170
- content: content || [],
171
- image: image || { id: '', alt: '' }, // Only store id and alt - plugin-images handles transforms
172
- categoryTags: {
173
- category: categoryTags?.category?.trim() || '',
174
- tags: categoryTags?.tags || [],
175
- },
176
- publicationData: {
177
- ...publicationData,
178
- status: finalStatus,
179
- date: publicationData?.date ? new Date(publicationData.date) : new Date(),
180
- },
181
- seo: seo || { title: '', description: '' },
182
- slug,
183
- authorId: userId,
184
- createdAt: new Date(),
185
- updatedAt: new Date(),
186
- };
187
-
188
- const result = await blogs.insertOne(blogDocument);
189
-
190
- return NextResponse.json({
191
- message: isPublishing ? 'Blog published successfully' : 'Draft saved successfully',
192
- blogId: result.insertedId,
193
- slug,
194
- });
195
- } catch (err: any) {
196
- console.error('[BlogAPI] POST error:', err);
197
- return NextResponse.json(
198
- { error: 'Failed to create blog', detail: err.message },
199
- { status: 500 }
200
- );
201
- }
202
- }
203
-
204
- /**
205
- * GET /api/blogs/[slug] - Get single blog post by slug
206
- */
207
- export async function GET_BY_SLUG(
208
- req: NextRequest,
209
- slug: string,
210
- config: BlogApiConfig
211
- ): Promise<NextResponse> {
212
- try {
213
- const userId = await config.getUserId(req);
214
- const dbConnection = await config.getDb();
215
- const db = dbConnection.db();
216
- const blogs = db.collection(config.collectionName || 'blogs');
217
-
218
- const blog = await blogs.findOne({ slug });
219
-
220
- if (!blog) {
221
- return NextResponse.json({ error: 'Blog not found' }, { status: 404 });
222
- }
223
-
224
- // Security check
225
- const isPublished = blog.publicationData?.status === 'published';
226
- const isAuthor = userId && blog.authorId === userId;
227
-
228
- if (!isPublished && !isAuthor) {
229
- return NextResponse.json({ error: 'Access denied' }, { status: 403 });
230
- }
231
-
232
- return NextResponse.json({
233
- ...blog,
234
- _id: blog._id.toString(),
235
- });
236
- } catch (err: any) {
237
- console.error('[BlogAPI] GET_BY_SLUG error:', err);
238
- return NextResponse.json(
239
- { error: 'Failed to fetch blog', detail: err.message },
240
- { status: 500 }
241
- );
242
- }
243
- }
244
-
245
- /**
246
- * PUT /api/blogs/[slug] - Update blog post by slug
247
- */
248
- export async function PUT_BY_SLUG(
249
- req: NextRequest,
250
- slug: string,
251
- config: BlogApiConfig
252
- ): Promise<NextResponse> {
253
- try {
254
- const userId = await config.getUserId(req);
255
- if (!userId) {
256
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
257
- }
258
-
259
- const body = await req.json();
260
- const {
261
- title,
262
- summary,
263
- content,
264
- contentBlocks,
265
- image,
266
- categoryTags,
267
- publicationData,
268
- seo,
269
- } = body;
270
-
271
- const dbConnection = await config.getDb();
272
- const db = dbConnection.db();
273
- const blogs = db.collection(config.collectionName || 'blogs');
274
-
275
- // Check if blog exists and user is author
276
- const existingBlog = await blogs.findOne({ slug });
277
- if (!existingBlog) {
278
- return NextResponse.json({ error: 'Blog not found' }, { status: 404 });
279
- }
280
- if (existingBlog.authorId !== userId) {
281
- return NextResponse.json({ error: 'Forbidden: Not the author' }, { status: 403 });
282
- }
283
-
284
- // Validation
285
- const isPublishing = publicationData?.status === 'published';
286
- if (!title?.trim()) {
287
- return NextResponse.json({ message: 'Title is required' }, { status: 400 });
288
- }
289
-
290
- if (isPublishing) {
291
- const hasContent =
292
- (contentBlocks && Array.isArray(contentBlocks) && contentBlocks.length > 0) ||
293
- (content && Array.isArray(content) && content.length > 0);
294
-
295
- // Collect all missing fields for better error messages
296
- const missingFields: string[] = [];
297
- if (!summary?.trim()) missingFields.push('summary');
298
- if (!image?.id?.trim()) missingFields.push('featured image');
299
- // Only require category if it's explicitly provided and empty
300
- // If categoryTags is undefined or category is undefined, that's also missing
301
- if (!categoryTags || !categoryTags.category || !categoryTags.category.trim()) {
302
- missingFields.push('category');
303
- }
304
- if (!hasContent) missingFields.push('content');
305
-
306
- if (missingFields.length > 0) {
307
- console.log('[BlogAPI] PUT_BY_SLUG validation failed:', {
308
- isPublishing,
309
- missingFields,
310
- summary: summary?.trim() || 'missing',
311
- imageId: image?.id?.trim() || 'missing',
312
- category: categoryTags?.category?.trim() || 'missing',
313
- hasContent,
314
- contentBlocksLength: contentBlocks?.length || 0,
315
- contentLength: content?.length || 0,
316
- });
317
- return NextResponse.json(
318
- {
319
- message: `Missing required fields for publishing: ${missingFields.join(', ')}`,
320
- missingFields
321
- },
322
- { status: 400 }
323
- );
324
- }
325
- }
326
-
327
- // Slug logic
328
- let finalSlug = slug;
329
- if (isPublishing) {
330
- finalSlug = slugify(title);
331
- } else if (publicationData?.status === 'concept' && !slug.includes('-draft-')) {
332
- finalSlug = `${slugify(title)}-draft-${Date.now().toString().slice(-4)}`;
333
- }
334
-
335
- // Update data
336
- // Determine the final status: if publishing, set to 'published', otherwise preserve or convert draft to concept
337
- let finalStatus = publicationData?.status;
338
- if (isPublishing) {
339
- finalStatus = 'published';
340
- } else if (publicationData?.status === 'draft') {
341
- finalStatus = 'concept';
342
- }
343
-
344
- const updateData = {
345
- title: title.trim(),
346
- summary: (summary || '').trim(),
347
- contentBlocks: contentBlocks || [],
348
- content: content || [],
349
- image: image || {},
350
- categoryTags: {
351
- category: categoryTags?.category?.trim() || '',
352
- tags: categoryTags?.tags || [],
353
- },
354
- publicationData: {
355
- ...publicationData,
356
- status: finalStatus,
357
- date: publicationData?.date ? new Date(publicationData.date) : new Date(),
358
- },
359
- seo: seo || {},
360
- slug: finalSlug,
361
- authorId: userId,
362
- updatedAt: new Date(),
363
- };
364
-
365
- await blogs.updateOne({ slug }, { $set: updateData });
366
-
367
- return NextResponse.json({
368
- message: 'Blog updated successfully',
369
- slug: finalSlug,
370
- });
371
- } catch (err: any) {
372
- console.error('[BlogAPI] PUT_BY_SLUG error:', err);
373
- return NextResponse.json(
374
- { error: 'Failed to update blog', detail: err.message },
375
- { status: 500 }
376
- );
377
- }
378
- }
379
-
380
- /**
381
- * DELETE /api/blogs/[slug] - Delete blog post by slug
382
- */
383
- export async function DELETE_BY_SLUG(
384
- req: NextRequest,
385
- slug: string,
386
- config: BlogApiConfig
387
- ): Promise<NextResponse> {
388
- try {
389
- const userId = await config.getUserId(req);
390
- if (!userId) {
391
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
392
- }
393
-
394
- const dbConnection = await config.getDb();
395
- const db = dbConnection.db();
396
- const blogs = db.collection(config.collectionName || 'blogs');
397
-
398
- // Verify ownership
399
- const blog = await blogs.findOne({ slug });
400
- if (!blog) {
401
- return NextResponse.json({ error: 'Blog not found' }, { status: 404 });
402
- }
403
- if (blog.authorId !== userId) {
404
- return NextResponse.json({ error: 'Forbidden: Not the author' }, { status: 403 });
405
- }
406
-
407
- await blogs.deleteOne({ slug });
408
-
409
- return NextResponse.json({ message: 'Blog deleted successfully' });
410
- } catch (err: any) {
411
- console.error('[BlogAPI] DELETE_BY_SLUG error:', err);
412
- return NextResponse.json(
413
- { error: 'Failed to delete blog', detail: err.message },
414
- { status: 500 }
415
- );
416
- }
417
- }
418
-
package/src/api/index.ts DELETED
@@ -1,33 +0,0 @@
1
- /**
2
- * Blog API Exports
3
- * RESTful API handlers for blog posts
4
- */
5
-
6
- export {
7
- GET,
8
- POST,
9
- PUT,
10
- DELETE,
11
- createBlogApiConfig,
12
- } from './route';
13
-
14
- export type { BlogApiConfig } from './handler';
15
-
16
- export {
17
- GET as GET_HANDLER,
18
- POST as POST_HANDLER,
19
- GET_BY_SLUG,
20
- PUT_BY_SLUG,
21
- DELETE_BY_SLUG,
22
- } from './handler';
23
-
24
- // Router exports
25
- export { handleBlogApi } from './router';
26
- export type { BlogApiRouterConfig } from './router';
27
-
28
- // Categories handler
29
- export { GET as GET_CATEGORIES } from './categories';
30
-
31
- // Check title handler
32
- export { GET as GET_CHECK_TITLE } from './check-title';
33
-
package/src/api/route.ts DELETED
@@ -1,116 +0,0 @@
1
- /**
2
- * Blog API Route Handler
3
- * Server-only wrapper for the blog API handlers
4
- * This file should ONLY be used in 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.
8
- */
9
-
10
- import { NextRequest, NextResponse } from 'next/server';
11
- import {
12
- GET as handlerGET,
13
- POST as handlerPOST,
14
- GET_BY_SLUG,
15
- PUT_BY_SLUG,
16
- DELETE_BY_SLUG,
17
- BlogApiConfig,
18
- } from './handler';
19
-
20
- /**
21
- * Default configuration factory
22
- * Client apps should provide their own config with MongoDB connection and auth
23
- */
24
- export function createBlogApiConfig(config: BlogApiConfig): BlogApiConfig {
25
- return config;
26
- }
27
-
28
- /**
29
- * GET handler - List all blogs or get by slug
30
- * Usage:
31
- * GET /api/blogs - List all
32
- * GET /api/blogs/[slug] - Get by slug
33
- */
34
- export async function GET(
35
- req: NextRequest,
36
- context?: { params?: Promise<{ slug?: string }> },
37
- config?: BlogApiConfig
38
- ): Promise<NextResponse> {
39
- if (!config) {
40
- return NextResponse.json(
41
- { error: 'Blog API config not provided' },
42
- { status: 500 }
43
- );
44
- }
45
-
46
- // Check if we have a slug parameter (single blog request)
47
- if (context?.params) {
48
- const { slug } = await context.params;
49
- if (slug) {
50
- return GET_BY_SLUG(req, slug, config);
51
- }
52
- }
53
-
54
- // Otherwise, list all blogs
55
- return handlerGET(req, config);
56
- }
57
-
58
- /**
59
- * POST handler - Create new blog
60
- * Usage: POST /api/blogs
61
- */
62
- export async function POST(
63
- req: NextRequest,
64
- context?: any,
65
- config?: BlogApiConfig
66
- ): Promise<NextResponse> {
67
- if (!config) {
68
- return NextResponse.json(
69
- { error: 'Blog API config not provided' },
70
- { status: 500 }
71
- );
72
- }
73
-
74
- return handlerPOST(req, config);
75
- }
76
-
77
- /**
78
- * PUT handler - Update blog by slug
79
- * Usage: PUT /api/blogs/[slug]
80
- */
81
- export async function PUT(
82
- req: NextRequest,
83
- context: { params: Promise<{ slug: string }> },
84
- config?: BlogApiConfig
85
- ): Promise<NextResponse> {
86
- if (!config) {
87
- return NextResponse.json(
88
- { error: 'Blog API config not provided' },
89
- { status: 500 }
90
- );
91
- }
92
-
93
- const { slug } = await context.params;
94
- return PUT_BY_SLUG(req, slug, config);
95
- }
96
-
97
- /**
98
- * DELETE handler - Delete blog by slug
99
- * Usage: DELETE /api/blogs/[slug]
100
- */
101
- export async function DELETE(
102
- req: NextRequest,
103
- context: { params: Promise<{ slug: string }> },
104
- config?: BlogApiConfig
105
- ): Promise<NextResponse> {
106
- if (!config) {
107
- return NextResponse.json(
108
- { error: 'Blog API config not provided' },
109
- { status: 500 }
110
- );
111
- }
112
-
113
- const { slug } = await context.params;
114
- return DELETE_BY_SLUG(req, slug, config);
115
- }
116
-
package/src/api/router.ts DELETED
@@ -1,128 +0,0 @@
1
- 'use server';
2
-
3
- /**
4
- * Plugin Blog API Router
5
- * Centralized API handler for all blog plugin routes
6
- *
7
- * This router handles requests to /api/plugin-blog/*
8
- * and routes them to the appropriate handler
9
- */
10
-
11
- import { NextRequest, NextResponse } from 'next/server';
12
- import { GET as BlogListHandler, POST as BlogCreateHandler } from './handler';
13
- import { GET as BlogGetHandler, PUT as BlogUpdateHandler, DELETE as BlogDeleteHandler, createBlogApiConfig } from './route';
14
- import { GET as ConfigGetHandler, POST as ConfigPostHandler } from './config-handler';
15
-
16
- export interface BlogApiRouterConfig {
17
- /** MongoDB client promise - should return { db: () => Database } */
18
- getDb: () => Promise<{ db: () => any }>;
19
- /** Function to get authenticated user ID from request */
20
- getUserId: (req: NextRequest) => Promise<string | null>;
21
- /** Collection name (default: 'blogs') */
22
- collectionName?: string;
23
- /** Site ID for multi-site setups */
24
- siteId?: string;
25
- }
26
-
27
- /**
28
- * Handle blog API requests
29
- * Routes requests to appropriate handlers based on path
30
- * Similar to plugin-dep, accepts config directly instead of requiring initialization
31
- */
32
- export async function handleBlogApi(
33
- req: NextRequest,
34
- path: string[],
35
- config: BlogApiRouterConfig
36
- ): Promise<NextResponse> {
37
- // Create the blog API config from the router config
38
- const blogApiConfig = createBlogApiConfig({
39
- getDb: config.getDb,
40
- getUserId: config.getUserId,
41
- collectionName: config.collectionName || 'blogs',
42
- });
43
-
44
- const method = req.method;
45
- // Handle empty path array - means we're at /api/plugin-blog
46
- // Ensure path is always an array
47
- const safePath = Array.isArray(path) ? path : [];
48
- const route = safePath.length > 0 ? safePath[0] : '';
49
-
50
- try {
51
- // Route: /api/plugin-blog (list/create) - empty path or 'list'
52
- // This handles both /api/plugin-blog and /api/plugin-blog?limit=3
53
- if (!route || route === 'list') {
54
- if (method === 'GET') {
55
- return await BlogListHandler(req, blogApiConfig);
56
- }
57
- if (method === 'POST') {
58
- return await BlogCreateHandler(req, blogApiConfig);
59
- }
60
- // Method not allowed for root route
61
- return NextResponse.json(
62
- { error: `Method ${method} not allowed for route: /` },
63
- { status: 405 }
64
- );
65
- }
66
- // Route: /api/plugin-blog/new (create new)
67
- else if (route === 'new') {
68
- if (method === 'POST') {
69
- return await BlogCreateHandler(req, blogApiConfig);
70
- }
71
- }
72
- // Route: /api/plugin-blog/categories (get categories)
73
- else if (route === 'categories') {
74
- if (method === 'GET') {
75
- // Import categories handler
76
- const categoriesModule = await import('./categories');
77
- return await categoriesModule.GET(req, blogApiConfig);
78
- }
79
- }
80
- // Route: /api/plugin-blog/check-title (check title duplicate)
81
- else if (route === 'check-title') {
82
- if (method === 'GET') {
83
- const checkTitleModule = await import('./check-title');
84
- return await checkTitleModule.GET(req, blogApiConfig);
85
- }
86
- }
87
- // Route: /api/plugin-blog/config (get/save plugin config)
88
- else if (route === 'config') {
89
- const configApiConfig = {
90
- getDb: config.getDb,
91
- getUserId: config.getUserId,
92
- siteId: config.siteId || 'default',
93
- };
94
- if (method === 'GET') {
95
- return await ConfigGetHandler(req, configApiConfig);
96
- }
97
- if (method === 'POST') {
98
- return await ConfigPostHandler(req, configApiConfig);
99
- }
100
- }
101
- // Route: /api/plugin-blog/[slug] (get/update/delete by slug)
102
- else {
103
- const slug = route;
104
- if (method === 'GET') {
105
- return await BlogGetHandler(req, { params: Promise.resolve({ slug }) }, blogApiConfig);
106
- }
107
- if (method === 'PUT') {
108
- return await BlogUpdateHandler(req, { params: Promise.resolve({ slug }) }, blogApiConfig);
109
- }
110
- if (method === 'DELETE') {
111
- return await BlogDeleteHandler(req, { params: Promise.resolve({ slug }) }, blogApiConfig);
112
- }
113
- }
114
-
115
- // Method not allowed
116
- return NextResponse.json(
117
- { error: `Method ${method} not allowed for route: ${route || '/'}` },
118
- { status: 405 }
119
- );
120
- } catch (error: any) {
121
- console.error('[BlogApiRouter] Error:', error);
122
- return NextResponse.json(
123
- { error: error.message || 'Internal server error' },
124
- { status: 500 }
125
- );
126
- }
127
- }
128
-