17 lines
2.7 KiB
TypeScript
17 lines
2.7 KiB
TypeScript
import {mkdir,readFile,writeFile,rename} from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import {randomUUID,timingSafeEqual} from 'node:crypto';
|
|
import type {NextApiRequest,NextApiResponse} from 'next';
|
|
import type {Field} from './schema';
|
|
export const root=path.resolve(process.env.EXTRACTOR_DATA_DIR||'.data');
|
|
export type Task={id:string;title:string;createdAt:string;fields:Field[];schema:any;document:{name:string;mime:string;size:number};status:'ready'|'completed'};
|
|
export type Table={name:string;columns:string[];rows:(string|number|boolean|null)[][]};
|
|
export function dir(id:string){if(!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(id))throw new Error('NOT_FOUND');return path.join(root,id);}
|
|
export async function readTask(id:string):Promise<Task>{return JSON.parse(await readFile(path.join(dir(id),'task.json'),'utf8'));}
|
|
export async function atomic(file:string,value:any){const tmp=file+'.'+randomUUID()+'.tmp';await writeFile(tmp,JSON.stringify(value,null,2));await rename(tmp,file);}
|
|
export async function saveTask(task:Task,bytes:Buffer){const d=dir(task.id);await mkdir(d,{recursive:true});await writeFile(path.join(d,'document'),bytes);await atomic(path.join(d,'task.json'),task);}
|
|
export function authorize(req:NextApiRequest,res:NextApiResponse){const token=process.env.EXTRACTOR_API_KEY;if(!token)return true;const a=Buffer.from(`Bearer ${token}`),b=Buffer.from(req.headers.authorization||'');if(a.length===b.length&&timingSafeEqual(a,b))return true;res.status(401).json({error:{code:'UNAUTHORIZED',message:'需要有效的 Bearer token'}});return false;}
|
|
export function fail(res:NextApiResponse,e:any){const missing=e.code==='ENOENT'||e.message==='NOT_FOUND';res.status(missing?404:500).json({error:{code:missing?'NOT_FOUND':'STORAGE_ERROR',message:missing?'任务不存在':'本地文件操作失败'}});}
|
|
export function links(id:string){return {schema:`/api/v1/tasks/${id}`,document:`/api/v1/tasks/${id}/document`,instructions:`/api/v1/tasks/${id}/instructions`,result:`/api/v1/tasks/${id}/result`};}
|
|
export function tablesFrom(data:Record<string,any>):Table[]{const tables:Table[]=[];const scalars=Object.entries(data).filter(([,v])=>v===null||typeof v!=='object');if(scalars.length)tables.push({name:'基本信息',columns:['字段','值'],rows:scalars});for(const [name,value] of Object.entries(data)){if(Array.isArray(value)&&value.length){if(value.every(v=>v!==null&&typeof v==='object'&&!Array.isArray(v))){const columns=Object.keys(value[0]);tables.push({name,columns,rows:value.map(v=>columns.map(c=>typeof v[c]==='object'&&v[c]!==null?JSON.stringify(v[c]):v[c]??null))});}else tables.push({name,columns:['值'],rows:value.map(v=>[typeof v==='object'&&v!==null?JSON.stringify(v):v])});}}return tables;}
|