feat: 添加文章管理的 RESTful API 接口及 Token 认证机制
This commit is contained in:
1 parent
70db079379
commit
fe08cd27ff
3 files changed
+239
No files matched your search
@@ -23,6 +23,47 @@ function accessNotConfigured(): Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
|
if (event.url.pathname.startsWith('/api/v1/')) {
|
||||||
|
const authHeader = event.request.headers.get('Authorization');
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||||
|
return new Response(JSON.stringify({ error: 'Unauthorized: Missing or invalid token' }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.slice('Bearer '.length).trim();
|
||||||
|
const db = event.platform?.env.DB;
|
||||||
|
if (!db) {
|
||||||
|
return new Response(JSON.stringify({ error: 'Database not available' }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await db
|
||||||
|
.prepare("SELECT value FROM settings WHERE key = 'agent.api_token'")
|
||||||
|
.first<{ value: string }>();
|
||||||
|
|
||||||
|
const validToken = result?.value;
|
||||||
|
if (!validToken || token !== validToken) {
|
||||||
|
return new Response(JSON.stringify({ error: 'Unauthorized: Invalid token' }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('API token verification failed', err);
|
||||||
|
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(event);
|
||||||
|
}
|
||||||
|
|
||||||
if (!event.url.pathname.startsWith('/admin')) {
|
if (!event.url.pathname.startsWith('/admin')) {
|
||||||
return resolve(event);
|
return resolve(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ platform, url }) => {
|
||||||
|
const db = platform?.env.DB;
|
||||||
|
if (!db) return json({ error: 'Database not connected' }, { status: 500 });
|
||||||
|
|
||||||
|
const limit = parseInt(url.searchParams.get('limit') || '10', 10);
|
||||||
|
const offset = parseInt(url.searchParams.get('offset') || '0', 10);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { results } = await db
|
||||||
|
.prepare(
|
||||||
|
`
|
||||||
|
SELECT p.id, p.slug, p.title, p.published_at, p.updated_at, c.slug as category_slug
|
||||||
|
FROM posts p
|
||||||
|
LEFT JOIN categories c ON p.category_id = c.id
|
||||||
|
ORDER BY p.published_at DESC
|
||||||
|
LIMIT ? OFFSET ?
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.bind(limit, offset)
|
||||||
|
.all();
|
||||||
|
|
||||||
|
const { results: countResult } = await db.prepare('SELECT COUNT(*) as total FROM posts').all();
|
||||||
|
const total = (countResult[0] as { total: number })?.total || 0;
|
||||||
|
|
||||||
|
return json({
|
||||||
|
data: results,
|
||||||
|
meta: {
|
||||||
|
total,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error('Failed to get posts', err);
|
||||||
|
return json({ error: 'Failed to retrieve posts' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ request, platform }) => {
|
||||||
|
const db = platform?.env.DB;
|
||||||
|
if (!db) return json({ error: 'Database not connected' }, { status: 500 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = (await request.json()) as Record<string, unknown>;
|
||||||
|
const { slug, title, content, category_slug } = data as {
|
||||||
|
slug?: string;
|
||||||
|
title?: string;
|
||||||
|
content?: string;
|
||||||
|
category_slug?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!slug || !title || !content) {
|
||||||
|
return json({ error: 'slug, title, and content are required' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get default admin user id (assuming id=1 for simplicity as a bot)
|
||||||
|
const authorId = 1;
|
||||||
|
|
||||||
|
let categoryId = null;
|
||||||
|
if (category_slug) {
|
||||||
|
const catResult = await db
|
||||||
|
.prepare('SELECT id FROM categories WHERE slug = ?')
|
||||||
|
.bind(category_slug)
|
||||||
|
.first<{ id: number }>();
|
||||||
|
if (catResult) categoryId = catResult.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db
|
||||||
|
.prepare(
|
||||||
|
`
|
||||||
|
INSERT INTO posts (slug, title, content, author_id, category_id, published_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.bind(slug, title, content, authorId, categoryId)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return json({ success: true, slug, title });
|
||||||
|
} else {
|
||||||
|
return json({ error: 'Failed to insert post' }, { status: 500 });
|
||||||
|
}
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error('Failed to create post', err);
|
||||||
|
// Duplicate slug error handling
|
||||||
|
if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
|
||||||
|
return json({ error: 'Slug already exists' }, { status: 409 });
|
||||||
|
}
|
||||||
|
return json({ error: 'Invalid request data or server error' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ params, platform }) => {
|
||||||
|
const db = platform?.env.DB;
|
||||||
|
if (!db) return json({ error: 'Database not connected' }, { status: 500 });
|
||||||
|
|
||||||
|
const { slug } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const post = await db
|
||||||
|
.prepare(
|
||||||
|
`
|
||||||
|
SELECT p.id, p.slug, p.title, p.content, p.published_at, p.updated_at, c.slug as category_slug
|
||||||
|
FROM posts p
|
||||||
|
LEFT JOIN categories c ON p.category_id = c.id
|
||||||
|
WHERE p.slug = ?
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.bind(slug)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return json({ error: 'Post not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return json({ data: post });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error('Failed to get post', err);
|
||||||
|
return json({ error: 'Failed to retrieve post' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PUT: RequestHandler = async ({ params, request, platform }) => {
|
||||||
|
const db = platform?.env.DB;
|
||||||
|
if (!db) return json({ error: 'Database not connected' }, { status: 500 });
|
||||||
|
|
||||||
|
const { slug } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = (await request.json()) as Record<string, unknown>;
|
||||||
|
const { title, content, category_slug } = data as {
|
||||||
|
title?: string;
|
||||||
|
content?: string;
|
||||||
|
category_slug?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!title && !content && !category_slug) {
|
||||||
|
return json({ error: 'No fields to update' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, check if post exists
|
||||||
|
const existingPost = await db
|
||||||
|
.prepare('SELECT id FROM posts WHERE slug = ?')
|
||||||
|
.bind(slug)
|
||||||
|
.first<{ id: number }>();
|
||||||
|
if (!existingPost) {
|
||||||
|
return json({ error: 'Post not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updates: string[] = [];
|
||||||
|
const bindings: unknown[] = [];
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
updates.push('title = ?');
|
||||||
|
bindings.push(title);
|
||||||
|
}
|
||||||
|
if (content) {
|
||||||
|
updates.push('content = ?');
|
||||||
|
bindings.push(content);
|
||||||
|
}
|
||||||
|
if (category_slug !== undefined) {
|
||||||
|
let categoryId = null;
|
||||||
|
if (category_slug) {
|
||||||
|
const catResult = await db
|
||||||
|
.prepare('SELECT id FROM categories WHERE slug = ?')
|
||||||
|
.bind(category_slug)
|
||||||
|
.first<{ id: number }>();
|
||||||
|
if (catResult) categoryId = catResult.id;
|
||||||
|
}
|
||||||
|
updates.push('category_id = ?');
|
||||||
|
bindings.push(categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.push('updated_at = CURRENT_TIMESTAMP');
|
||||||
|
bindings.push(slug); // for WHERE clause
|
||||||
|
|
||||||
|
const query = `UPDATE posts SET ${updates.join(', ')} WHERE slug = ?`;
|
||||||
|
|
||||||
|
const result = await db
|
||||||
|
.prepare(query)
|
||||||
|
.bind(...bindings)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return json({ success: true, slug });
|
||||||
|
} else {
|
||||||
|
return json({ error: 'Failed to update post' }, { status: 500 });
|
||||||
|
}
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error('Failed to update post', err);
|
||||||
|
return json({ error: 'Invalid request data or server error' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in new issue
Block a user