56 lines
4.0 KiB
TypeScript
56 lines
4.0 KiB
TypeScript
import type {NextApiRequest,NextApiResponse} from 'next';
|
|
import {serveApiDocumentation} from '@/lib/api-documentation';
|
|
import {currentAccount} from '@/lib/account';
|
|
import {trustedOrigin} from '@/lib/account-crypto';
|
|
import {db} from '@/lib/db';
|
|
import {CanvasError,uuid,authorizeCanvas,canvasState,createCanvas,saveCanvas,createCanvasLink,queueCanvasTask,readCanvasTask,updateCanvasTask} from '@/lib/canvas-store';
|
|
export const config={api:{bodyParser:{sizeLimit:'15mb'}}};
|
|
export default async function handler(req:NextApiRequest,res:NextApiResponse){
|
|
if(serveApiDocumentation(req,res,'/api/canvases/{...path}'))return;
|
|
res.setHeader('Cache-Control','private, no-store');res.setHeader('Referrer-Policy','no-referrer');
|
|
const path=req.query.path||[];if(!Array.isArray(path)||path.length>3)return res.status(404).end();
|
|
const [id,section,taskId]=path;
|
|
if(id&&!uuid(id)||taskId&&!uuid(taskId))return res.status(404).end();
|
|
if(!['GET','POST','PUT','PATCH','DELETE'].includes(req.method||'')){res.setHeader('Allow','GET, POST, PUT, PATCH, DELETE');return res.status(405).end()}
|
|
const input=req.body;
|
|
if(req.method!=='GET'&&(!input||typeof input!=='object'||Array.isArray(input)))return res.status(400).json({error:'请发送 JSON 对象'});
|
|
try{
|
|
const headers=new Headers(Object.entries(req.headers).filter(([,v])=>typeof v==='string') as [string,string][]);
|
|
const request=new Request(new URL(req.url||'/',process.env.ACCOUNT_PUBLIC_ORIGIN||'http://'+req.headers.host),{headers});
|
|
const bearer=req.headers.authorization?.startsWith('Bearer ')?req.headers.authorization.slice(7):null;
|
|
if(bearer&&req.headers['sec-fetch-site']==='cross-site')throw new CanvasError(403,'请求来源不受信任');
|
|
if(!bearer&&req.method!=='GET'&&!trustedOrigin(request))throw new CanvasError(403,'请求来源不受信任');
|
|
const user=bearer?null:await currentAccount(request);
|
|
if(!id){
|
|
if(!user)throw new CanvasError(401,'请先登录邮箱账户');
|
|
if(req.method==='GET'){const rows=await db().query('SELECT id,name,revision,updated_at AS "updatedAt" FROM canvas_documents WHERE owner_id=$1 ORDER BY updated_at DESC LIMIT 100',[user.id]);return res.json({canvases:rows.rows})}
|
|
if(req.method==='POST')return res.status(201).json(await createCanvas(user.id,input.document,input.name));
|
|
return res.status(405).end();
|
|
}
|
|
const record=await authorizeCanvas(id,user?.id||null,bearer);
|
|
if(!section){
|
|
if(req.method==='GET')return res.json(await canvasState(record));
|
|
if(req.method==='PUT')return res.json(await canvasState(await saveCanvas(id,input.revision,input.document)));
|
|
return res.status(405).end();
|
|
}
|
|
if(section==='link'&&!taskId){
|
|
if(!user||user.id!==record.owner_id)throw new CanvasError(403,'只有画布所有者可以管理 Agent 连接');
|
|
if(req.method==='POST')return res.status(201).json({...await createCanvasLink(id),canvasId:id,documentation:`/api/canvases/${id}?agent_mode=True`});
|
|
if(req.method==='DELETE'){await db().query('DELETE FROM canvas_agent_links WHERE canvas_id=$1',[id]);return res.json({revoked:true})}
|
|
if(req.method==='GET'){const r=await db().query('SELECT expires_at AS "expiresAt",last_seen_at AS "lastSeenAt" FROM canvas_agent_links WHERE canvas_id=$1 AND expires_at>now() ORDER BY expires_at DESC LIMIT 1',[id]);return res.json({link:r.rows[0]||null})}
|
|
return res.status(405).end();
|
|
}
|
|
if(section==='tasks'){
|
|
if(!taskId&&req.method==='POST')return res.status(201).json(await queueCanvasTask(id,input.revision,input.targetId));
|
|
if(taskId&&req.method==='GET')return res.json(await readCanvasTask(id,taskId));
|
|
if(taskId&&req.method==='PATCH')return res.json(await updateCanvasTask(id,taskId,input));
|
|
return res.status(405).end();
|
|
}
|
|
return res.status(404).end();
|
|
}catch(e){
|
|
if(e instanceof CanvasError)return res.status(e.status).json({error:e.message});
|
|
if(e instanceof SyntaxError||e instanceof Error&&/格式|节点|引用|创作要求|图片|画布不能/.test(e.message))return res.status(400).json({error:'画布或任务参数无效'});
|
|
return res.status(503).json({error:'在线画布暂时不可用'});
|
|
}
|
|
}
|