feat: 添加文章管理的 RESTful API 接口及 Token 认证机制

This commit is contained in:
seahi committed 2026-09-21 19:40:57 +08:00
1 parent 70db079379
commit fe08cd27ff
3 files changed
+239

No files matched your search

+41
View File
@@ -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);
} }
+94
View File
@@ -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 });
}
};
+104
View File
@@ -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 });
}
};