|
|
|
@@ -27,12 +27,24 @@ import {
|
|
|
|
|
generatePostFilePath,
|
|
|
|
|
generateUniqueFilename,
|
|
|
|
|
validateUpload,
|
|
|
|
|
getFileExtension,
|
|
|
|
|
FILE_TYPE_PRESETS,
|
|
|
|
|
FILE_SIZE_LIMITS
|
|
|
|
|
} from '@zen/core/storage';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extension → MIME type map derived from the validated file extension.
|
|
|
|
|
* The client-supplied file.type is NEVER trusted — it is an attacker-controlled
|
|
|
|
|
* multipart field with no server-side enforcement.
|
|
|
|
|
*/
|
|
|
|
|
const EXTENSION_TO_MIME = {
|
|
|
|
|
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
|
|
|
'.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
import { getPostsConfig, getPostType } from './config.js';
|
|
|
|
|
import { fail, warn } from '../../shared/lib/logger.js';
|
|
|
|
|
import { defineApiRoutes, apiSuccess, apiError } from '@zen/core/api';
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Config
|
|
|
|
@@ -41,9 +53,10 @@ import { fail, warn } from '../../shared/lib/logger.js';
|
|
|
|
|
async function handleGetConfig() {
|
|
|
|
|
try {
|
|
|
|
|
const config = getPostsConfig();
|
|
|
|
|
return { success: true, config };
|
|
|
|
|
return apiSuccess({ success: true, config });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return { success: false, error: error.message || 'Failed to get config' };
|
|
|
|
|
fail(`Posts: error getting config: ${error.message}`);
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to get config');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -55,14 +68,14 @@ async function handleGetPosts(request) {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!getPostType(postType)) return { success: false, error: `Unknown post type: ${postType}` };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!getPostType(postType)) return apiError('Bad Request', `Unknown post type: ${postType}`);
|
|
|
|
|
|
|
|
|
|
const id = url.searchParams.get('id');
|
|
|
|
|
if (id) {
|
|
|
|
|
const post = await getPostById(postType, parseInt(id));
|
|
|
|
|
if (!post) return { success: false, error: 'Post not found' };
|
|
|
|
|
return { success: true, post };
|
|
|
|
|
if (!post) return apiError('Not Found', 'Post not found');
|
|
|
|
|
return apiSuccess({ success: true, post });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const page = parseInt(url.searchParams.get('page')) || 1;
|
|
|
|
@@ -83,17 +96,17 @@ async function handleGetPosts(request) {
|
|
|
|
|
withRelations
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
return apiSuccess({
|
|
|
|
|
success: true,
|
|
|
|
|
posts: result.posts,
|
|
|
|
|
total: result.pagination.total,
|
|
|
|
|
totalPages: result.pagination.totalPages,
|
|
|
|
|
page: result.pagination.page,
|
|
|
|
|
limit: result.pagination.limit
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error GET posts: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to fetch posts' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to fetch posts');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -101,19 +114,19 @@ async function handleCreatePost(request) {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
|
|
|
|
|
const body = await request.json();
|
|
|
|
|
const postData = body.post || body;
|
|
|
|
|
if (!postData || Object.keys(postData).length === 0) {
|
|
|
|
|
return { success: false, error: 'Post data is required' };
|
|
|
|
|
return apiError('Bad Request', 'Post data is required');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const post = await createPost(postType, postData);
|
|
|
|
|
return { success: true, post, message: 'Post created successfully' };
|
|
|
|
|
return apiSuccess({ success: true, post, message: 'Post created successfully' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error creating post: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to create post' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to create post');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -124,19 +137,18 @@ async function handleUpdatePost(request) {
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
const id = url.searchParams.get('id') || body.id;
|
|
|
|
|
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!id) return { success: false, error: 'Post ID is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!id) return apiError('Bad Request', 'Post ID is required');
|
|
|
|
|
|
|
|
|
|
const updates = body.post || (({ id: _i, ...rest }) => rest)(body);
|
|
|
|
|
if (!updates || Object.keys(updates).length === 0) {
|
|
|
|
|
return { success: false, error: 'Update data is required' };
|
|
|
|
|
return apiError('Bad Request', 'Update data is required');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const typeConfig = getPostType(postType);
|
|
|
|
|
|
|
|
|
|
// Handle old image cleanup
|
|
|
|
|
const existing = await getPostById(postType, parseInt(id));
|
|
|
|
|
if (!existing) return { success: false, error: 'Post not found' };
|
|
|
|
|
if (!existing) return apiError('Not Found', 'Post not found');
|
|
|
|
|
|
|
|
|
|
const post = await updatePost(postType, parseInt(id), updates);
|
|
|
|
|
|
|
|
|
@@ -155,10 +167,10 @@ async function handleUpdatePost(request) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { success: true, post, message: 'Post updated successfully' };
|
|
|
|
|
return apiSuccess({ success: true, post, message: 'Post updated successfully' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error updating post: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to update post' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to update post');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -168,15 +180,15 @@ async function handleDeletePost(request) {
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
const id = url.searchParams.get('id');
|
|
|
|
|
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!id) return { success: false, error: 'Post ID is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!id) return apiError('Bad Request', 'Post ID is required');
|
|
|
|
|
|
|
|
|
|
const deleted = await deletePost(postType, parseInt(id));
|
|
|
|
|
if (!deleted) return { success: false, error: 'Post not found' };
|
|
|
|
|
return { success: true, message: 'Post deleted successfully' };
|
|
|
|
|
if (!deleted) return apiError('Not Found', 'Post not found');
|
|
|
|
|
return apiSuccess({ success: true, message: 'Post deleted successfully' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error deleting post: ${error.message}`);
|
|
|
|
|
return { success: false, error: 'Failed to delete post' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to delete post');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -190,40 +202,50 @@ async function handleUploadImage(request) {
|
|
|
|
|
const file = formData.get('file');
|
|
|
|
|
const postType = formData.get('type');
|
|
|
|
|
|
|
|
|
|
if (!file) return { success: false, error: 'No file provided' };
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!getPostType(postType)) return { success: false, error: `Unknown post type: ${postType}` };
|
|
|
|
|
if (!file) return apiError('Bad Request', 'No file provided');
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!getPostType(postType)) return apiError('Bad Request', `Unknown post type: ${postType}`);
|
|
|
|
|
|
|
|
|
|
// Read the buffer before validation so both magic-byte assertion and
|
|
|
|
|
// dangerous-pattern inspection (HTML/SVG/XML denylist) are executed
|
|
|
|
|
// against actual file content, not merely the client-supplied metadata.
|
|
|
|
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
|
|
|
|
|
|
|
|
const validation = validateUpload({
|
|
|
|
|
filename: file.name,
|
|
|
|
|
size: file.size,
|
|
|
|
|
allowedTypes: FILE_TYPE_PRESETS.IMAGES,
|
|
|
|
|
maxSize: FILE_SIZE_LIMITS.IMAGE
|
|
|
|
|
maxSize: FILE_SIZE_LIMITS.IMAGE,
|
|
|
|
|
buffer,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!validation.valid) {
|
|
|
|
|
return { success: false, error: validation.errors.join(', ') };
|
|
|
|
|
return apiError('Bad Request', validation.errors.join(', '));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uniqueFilename = generateUniqueFilename(file.name);
|
|
|
|
|
const key = generatePostFilePath(postType, Date.now(), uniqueFilename);
|
|
|
|
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
|
|
|
|
|
|
|
|
// Derive content-type from the validated extension — never from file.type,
|
|
|
|
|
// which is fully attacker-controlled.
|
|
|
|
|
const ext = getFileExtension(file.name).toLowerCase();
|
|
|
|
|
const contentType = EXTENSION_TO_MIME[ext] ?? 'application/octet-stream';
|
|
|
|
|
|
|
|
|
|
const uploadResult = await uploadImage({
|
|
|
|
|
key,
|
|
|
|
|
body: buffer,
|
|
|
|
|
contentType: file.type,
|
|
|
|
|
contentType,
|
|
|
|
|
metadata: { originalName: file.name }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!uploadResult.success) {
|
|
|
|
|
return { success: false, error: uploadResult.error || 'Upload failed' };
|
|
|
|
|
return apiError('Internal Server Error', 'Upload failed');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { success: true, key: uploadResult.data.key };
|
|
|
|
|
return apiSuccess({ success: true, key: uploadResult.data.key });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error uploading image: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Upload failed' };
|
|
|
|
|
return apiError('Internal Server Error', 'Upload failed');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -235,13 +257,13 @@ async function handleGetCategories(request) {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
|
|
|
|
|
const id = url.searchParams.get('id');
|
|
|
|
|
if (id) {
|
|
|
|
|
const category = await getCategoryById(postType, parseInt(id));
|
|
|
|
|
if (!category) return { success: false, error: 'Category not found' };
|
|
|
|
|
return { success: true, category };
|
|
|
|
|
if (!category) return apiError('Not Found', 'Category not found');
|
|
|
|
|
return apiSuccess({ success: true, category });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const page = parseInt(url.searchParams.get('page')) || 1;
|
|
|
|
@@ -260,17 +282,17 @@ async function handleGetCategories(request) {
|
|
|
|
|
sortOrder
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
return apiSuccess({
|
|
|
|
|
success: true,
|
|
|
|
|
categories: result.categories,
|
|
|
|
|
total: result.pagination.total,
|
|
|
|
|
totalPages: result.pagination.totalPages,
|
|
|
|
|
page: result.pagination.page,
|
|
|
|
|
limit: result.pagination.limit
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error GET categories: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to fetch categories' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to fetch categories');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -278,19 +300,19 @@ async function handleCreateCategory(request) {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
|
|
|
|
|
const body = await request.json();
|
|
|
|
|
const categoryData = body.category || body;
|
|
|
|
|
if (!categoryData || Object.keys(categoryData).length === 0) {
|
|
|
|
|
return { success: false, error: 'Category data is required' };
|
|
|
|
|
return apiError('Bad Request', 'Category data is required');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const category = await createCategory(postType, categoryData);
|
|
|
|
|
return { success: true, category, message: 'Category created successfully' };
|
|
|
|
|
return apiSuccess({ success: true, category, message: 'Category created successfully' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error creating category: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to create category' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to create category');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -301,22 +323,22 @@ async function handleUpdateCategory(request) {
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
const id = url.searchParams.get('id') || body.id;
|
|
|
|
|
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!id) return { success: false, error: 'Category ID is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!id) return apiError('Bad Request', 'Category ID is required');
|
|
|
|
|
|
|
|
|
|
const updates = body.category || (({ id: _i, ...rest }) => rest)(body);
|
|
|
|
|
if (!updates || Object.keys(updates).length === 0) {
|
|
|
|
|
return { success: false, error: 'Update data is required' };
|
|
|
|
|
return apiError('Bad Request', 'Update data is required');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = await getCategoryById(postType, parseInt(id));
|
|
|
|
|
if (!existing) return { success: false, error: 'Category not found' };
|
|
|
|
|
if (!existing) return apiError('Not Found', 'Category not found');
|
|
|
|
|
|
|
|
|
|
const category = await updateCategory(postType, parseInt(id), updates);
|
|
|
|
|
return { success: true, category, message: 'Category updated successfully' };
|
|
|
|
|
return apiSuccess({ success: true, category, message: 'Category updated successfully' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error updating category: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to update category' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to update category');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -326,17 +348,17 @@ async function handleDeleteCategory(request) {
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
const id = url.searchParams.get('id');
|
|
|
|
|
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!id) return { success: false, error: 'Category ID is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!id) return apiError('Bad Request', 'Category ID is required');
|
|
|
|
|
|
|
|
|
|
await deleteCategory(postType, parseInt(id));
|
|
|
|
|
return { success: true, message: 'Category deleted successfully' };
|
|
|
|
|
return apiSuccess({ success: true, message: 'Category deleted successfully' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error deleting category: ${error.message}`);
|
|
|
|
|
if (error.message.includes('Cannot delete')) {
|
|
|
|
|
return { success: false, error: error.message };
|
|
|
|
|
return apiError('Bad Request', error.message);
|
|
|
|
|
}
|
|
|
|
|
return { success: false, error: 'Failed to delete category' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to delete category');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -348,17 +370,17 @@ async function handleSearchPosts(request) {
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
const postType = url.searchParams.get('type');
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!getPostType(postType)) return { success: false, error: `Unknown post type: ${postType}` };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!getPostType(postType)) return apiError('Bad Request', `Unknown post type: ${postType}`);
|
|
|
|
|
|
|
|
|
|
const q = url.searchParams.get('q') || '';
|
|
|
|
|
const limit = parseInt(url.searchParams.get('limit')) || 20;
|
|
|
|
|
|
|
|
|
|
const posts = await searchPosts(postType, q, limit);
|
|
|
|
|
return { success: true, posts };
|
|
|
|
|
return apiSuccess({ success: true, posts });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error searching posts: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to search posts' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to search posts');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -369,17 +391,18 @@ async function handleSearchPosts(request) {
|
|
|
|
|
async function handlePublicGetConfig() {
|
|
|
|
|
try {
|
|
|
|
|
const config = getPostsConfig();
|
|
|
|
|
return { success: true, config };
|
|
|
|
|
return apiSuccess({ success: true, config });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return { success: false, error: error.message || 'Failed to get config' };
|
|
|
|
|
fail(`Posts: error getting public config: ${error.message}`);
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to get config');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handlePublicGetPosts(request, params) {
|
|
|
|
|
try {
|
|
|
|
|
const postType = params?.type;
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!getPostType(postType)) return { success: false, error: `Unknown post type: ${postType}` };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
if (!getPostType(postType)) return apiError('Bad Request', `Unknown post type: ${postType}`);
|
|
|
|
|
|
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
const page = parseInt(url.searchParams.get('page')) || 1;
|
|
|
|
@@ -398,17 +421,17 @@ async function handlePublicGetPosts(request, params) {
|
|
|
|
|
withRelations
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
return apiSuccess({
|
|
|
|
|
success: true,
|
|
|
|
|
posts: result.posts,
|
|
|
|
|
total: result.pagination.total,
|
|
|
|
|
totalPages: result.pagination.totalPages,
|
|
|
|
|
page: result.pagination.page,
|
|
|
|
|
limit: result.pagination.limit
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error public GET posts: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to fetch posts' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to fetch posts');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -416,27 +439,27 @@ async function handlePublicGetPostBySlug(request, params) {
|
|
|
|
|
try {
|
|
|
|
|
const postType = params?.type;
|
|
|
|
|
const slug = params?.slug;
|
|
|
|
|
if (!postType || !slug) return { success: false, error: 'Post type and slug are required' };
|
|
|
|
|
if (!postType || !slug) return apiError('Bad Request', 'Post type and slug are required');
|
|
|
|
|
|
|
|
|
|
const post = await getPostBySlug(postType, slug);
|
|
|
|
|
if (!post) return { success: false, error: 'Post not found' };
|
|
|
|
|
return { success: true, post };
|
|
|
|
|
if (!post) return apiError('Not Found', 'Post not found');
|
|
|
|
|
return apiSuccess({ success: true, post });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error public GET post by slug: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to fetch post' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to fetch post');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handlePublicGetCategories(request, params) {
|
|
|
|
|
try {
|
|
|
|
|
const postType = params?.type;
|
|
|
|
|
if (!postType) return { success: false, error: 'Post type is required' };
|
|
|
|
|
if (!postType) return apiError('Bad Request', 'Post type is required');
|
|
|
|
|
|
|
|
|
|
const categories = await getActiveCategories(postType);
|
|
|
|
|
return { success: true, categories };
|
|
|
|
|
return apiSuccess({ success: true, categories });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
fail(`Posts: error public GET categories: ${error.message}`);
|
|
|
|
|
return { success: false, error: error.message || 'Failed to fetch categories' };
|
|
|
|
|
return apiError('Internal Server Error', 'Failed to fetch categories');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -445,15 +468,15 @@ async function handlePublicGetCategories(request, params) {
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
routes: [
|
|
|
|
|
routes: defineApiRoutes([
|
|
|
|
|
// Admin config
|
|
|
|
|
{ path: '/admin/posts/config', method: 'GET', handler: handleGetConfig, auth: 'admin' },
|
|
|
|
|
|
|
|
|
|
// Admin posts
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'GET', handler: handleGetPosts, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'POST', handler: handleCreatePost, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'PUT', handler: handleUpdatePost, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'DELETE', handler: handleDeletePost, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'GET', handler: handleGetPosts, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'POST', handler: handleCreatePost, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'PUT', handler: handleUpdatePost, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/posts', method: 'DELETE', handler: handleDeletePost, auth: 'admin' },
|
|
|
|
|
|
|
|
|
|
// Admin image upload
|
|
|
|
|
{ path: '/admin/posts/upload-image', method: 'POST', handler: handleUploadImage, auth: 'admin' },
|
|
|
|
@@ -462,15 +485,15 @@ export default {
|
|
|
|
|
{ path: '/admin/posts/search', method: 'GET', handler: handleSearchPosts, auth: 'admin' },
|
|
|
|
|
|
|
|
|
|
// Admin categories
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'GET', handler: handleGetCategories, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'POST', handler: handleCreateCategory, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'PUT', handler: handleUpdateCategory, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'DELETE', handler: handleDeleteCategory, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'GET', handler: handleGetCategories, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'POST', handler: handleCreateCategory, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'PUT', handler: handleUpdateCategory, auth: 'admin' },
|
|
|
|
|
{ path: '/admin/posts/categories', method: 'DELETE', handler: handleDeleteCategory, auth: 'admin' },
|
|
|
|
|
|
|
|
|
|
// Public
|
|
|
|
|
{ path: '/posts/config', method: 'GET', handler: handlePublicGetConfig, auth: 'public' },
|
|
|
|
|
{ path: '/posts/:type', method: 'GET', handler: handlePublicGetPosts, auth: 'public' },
|
|
|
|
|
{ path: '/posts/:type/:slug', method: 'GET', handler: handlePublicGetPostBySlug, auth: 'public' },
|
|
|
|
|
{ path: '/posts/config', method: 'GET', handler: handlePublicGetConfig, auth: 'public' },
|
|
|
|
|
{ path: '/posts/:type', method: 'GET', handler: handlePublicGetPosts, auth: 'public' },
|
|
|
|
|
{ path: '/posts/:type/:slug', method: 'GET', handler: handlePublicGetPostBySlug, auth: 'public' },
|
|
|
|
|
{ path: '/posts/:type/categories', method: 'GET', handler: handlePublicGetCategories, auth: 'public' },
|
|
|
|
|
]
|
|
|
|
|
])
|
|
|
|
|
};
|
|
|
|
|