refactor: 拆分大文件(contracts 端点、整理服务、浏览器纯函数、整理弹窗来源)

- 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]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-02 17:40:43 +08:00
1 parent c9e8120fc6
commit 8bec4bad5d
26 files changed
+5906 -5696

No files matched your search

+210
View File
@@ -0,0 +1,210 @@
import { z } from "zod";
import { MetadataEnrichOutput } from "../capabilities-io.ts";
import { cursorPage, IdParam } from "../common.ts";
import * as execution from "../execution.ts";
import * as identity from "../identity.ts";
import * as libraryVersions from "../library-versions.ts";
import * as organization from "../organization.ts";
import * as scrape from "../scrape.ts";
import * as search from "../search.ts";
import { ep, ReleaseIdentityResult, TaskAccepted } from "./shared.ts";
export const libraryApi = {
// ── 待识别池 ──
identityPool: {
list: ep({
method: "get",
path: "/identity-pool",
scope: "read",
summary: "未识别资源(可按原始标题检索;候选不建正式作品)",
query: identity.IdentityPoolQuery,
response: cursorPage(identity.IdentityPoolItem),
}),
assign: ep({
method: "post",
path: "/identity-pool/{id}/assign",
scope: "organize.execute",
summary: "为未识别资源手动指定候选 / 本地作品 / 外部身份",
params: IdParam,
body: identity.ReleaseIdentityAssign,
response: ReleaseIdentityResult,
}),
dismiss: ep({
method: "post",
path: "/identity-pool/{id}/dismiss",
scope: "organize.execute",
summary: "忽略未识别资源(不再自动复查)",
params: IdParam,
body: z.object({ note: z.string().max(2000).optional() }),
response: identity.IdentityCandidate,
}),
retry: ep({
method: "post",
path: "/identity-pool/{id}/retry",
scope: "organize.execute",
summary: "立即重新识别",
params: IdParam,
response: TaskAccepted,
}),
// META-1 元数据补全:先看建议(metadata.enrich,不改作品),再在控制台应用
enrich: ep({
method: "post",
path: "/identity-pool/{id}/enrich",
scope: "organize.execute",
summary: "元数据补全建议(网页搜索 + 白名单网页 + 模型抽取;不改作品;同一标题 7 天内返回上次结果)",
params: IdParam,
body: identity.EnrichRequest,
response: MetadataEnrichOutput,
}),
enrichApply: ep({
method: "post",
path: "/identity-pool/{id}/enrich/apply",
scope: "admin",
auth: "session",
summary: "应用元数据补全建议:建本地临时作品(或并入同名作品)并关联未识别资源(只接受控制台管理员会话)",
params: IdParam,
body: identity.EnrichApplyInput,
response: identity.EnrichApplyResult,
}),
// META-2 补全命中率(按来源类别)
enrichStats: ep({
method: "get",
path: "/identity/enrich-stats",
scope: "read",
summary: "元数据补全命中率(最近 N 天,按来源类别:百科、平台、豆瓣摘要、其它网站摘要)",
query: identity.EnrichStatsQuery,
response: identity.EnrichStats,
}),
},
// ── 媒体库 / 库存 / 首页 ──
library: {
list: ep({
method: "get",
path: "/library",
scope: "read",
summary: "媒体库列表(已登记资产或正在追踪的作品)",
query: organization.LibraryListQuery,
response: cursorPage(organization.LibraryItem),
}),
get: ep({
method: "get",
path: "/library/{id}",
scope: "read",
summary: "作品详情:实际路径、身份映射、元数据来源、各收录版本、候选拒绝理由、下载历史与校验结果",
params: IdParam,
response: organization.LibraryDetail,
}),
versions: ep({
method: "get",
path: "/library/{id}/versions",
scope: "read",
summary: "作品的入库版本:每季每集的本地文件版本与规格、季 / 作品汇总、与版本目标的比对",
params: IdParam,
query: libraryVersions.LibraryVersionsQuery,
response: libraryVersions.LibraryVersions,
}),
scrape: ep({
method: "post",
path: "/library/{id}/scrape",
scope: "organize.execute",
summary: "刮削:把作品的 NFO 与图片写进媒体库目录(后台任务 scrapeMedia;覆盖方式缺省按设置)",
params: IdParam,
body: scrape.LibraryScrapeRequest,
response: scrape.LibraryScrapeResult,
}),
reconcile: ep({
method: "post",
path: "/library/reconcile",
scope: "admin",
summary: "立即做一轮资产对账(库根哨兵 + 增量 stat;不删记录、不补下载)",
body: z.object({ libraryRootId: z.uuid().optional() }),
response: z.object({ runId: z.uuid(), stats: z.record(z.string(), z.unknown()) }),
}),
},
inventory: {
overlay: ep({
method: "get",
path: "/inventory",
scope: "read",
summary: "库存叠加(站点工作台):按作品返回已有版本 / 收录版本缺口 / 下载中",
query: organization.InventoryQuery,
response: organization.InventoryResponse,
}),
mediaServers: ep({
method: "get",
path: "/inventory/media-servers",
scope: "read",
summary: "Emby 兼容库存查询(多实例;接口失败/截断 = unknown,不等于缺失)",
query: organization.MediaServerInventoryQuery,
response: organization.MediaServerInventory,
}),
},
dashboard: {
get: ep({
method: "get",
path: "/dashboard",
scope: "read",
summary: "首页聚合:自动化概览/当前工作/需要我处理/缓存与保留/来源健康/最近产出",
response: organization.Dashboard,
}),
},
downloadCache: {
cleanup: ep({
method: "post",
path: "/download-cache/cleanup",
scope: "admin",
summary: "立即按阈值清理下载缓存(只清已验证入库且满足保留/H&R 的纳管任务)",
body: z.object({ downloaderInstanceId: z.uuid().optional() }),
response: z.object({ report: z.record(z.string(), z.unknown()) }),
}),
},
// ── 全局搜索(W9-G):顶栏搜索 / 命令面板 / 资源结果页 ──
search: {
all: ep({
method: "get",
path: "/search",
scope: "read",
summary:
"全局搜索聚合:本地作品 / TMDB 候选 / 跨站资源(15s 超时、并发合并、30s 缓存、库存叠加)/ 页面与操作;每组带 tookMs",
query: search.SearchQuery,
response: search.SearchResponse,
}),
releases: ep({
method: "get",
path: "/search/releases",
scope: "read",
summary: "跨站资源结果页:站点/分辨率/来源/大小/免费/H&R 筛选、排序、游标;与 /search 共用缓存",
query: search.ReleaseSearchQuery,
response: search.ReleaseSearchPage,
}),
plan: ep({
method: "post",
path: "/search/releases/{siteId}/{siteReleaseId}/plan",
scope: "organize.execute",
summary: "从搜索结果直接下载:定位资源后走 POST /acquisition-plans 同一流程",
params: search.SearchReleaseParams,
body: search.SearchReleasePlanRequest,
response: execution.ManualAcquisitionResult,
}),
tmdbDetail: ep({
method: "get",
path: "/media/tmdb/{type}/{id}",
scope: "read",
summary:
"仅 TMDB 作品的详情:TMDB 信息与季列表、本地库存对比、是否已建档、搜资源的查询词;include=mediaServers 同时查 Emby",
params: search.TmdbMediaParams,
query: search.TmdbMediaQuery,
response: search.TmdbMediaDetail,
}),
tmdbSeason: ep({
method: "get",
path: "/media/tmdb/tv/{id}/season/{season}",
scope: "read",
summary: "仅 TMDB 剧集的季详情与每集信息(标题 / 简介按语言回退链;走 TMDB 请求缓存)",
params: search.TmdbSeasonParams,
response: search.TmdbSeasonDetailView,
}),
},
} as const;