- contracts/src/api.ts 2733 行 → api/*.ts 13 个领域文件 + shared.ts;api.ts 按原顺序合并 Api。 新旧对比:354 个端点内容与顺序一致,分组键与对外导出一致 - acquisition-organize/services/organize.ts 2645 行 → organize-types / -plan / -exec / -finalize / -records, organize.ts 保留为汇总入口,外部引用不变 - browser/service.ts 类外的纯函数(视图映射、原因文案、坐标换算、图片尺寸、出口代理)移到 session-view.ts, service.ts 照旧转出 - organize-wizard:来源解析 useResolvedSource 移到 source.ts,FormSelect 移到 form-select.tsx 只挪位置不改逻辑;typecheck、lint、test:all、web build、server 编译与编译产物自检通过。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
153 lines
5.1 KiB
TypeScript
153 lines
5.1 KiB
TypeScript
// 端点定义共用的 helper 与跨分组 schema(由 api.ts 汇总导出公开部分)
|
|
import { z } from "zod";
|
|
import * as agent from "../agent.ts";
|
|
import { type ApiScope, CursorQuery, cursorPage, Id, IdParam } from "../common.ts";
|
|
import * as content from "../content.ts";
|
|
import * as identity from "../identity.ts";
|
|
import * as instances from "../instances.ts";
|
|
// 所有 REST 端点的路径常量与请求/响应 schema 汇总。
|
|
// path 相对 API_PREFIX,参数用 OpenAPI 风格 `{id}`(server 的 createRoute 直接使用);
|
|
// web 用 buildPath() 填参数。响应体统一套 apiResponse() 信封,这里的 response 是 data 部分。
|
|
|
|
export const API_PREFIX = "/api/v1";
|
|
|
|
export type HttpMethod = "get" | "post" | "patch" | "put" | "delete";
|
|
|
|
/**
|
|
* 鉴权方式(不写即按 scope 校验,cookie 会话与 API Key 同样适用):
|
|
* - public:免鉴权(必须与服务端 http/auth.ts 的 PUBLIC_AUTH_PATHS 一致,测试断言);
|
|
* - session:只接受控制台管理员 cookie 会话,API Key(含 admin scope)一律 403。
|
|
*/
|
|
export type EndpointAuth = "public" | "session";
|
|
|
|
export interface EndpointDef {
|
|
method: HttpMethod;
|
|
path: string;
|
|
scope: ApiScope;
|
|
auth?: EndpointAuth;
|
|
summary: string;
|
|
params?: z.ZodObject;
|
|
query?: z.ZodType;
|
|
body?: z.ZodType;
|
|
response: z.ZodType;
|
|
}
|
|
|
|
export const ep = <E extends EndpointDef>(e: E): E => e;
|
|
|
|
/** 列表查询:游标分页 + 资源自有过滤 */
|
|
export const listQuery = <S extends z.ZodRawShape>(filters: S) => CursorQuery.extend(filters);
|
|
|
|
/** 删除/无内容动作的响应 */
|
|
export const OkResult = z.object({ ok: z.literal(true) });
|
|
|
|
export interface CrudSchemas {
|
|
row: z.ZodType;
|
|
create?: z.ZodType;
|
|
update?: z.ZodType;
|
|
filters?: z.ZodRawShape;
|
|
}
|
|
|
|
/** 标准资源端点:list / get / create / update / remove(按给出的 schema 决定是否生成) */
|
|
export function crud<const B extends string>(
|
|
base: B,
|
|
name: string,
|
|
s: CrudSchemas,
|
|
scopes: { read: ApiScope; write: ApiScope },
|
|
) {
|
|
const item = `${base}/{id}` as const;
|
|
return {
|
|
list: ep({
|
|
method: "get",
|
|
path: base,
|
|
scope: scopes.read,
|
|
summary: `${name}列表`,
|
|
query: listQuery(s.filters ?? {}),
|
|
response: cursorPage(s.row),
|
|
}),
|
|
get: ep({
|
|
method: "get",
|
|
path: item,
|
|
scope: scopes.read,
|
|
summary: `${name}详情`,
|
|
params: IdParam,
|
|
response: s.row,
|
|
}),
|
|
...(s.create
|
|
? {
|
|
create: ep({
|
|
method: "post",
|
|
path: base,
|
|
scope: scopes.write,
|
|
summary: `新建${name}`,
|
|
body: s.create,
|
|
response: s.row,
|
|
}),
|
|
}
|
|
: {}),
|
|
...(s.update
|
|
? {
|
|
update: ep({
|
|
method: "patch",
|
|
path: item,
|
|
scope: scopes.write,
|
|
summary: `修改${name}`,
|
|
params: IdParam,
|
|
body: s.update,
|
|
response: s.row,
|
|
}),
|
|
}
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
export const R = { read: "read", write: "admin" } as const;
|
|
|
|
export const del = (path: string, summary: string) =>
|
|
ep({ method: "delete", path, scope: "admin", summary, params: IdParam, response: OkResult });
|
|
|
|
/** 实例连通性测试 */
|
|
export const instanceTest = (path: string, summary: string) =>
|
|
ep({ method: "post", path, scope: "admin", summary, params: IdParam, response: instances.InstanceTestResult });
|
|
|
|
/** 入队后台任务的受理结果(taskId = 引擎 run id) */
|
|
export const TaskAccepted = z.object({ taskId: Id });
|
|
export type TaskAccepted = z.infer<typeof TaskAccepted>;
|
|
|
|
/** 人工指定来源资源身份的结果(待识别池 assign / source-releases identity) */
|
|
export const ReleaseIdentityResult = z.object({ mediaEntityId: Id, contentUnitIds: z.array(Id) });
|
|
export type ReleaseIdentityResult = z.infer<typeof ReleaseIdentityResult>;
|
|
|
|
/** 身份修订历史 */
|
|
export const IdentityRevisionList = z.object({ items: z.array(identity.IdentityRevision) });
|
|
export type IdentityRevisionList = z.infer<typeof IdentityRevisionList>;
|
|
|
|
/** 字段覆盖/取消覆盖的结果 */
|
|
export const FieldOverrideResult = z.object({
|
|
mediaEntityId: Id,
|
|
changedFields: z.array(z.string()),
|
|
lockedFields: z.array(z.string()),
|
|
});
|
|
export const FieldParam = z.object({ id: Id, field: content.EntityMetadataField });
|
|
export const UnitFieldParam = z.object({ id: Id, field: content.UnitMetadataField });
|
|
export const UnitFieldOverrideResult = z.object({
|
|
contentUnitId: Id,
|
|
changedFields: z.array(z.string()),
|
|
lockedFields: z.array(z.string()),
|
|
});
|
|
|
|
/** 引擎 run id 不是 uuid(openworkflow 自有 id) */
|
|
export const EngineRunIdParam = z.object({ id: z.string().min(1).max(200) });
|
|
|
|
/** 待处理事项聚合分组 */
|
|
export const DecisionSummaryGroup = z.object({
|
|
kind: agent.DecisionKind,
|
|
mediaEntityId: Id.nullable(),
|
|
mediaEntityTitle: z.string().nullable(),
|
|
count: z.number().int(),
|
|
maxSeverity: z.enum(["info", "warning", "critical"]),
|
|
latestAt: z.string(),
|
|
});
|
|
export type DecisionSummaryGroup = z.infer<typeof DecisionSummaryGroup>;
|
|
export const DecisionSummary = z.object({ total: z.number().int(), groups: z.array(DecisionSummaryGroup) });
|
|
export type DecisionSummary = z.infer<typeof DecisionSummary>;
|