- 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]>
202 lines
5.6 KiB
TypeScript
202 lines
5.6 KiB
TypeScript
import { z } from "zod";
|
|
import * as assets from "../assets.ts";
|
|
import { Id, IdParam } from "../common.ts";
|
|
import * as content from "../content.ts";
|
|
import * as identity from "../identity.ts";
|
|
import {
|
|
crud,
|
|
del,
|
|
ep,
|
|
FieldOverrideResult,
|
|
FieldParam,
|
|
IdentityRevisionList,
|
|
R,
|
|
TaskAccepted,
|
|
UnitFieldOverrideResult,
|
|
UnitFieldParam,
|
|
} from "./shared.ts";
|
|
|
|
export const contentApi = {
|
|
// ── 内容 / 身份 ──
|
|
mediaEntities: {
|
|
...crud(
|
|
"/media-entities",
|
|
"作品",
|
|
{
|
|
row: content.MediaEntity,
|
|
create: content.MediaEntityCreate,
|
|
update: content.MediaEntityUpdate,
|
|
filters: {
|
|
kind: content.MediaKind.optional(),
|
|
status: content.MediaEntityStatus.optional(),
|
|
q: z.string().max(200).optional(),
|
|
},
|
|
},
|
|
R,
|
|
),
|
|
merge: ep({
|
|
method: "post",
|
|
path: "/media-entities/{id}/merge",
|
|
scope: "admin",
|
|
summary: "合并重复作品(保留旧 ID 跳转)",
|
|
params: IdParam,
|
|
body: z.object({ intoId: Id, reason: z.string().max(2000) }),
|
|
response: content.MediaEntity,
|
|
}),
|
|
detail: ep({
|
|
method: "get",
|
|
path: "/media-entities/{id}/detail",
|
|
scope: "read",
|
|
summary: "作品详情:外部身份、别名、内容单元、字段来源与覆盖(旧 ID 自动跳转)",
|
|
params: IdParam,
|
|
response: content.MediaEntityDetail,
|
|
}),
|
|
setIdentity: ep({
|
|
method: "post",
|
|
path: "/media-entities/{id}/identities",
|
|
scope: "organize.execute",
|
|
summary: "人工纠错外部映射:旧映射退役(留修订),新映射 accepted + 人工锁",
|
|
params: IdParam,
|
|
body: identity.EntityIdentitySet,
|
|
response: z.object({
|
|
mediaEntityId: Id,
|
|
changed: z.boolean(),
|
|
identityId: Id.optional(),
|
|
retired: z.array(Id).optional(),
|
|
}),
|
|
}),
|
|
refresh: ep({
|
|
method: "post",
|
|
path: "/media-entities/{id}/refresh",
|
|
scope: "organize.execute",
|
|
summary:
|
|
"刷新元数据(字段级;用户锁优先,不动路径)。标题 / 简介按设置 metadata.languages 回退;full=1 拉取全部季的每集信息",
|
|
params: IdParam,
|
|
query: z.object({ full: z.enum(["0", "1", "true", "false"]).optional() }),
|
|
response: TaskAccepted,
|
|
}),
|
|
setField: ep({
|
|
method: "put",
|
|
path: "/media-entities/{id}/fields/{field}",
|
|
scope: "organize.execute",
|
|
summary: "覆盖作品字段并锁定(自动刷新不再改写)",
|
|
params: FieldParam,
|
|
body: content.FieldOverrideInput,
|
|
response: FieldOverrideResult,
|
|
}),
|
|
clearField: ep({
|
|
method: "delete",
|
|
path: "/media-entities/{id}/fields/{field}",
|
|
scope: "organize.execute",
|
|
summary: "取消字段覆盖与锁定",
|
|
params: FieldParam,
|
|
response: FieldOverrideResult,
|
|
}),
|
|
},
|
|
contentUnits: {
|
|
...crud(
|
|
"/content-units",
|
|
"内容单元",
|
|
{
|
|
row: content.ContentUnit,
|
|
update: content.ContentUnitUpdate,
|
|
filters: { mediaEntityId: Id.optional(), seasonNumber: z.coerce.number().int().optional() },
|
|
},
|
|
R,
|
|
),
|
|
setField: ep({
|
|
method: "put",
|
|
path: "/content-units/{id}/fields/{field}",
|
|
scope: "organize.execute",
|
|
summary: "覆盖季 / 集字段并锁定(元数据刷新不再改写)",
|
|
params: UnitFieldParam,
|
|
body: content.FieldOverrideInput,
|
|
response: UnitFieldOverrideResult,
|
|
}),
|
|
clearField: ep({
|
|
method: "delete",
|
|
path: "/content-units/{id}/fields/{field}",
|
|
scope: "organize.execute",
|
|
summary: "取消季 / 集字段覆盖与锁定",
|
|
params: UnitFieldParam,
|
|
response: UnitFieldOverrideResult,
|
|
}),
|
|
},
|
|
titleAliases: {
|
|
...crud(
|
|
"/title-aliases",
|
|
"别名",
|
|
{
|
|
row: content.TitleAlias,
|
|
create: content.TitleAliasCreate,
|
|
filters: { mediaEntityId: Id.optional(), q: z.string().max(200).optional() },
|
|
},
|
|
R,
|
|
),
|
|
remove: del("/title-aliases/{id}", "删除别名"),
|
|
},
|
|
externalIdentities: {
|
|
...crud(
|
|
"/external-identities",
|
|
"外部身份",
|
|
{
|
|
row: identity.ExternalIdentity,
|
|
create: identity.ExternalIdentityCreate,
|
|
update: identity.ExternalIdentityUpdate,
|
|
filters: {
|
|
mediaEntityId: Id.optional(),
|
|
provider: z.string().optional(),
|
|
status: identity.IdentityStatus.optional(),
|
|
},
|
|
},
|
|
R,
|
|
),
|
|
revisions: ep({
|
|
method: "get",
|
|
path: "/external-identities/{id}/revisions",
|
|
scope: "read",
|
|
summary: "身份修订历史",
|
|
params: IdParam,
|
|
response: IdentityRevisionList,
|
|
}),
|
|
},
|
|
|
|
// ── 文件(只读;写入只经整理/对账流程,不开放直接改) ──
|
|
mediaAssets: crud(
|
|
"/media-assets",
|
|
"资产",
|
|
{
|
|
row: assets.MediaAsset,
|
|
filters: { mediaEntityId: Id.optional(), status: assets.AssetStatus.optional() },
|
|
},
|
|
R,
|
|
),
|
|
assetUnitCoverage: crud(
|
|
"/asset-unit-coverage",
|
|
"资产覆盖",
|
|
{
|
|
row: assets.AssetUnitCoverage,
|
|
filters: { mediaAssetId: Id.optional(), contentUnitId: Id.optional() },
|
|
},
|
|
R,
|
|
),
|
|
assetLocations: crud(
|
|
"/asset-locations",
|
|
"资产位置",
|
|
{
|
|
row: assets.AssetLocation,
|
|
filters: { mediaAssetId: Id.optional(), status: assets.LocationStatus.optional() },
|
|
},
|
|
R,
|
|
),
|
|
pathBindings: crud(
|
|
"/path-bindings",
|
|
"路径绑定",
|
|
{
|
|
row: assets.PathBinding,
|
|
filters: { mediaEntityId: Id.optional(), status: assets.BindingStatus.optional() },
|
|
},
|
|
R,
|
|
),
|
|
} as const;
|