import { NextResponse } from "next/server"; import { ok, noContent, withErrorHandling, parseJsonBody, ApiError, } from "@/lib/api/response"; import { updatePostSchema } from "@/lib/validation/post"; import { getPostBySlug, getPostBySlugIncludingDrafts, updatePost, deletePost, assertSlugAvailable, } from "@/lib/posts/repository"; /** * /api/posts/[slug] —— 单篇文章的读取、更新与删除 * * 注意:GET 默认只返回已发布文章;传入 ?includeDrafts=true 可读到草稿, * 供后续接入后台管理时使用(v0.1 尚无鉴权,接入后台前应加上权限校验)。 */ type RouteContext = { params: Promise<{ slug: string }> }; /** GET /api/posts/[slug] —— 读取单篇文章(含正文) */ export const GET = withErrorHandling( async (request: Request, context: RouteContext) => { const { slug } = await context.params; const { searchParams } = new URL(request.url); const includeDrafts = searchParams.get("includeDrafts") === "true"; const post = includeDrafts ? await getPostBySlugIncludingDrafts(slug) : await getPostBySlug(slug); if (!post) { throw ApiError.notFound(`文章 "${slug}" 不存在或尚未发布`); } return ok(post); }, ); /** * PATCH /api/posts/[slug] —— 更新文章(部分更新) * * 支持更新 slug 本身;此时会校验新 slug 未被占用。 */ export const PATCH = withErrorHandling( async (request: Request, context: RouteContext) => { const { slug } = await context.params; const body = await parseJsonBody(request); const input = updatePostSchema.parse(body); // 目标文章必须存在(含草稿,便于把草稿改为已发布) const existing = await getPostBySlugIncludingDrafts(slug); if (!existing) { throw ApiError.notFound(`文章 "${slug}" 不存在`); } // 改 slug 时需要判重(排除自身) if (input.slug && input.slug !== slug) { await assertSlugAvailable(input.slug, slug); } const updated = await updatePost(slug, input); if (!updated) { throw ApiError.notFound(`文章 "${slug}" 不存在`); } return ok(updated); }, ); /** PUT 与 PATCH 同义,便于不支持 PATCH 的客户端使用 */ export const PUT = PATCH; /** DELETE /api/posts/[slug] —— 删除文章 */ export const DELETE = withErrorHandling( async (_request: Request, context: RouteContext) => { const { slug } = await context.params; const removed = await deletePost(slug); if (!removed) { throw ApiError.notFound(`文章 "${slug}" 不存在`); } // 204 No Content:删除成功且无响应体 return noContent(); }, ); /** 列出该资源支持的方法 */ export function OPTIONS() { return new NextResponse(null, { status: 204, headers: { Allow: "GET, PATCH, PUT, DELETE, OPTIONS" }, }); }