import type {NextApiRequest,NextApiResponse} from 'next'; // Read documentation before business validation or storage access. Mutations never enter this branch. export const apiDocuments:Record={ "/api/posts/{id}/archive": [ "归档或恢复笔记", "POST", "路径参数 id 为存储 UUID 或24位 publicId。JSON 参数:{archived:true} 归档,{archived:false} 恢复。重复调用幂等,不删除 post、正文、媒体、标签或页面记录。", "200 {post:{id,publicId,title,archived,archivedAt,url}}。归档内容不出现在首页、分类、搜索和普通素材流,仍可通过原地址读取并恢复。GET /api/feed?archived=true 列举归档内容;默认只列出未归档内容。", "POST /api/posts/{id}/archive\n{\"archived\":true}", "400 参数不合法;404 笔记不存在;405 方法不支持。" ], "/api/explore-feed": [ "统一内容流", "GET", "查询参数:q(可选,最多 200 字符);category(可选,标签名称或命名空间);cursor(可选,上一页 nextCursor)。", "JSON:entries、total、sort 与 nextCursor;entry.backlinkCount 为不同来源笔记数(重复链接和自引用不计);默认按引用数降序,搜索时先按标题/标签/简介/正文的匹配程度再按引用数排序,最后按新旧排序。q 支持空格分隔的多个关键词(全部匹配)及双引号短语,中英文不区分大小写。分类过滤发生在分页之前。没有更多结果时 nextCursor 为 null。静态文档与数据库笔记统一搜索与分页;已由相同 publicId 的数据库笔记接管时不重复展示。", "GET /api/explore-feed?q=排版", "400:游标无效或与搜索条件不符;503:素材库暂时无法连接。" ], "/api/feed": [ "素材列表", "GET", "查询参数:archived(字符串 true / false,默认 false;true 只列出归档);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, PATCH", "PATCH 只更新标签,application/json 必须为 {tags:标签 UUID 数组,revision:GET 返回值};系统标签从 GET /api/tags 中 kind=system 的固定项选择。路径参数: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.backlinks 为引用来源 {id,title} 数组,entry.backlinkCount 为其长度;只计算正文中本站 post 的 Markdown 链接(含引用式链接和粘贴的完整网址),排除代码块、图片、外站链接及自引用。entry.assets 为媒体关联、entry.coverAssetId 为显式封面;PUT 返回更新后的 {entry}。先 GET 获取 revision,再 PUT;冲突后重新读取。", "GET /api/notes/{id}\nPUT /api/notes/{id}\n{\"title\":\"标题\",\"description\":\"简介\",\"markdown\":\"正文\",\"tags\":[],\"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/。id 仍为存储 UUID。引用已有素材;不会在此接口上传二进制文件。", "POST /api/posts\n{\"kind\":\"article\",\"title\":\"新笔记\",\"text\":\"正文\",\"tags\":[]}", "400:输入或素材引用不合法;409:slug 或 publicId 已存在。" ], "/api/tags": [ "标签列表与创建", "GET, POST", "标签 JSON 含 kind=normal|system;系统项另含 system_key。POST 只允许创建/复用普通标签,不能提交 kind 或 system_key,也不能用名称添加系统标签;系统标签只通过已有 UUID 选择。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/,可带 ?variant=poster;作为查询值时必须 URL 编码);width(可选,240 / 480 / 960 / 1280,默认 480)。高清导出使用 profile=export 且 width=2160:WebP 质量95,不按常规缩略图预算缩小,不放大小原件;其他 profile 或尺寸组合返回400。不接受任意远程图片地址。支持 If-None-Match。", "WebP 图片;包含 ETag;缓存命中可返回 304;HEAD 无正文。", "GET /api/preview?source=%2Fapi%2Fmedia%2F&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; }