Files
probably-rendering-one-more…/apps/wiki/src/lib/api-documentation.ts
T
codex-bot 38ec492ca3
wiki CI and deployment / verify-and-deploy (push) Successful in 16m28s
Publish reference layouts as API-authored text and image notes
2026-10-26 17:20:57 +08:00

102 lines
8.4 KiB
TypeScript
Raw 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 type {NextApiRequest,NextApiResponse} from 'next';
// Read documentation before business validation or storage access. Mutations never enter this branch.
export const apiDocuments:Record<string,readonly string[]>={
"/api/explore-feed": [
"统一内容流",
"GET",
"查询参数:q(可选,最多 200 字符);category(可选,标签名称或命名空间);cursor(可选,上一页 nextCursor)。",
"JSON:entries 数组与 nextCursor;没有更多结果时 nextCursor 为 null。静态内容仅出现在第一页;已由相同 publicId 的数据库笔记接管时不重复展示。",
"GET /api/explore-feed?q=排版",
"503:素材库暂时无法连接。"
],
"/api/feed": [
"素材列表",
"GET",
"查询参数:section(images / videos / audio / articles / motion,可选);path(可选,标签 path);q(可选,最多 200 字符);cursor(可选,上一页 nextCursor)。",
"JSON:posts、tags、contributions、nextCursor;每页最多 24 个 post。",
"GET /api/feed?section=images",
"400:未知 section、标签或无效游标。"
],
"/api/notes/{id}": [
"读取与编辑笔记",
"GET, PUT",
"路径参数:id,笔记稳定 ID(publicId 或存储 UUID)。PUT 使用 application/json,以下字段必填:title(1–200 字符)、description(最多 1000 字符)、markdown(最多 100000 字符)、tags(最多 50 个标签 UUID)、revision(GET 返回的 entry.revision,64 位十六进制)。可选 assets(最多 100 项 {id:媒体 UUID,role:content|caption},替换媒体关联,原二进制不删除);coverAssetId(图片 UUID 或 null,必须为本笔记 content 图片;null 清除显式封面)。静态代码页须先通过创建接口保存为数据库笔记,才可更新媒体。",
"GET 返回 {entry,tags},entry.assets 为媒体关联、entry.coverAssetId 为显式封面;PUT 返回更新后的 {entry}。先 GET 获取 revision,再 PUT;冲突后重新读取。",
"GET /api/notes/{id}\nPUT /api/notes/{id}\n{\"title\":\"标题\",\"description\":\"简介\",\"markdown\":\"正文\",\"tags\":[],\"revision\":\"<GET 返回的 entry.revision>\"}",
"400:输入错误;404:笔记不存在;409:版本冲突。"
],
"/api/posts": [
"创建笔记或素材包",
"POST",
"application/json:kind(必填,image / video / audio / article / motion)、title(必填,1–200 字符);publicId(可选,唯一的 24 位小写十六进制稳定 ID,用于保留原页面地址);slug(可选,最多 120 字符,以小写字母或数字开头,仅含小写字母、数字、连字符);description(可选,最多 1000 字符);text(可选,最多 100000 字符);tags(可选,最多 50 个 UUID);coverAssetId(可选,图片 UUID);assets(可选,最多 100 项,{id: UUID,role: content | caption},role 默认 content)。媒体类型必须引用至少一个对应类型的 content 素材。",
"201 JSON:{id,slug,url,publicId?},url 为 /explore/<publicId 或 id>。id 仍为存储 UUID。引用已有素材;不会在此接口上传二进制文件。",
"POST /api/posts\n{\"kind\":\"article\",\"title\":\"新笔记\",\"text\":\"正文\",\"tags\":[]}",
"400:输入或素材引用不合法;409:slug 或 publicId 已存在。"
],
"/api/tags": [
"标签列表与创建",
"GET, POST",
"POST 使用 application/json:name(必填,去掉开头 # 并 trim 后 1–60 字符,不含 #、空白、英文或中文逗号);slug(可选,最多 60 字符,以小写字母或数字开头,仅含小写字母、数字、连字符)。",
"GET 返回标签数组;POST 新建返回 201,名称已存在时复用并返回 200。标签含 id、name、slug、path。",
"POST /api/tags\n{\"name\":\"排版\"}",
"400:输入错误;409:slug 冲突。"
],
"/api/upload": [
"上传媒体,可独立保存素材",
"POST",
"multipart/form-data:kind(必填,image / video / audio);title(普通上传必填,1–200 字符;assetOnly=true 时可省略);assetOnly(可选,字符串 true / false,默认 false;true 时只存媒体,不创建 post);slug(可选,小写字母或数字组成的非空段,以连字符分隔,最多 120 字符);description(可选,最多 100000 字符);tags(可选,UUID 数组的 JSON 字符串,最多 50 项);文件字段名任意,至少一个文件。最多 16 个文件,单个不超过 250 MB,总计不超过 1 GB。图片支持 JPG、PNG、WebP、GIF、AVIF;音频必须为独立声音文件。",
"201 JSON:普通上传为 {id,slug,url,assets};assetOnly=true 为 {assets,files:[{id,filename,sha256}]},不创建 post。assets 为素材 UUID 数组,之后可交给 /api/posts 组合;视频素材会自动进入后台加工队列。",
"POST /api/upload\nContent-Type: multipart/form-data\nkind=image\ntitle=示例图片\ntags=[]\nfile=<图片文件>",
"400:输入或媒体不合法;409:slug 冲突;413:上传超过限制。"
],
"/api/media/{id}": [
"读取媒体原件或衍生文件",
"GET, HEAD",
"路径参数:id,素材 UUID。查询参数:variant(可选,poster / silent_video / audio_track,读取以 id 为来源的衍生素材)。支持 Range 请求头。",
"二进制媒体,Content-Type 随文件类型变化;200 为完整文件,206 为范围响应,HEAD 无正文。",
"GET /api/media/{id}?variant=poster",
"404:素材不存在或无效 variant;416:请求范围无效。"
],
"/api/preview": [
"轻量图片预览",
"GET, HEAD",
"查询参数:source(必填,本站静态栅格图片路径,或 /api/media/<UUID>,可带 ?variant=poster;作为查询值时必须 URL 编码);width(可选,240 / 480 / 960 / 1280,默认 480)。不接受任意远程图片地址。支持 If-None-Match。",
"WebP 图片;包含 ETag;缓存命中可返回 304;HEAD 无正文。",
"GET /api/preview?source=%2Fapi%2Fmedia%2F<UUID>&width=480",
"400:参数不合法;404:图片不存在;503:预览暂不可用。"
],
"/api/skills/street-people": [
"透明人物规范与上传",
"GET, POST",
"GET 默认返回 JSON 规范,format=md 返回创作指导 Markdown。POST 使用 multipart/form-data,沿用 /api/upload 字段与大小限制;kind 固定为 image,title 默认“街头小人 · 新生成”。图片必须为带 alpha 的 PNG,含 alpha=0 的透明像素及 alpha≥250 的像素。",
"GET 返回生成规范;POST 返回 201 {id,slug,url,assets}。",
"GET /api/skills/street-people\nPOST /api/skills/street-people\nContent-Type: multipart/form-data\nfile=<透明 PNG>",
"400:图片或字段不合法;413:超过上传限制。"
],
"/api/health": [
"应用健康检查",
"GET, HEAD",
"无业务参数。",
"GET 返回 JSON:status、service、release;HEAD 仅返回状态和响应头。",
"GET /api/health",
"405:不支持的方法。"
],
"/api/ready": [
"依赖就绪检查",
"GET",
"无业务参数。检查 PostgreSQL 和对象存储桶。",
"200 JSON:{\"status\":\"ok\",\"database\":\"ready\",\"objects\":\"ready\"}。",
"GET /api/ready",
"503 JSON:{\"status\":\"unavailable\"}。"
]
};
export function serveApiDocumentation(req:NextApiRequest,res:NextApiResponse,path:string){
if(!['GET','HEAD'].includes(req.method||'')||typeof req.query.agent_mode!=='string'||req.query.agent_mode.toLowerCase()!=='true')return false;
const doc=apiDocuments[path];if(!doc)return false;
const [title,methods,parameters,response,example,errors]=doc;
const text=`# ${title}\n\n地址:${path}\n方法:${methods}\n\n## 参数\n${parameters}\n\n## 响应\n${response}\n\n## 调用示例\n${example}\n\n## 错误\n${errors}\n\n## 读取说明\n使用 GET 或 HEAD,在原地址加 ?agent_mode=True;已有查询参数时加 &agent_mode=True。值不区分大小写。动态路径可使用实际 ID,也可保留占位符读取本接口说明。\n业务调用请移除 agent_mode。POST / PUT 不会被转换为说明请求。写入接口会拒绝 Origin 与 Host 不一致的请求;当前没有 API Key 或登录鉴权。\n\nAPI 目录:/api?agent_mode=True\n`;
res.setHeader('Content-Type','text/plain; charset=utf-8');res.setHeader('Cache-Control','no-store');
res.status(200);if(req.method==='HEAD')res.end();else res.send(text);return true;
}