// 端点定义共用的 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: E): E => e; /** 列表查询:游标分页 + 资源自有过滤 */ export const listQuery = (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( 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; /** 人工指定来源资源身份的结果(待识别池 assign / source-releases identity) */ export const ReleaseIdentityResult = z.object({ mediaEntityId: Id, contentUnitIds: z.array(Id) }); export type ReleaseIdentityResult = z.infer; /** 身份修订历史 */ export const IdentityRevisionList = z.object({ items: z.array(identity.IdentityRevision) }); export type IdentityRevisionList = z.infer; /** 字段覆盖/取消覆盖的结果 */ 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; export const DecisionSummary = z.object({ total: z.number().int(), groups: z.array(DecisionSummaryGroup) }); export type DecisionSummary = z.infer;