import { ZodError } from "zod"; import type { NextApiRequest, NextApiResponse } from "next"; import { currentAccount } from "@/lib/account"; import { trustedOrigin } from "@/lib/account-crypto"; import { documentInput, parseDocumentXml } from "@/lib/guidance-input"; import { discoverDocuments, publishDocument, resolveDocument, withdrawDocument, } from "@/lib/guidance-store"; import { guidanceXml } from "@/content/guides/catalog"; export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { res.setHeader("Cache-Control", "no-store"); const path = req.query.path || []; if (!Array.isArray(path) || path.length > 1) return res.status(404).end(); const id = path[0]; try { if (req.method === "GET") { if (!id) { const author = req.query.author; if ( author !== undefined && (typeof author !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( author, )) ) return res.status(400).json({ error: "作者 ID 无效" }); return res.json({ documents: await discoverDocuments(author) }); } const revision = req.query.revision; if ( revision !== undefined && (typeof revision !== "string" || !/^[1-9][0-9]{0,9}$/.test(revision) || Number(revision) > 2147483647) ) return res.status(400).json({ error: "版本无效" }); const doc = await resolveDocument( id, revision === undefined ? undefined : Number(revision), ); if (!doc) return res.status(404).json({ error: "文档或精确版本不存在" }); if (req.query.format === "xml") { res.setHeader("Content-Type", "application/xml; charset=utf-8"); return res.send(guidanceXml(doc)); } return res.json({ document: doc }); } if (!["POST", "DELETE"].includes(req.method || "")) { res.setHeader("Allow", "GET, POST, DELETE"); return res.status(405).end(); } const request = new Request( new URL( req.url || "/", process.env.ACCOUNT_PUBLIC_ORIGIN || "http://" + req.headers.host, ), { headers: new Headers( Object.entries(req.headers).filter( ([, v]) => typeof v === "string", ) as [string, string][], ), }, ); if (!trustedOrigin(request)) return res.status(403).json({ error: "请求来源不受信任" }); const user = await currentAccount(request); if (!user) return res.status(401).json({ error: "请先登录已验证邮箱账户" }); if (req.method === "DELETE") return id && (await withdrawDocument(user.id, id)) ? res.json({ ok: true }) : res.status(404).json({ error: "无权删除或不存在" }); const input = typeof req.body === "string" ? parseDocumentXml(req.body) : documentInput.parse(req.body); if (id === "preview") { const links = await Promise.all( input.links.map(async (l) => ({ ...l, resolved: !!(await resolveDocument(l.target, l.revision)), })), ); return res.json({ document: input, xml: guidanceXml(input), links }); } if (id && id !== input.id) return res.status(400).json({ error: "文档 ID 不匹配" }); return res .status(201) .json({ document: await publishDocument(user.id, input) }); } catch (e) { if (e instanceof ZodError) return res .status(400) .json({ error: "输入不符合协议,请检查以下字段", fields: e.issues.slice(0, 12).map((i) => i.path.join(".") || "文档"), }); const message = e instanceof Error ? e.message : ""; if (message === "INVALID_ID") return res .status(400) .json({ error: "文档 ID 必须以 user-你的账户ID- 开头,请使用编辑器生成的前缀", }); if (message === "FORBIDDEN") return res.status(403).json({ error: "仅作者可编辑" }); if (message === "CONFLICT" || (e as { code?: string }).code === "23505") return res .status(409) .json({ error: "版本冲突:请加载最新版本,已发布版本不可修改" }); if ( e instanceof Error && (e.name === "ZodError" || /XML|实体|元素|属性|协议|链接/.test(message)) ) return res.status(400).json({ error: "输入不符合协议" }); return res.status(503).json({ error: "文档服务暂不可用" }); } } export const config = { api: { bodyParser: { sizeLimit: "128kb" } } };