Files
2026-10-08 11:09:37 +08:00

78 lines
2.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from "next/server";
import { ok, created, withErrorHandling, parseJsonBody } from "@/lib/api/response";
import { createPostSchema, listPostsQuerySchema } from "@/lib/validation/post";
import { createPost, listPosts } from "@/lib/posts/repository";
/**
* GET /api/posts —— 文章列表
*
* 查询参数:
* - limit 1–100,默认 20
* - offset 默认 0
* - tag 按标签过滤
* - status draft | published(不传则只返回已发布)
* - includeDrafts true 时包含草稿(内部/管理用途)
*
* 响应:{ data: PostMeta[], meta: { total, limit, offset, hasMore } }
*/
export const GET = withErrorHandling(async (request: Request) => {
const { searchParams } = new URL(request.url);
// 校验查询参数:非法值直接返回 400 + 字段级错误
const query = listPostsQuerySchema.parse(
Object.fromEntries(searchParams.entries()),
);
const result = await listPosts({
limit: query.limit,
offset: query.offset,
tag: query.tag,
includeDrafts: query.includeDrafts,
status: query.status,
});
return ok(result.items, {
total: result.total,
limit: result.limit,
offset: result.offset,
hasMore: result.hasMore,
});
});
/**
* POST /api/posts —— 新建文章
*
* 请求体(JSON):
* - slug 必填,^[a-z0-9]+(-[a-z0-9]+)*$
* - title 必填
* - summary 必填
* - content 必填(Markdown / MDX 源码)
* - tags 可选,字符串数组
* - status draft | published,默认 draft
* - publishedAt 可选,ISO 日期;status=published 时缺省为当前时间
* - cover 可选
* - description 可选,覆盖 SEO 描述
*
* 响应:201 { data: Post }
*/
export const POST = withErrorHandling(async (request: Request) => {
const body = await parseJsonBody(request);
const input = createPostSchema.parse(body);
// slug 唯一性在应用层先查一次,给出友好的字段级报错;
// 数据库唯一索引仍作为最终防线(并发下可能拦截,返回 409)
const post = await createPost(input);
return created(post);
});
/** 其他方法统一返回 405 */
export function OPTIONS() {
return new NextResponse(null, {
status: 204,
headers: {
Allow: "GET, POST, OPTIONS",
},
});
}