Files
UHD_Media_Tool/packages/contracts/src/api/organize.ts
T
rootandClaude Opus 5.5 550ac285f4 feat(copy): 处理状态的「入库」改为「整理」,术语表与 check:copy 同步
真正入库取决于媒体服务器有没有扫到,本工具只负责整理(元数据、NFO 与图片、按规则归到目录)。
界面与面向用户的服务端文案里「已入库 / 未入库 / 部分入库 / 入库失败 / 入库中 / 入库后 / 原地入库…」
改为「已整理 / 未整理 / 部分整理 / 整理失败 / 整理中 / 整理后 / 原地整理…」;文件管理快速筛选「已整理」、
目录副行「n 个已整理 / 未整理」、季卡片「已整理 x/y」。代码标识符不改,「Emby 已有」这类媒体服务器状态保留。
glossary 加「整理」口径条目;check:copy 拦处理状态语境的「入库」搭配,其它线待替换的文件暂列 TERM_PENDING。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-06 17:54:28 +08:00

339 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod";
import * as classification from "../classification.ts";
import { cursorPage, IdParam } from "../common.ts";
import * as files from "../files.ts";
import * as organization from "../organization.ts";
import * as organizeSources from "../organize-sources.ts";
import { ep } from "./shared.ts";
export const organizeApi = {
// ── 整理(公开合同) ──
organization: {
list: ep({
method: "get",
path: "/organization-records",
scope: "organize.read",
summary: "整理记录列表",
query: organization.OrganizationListQuery,
response: cursorPage(organization.PublicOrganizationRecord),
}),
get: ep({
method: "get",
path: "/organization-records/{id}",
scope: "organize.read",
summary: "整理记录详情(含逐文件结果)",
params: IdParam,
response: organization.PublicOrganizationRecord,
}),
changes: ep({
method: "get",
path: "/organization-records/changes",
scope: "organize.read",
summary: "按 changeSeq 增量读取(含既有记录状态更新)",
query: organization.OrganizationChangesQuery,
response: organization.OrganizationChangesResponse,
}),
stats: ep({
method: "get",
path: "/organization-records/stats",
scope: "organize.read",
summary: "整理记录按状态计数(筛选同列表)",
query: organization.OrganizationStatsQuery,
response: organization.OrganizationStats,
}),
remove: ep({
method: "post",
path: "/organization-records/delete",
scope: "organize.execute",
summary: "删除整理记录,可选把源文件 / 媒体库文件移入回收站(dryRun 只核对)",
body: organization.OrganizationDeleteRequest,
response: organization.OrganizationDeleteResult,
}),
dismissAttention: ep({
method: "post",
path: "/organization-records/{id}/dismiss-attention",
scope: "organize.execute",
summary: "忽略这条整理记录的问题:不再算需要处理,关联的未关闭事项一并关闭",
params: IdParam,
response: organization.PublicOrganizationRecord,
}),
agentIdentify: ep({
method: "post",
path: "/organization-records/{id}/agent-identify",
scope: "organize.execute",
summary: "让智能体识别这条整理记录的作品(没识别出作品的记录),认出来自动重新整理",
params: IdParam,
response: organization.OrganizationAgentIdentifyResult,
}),
create: ep({
method: "post",
path: "/organization-records",
scope: "organize.execute",
summary: "手动发起整理",
body: organization.OrganizeRequest,
response: organization.OrganizeAccepted,
}),
},
// ── 文件管理(docs/runbook/files.md;只浏览已登记根,写操作两段式 dryRun → planId) ──
classification: {
get: ep({
method: "get",
path: "/classification",
scope: "organize.read",
summary: "分类配置",
response: classification.ClassificationConfigView,
}),
put: ep({
method: "put",
path: "/classification",
scope: "admin",
summary: "保存分类配置",
body: classification.ClassificationPut,
response: classification.ClassificationConfigView,
}),
preview: ep({
method: "post",
path: "/classification/preview",
scope: "organize.read",
summary: "测试分类规则",
body: classification.ClassificationPreviewInput,
response: classification.ClassificationPreviewResult,
}),
},
organizeSources: {
list: ep({
method: "get",
path: "/organize-sources",
scope: "organize.read",
summary: "整理来源",
response: organizeSources.OrganizeSourceList,
}),
save: ep({
method: "put",
path: "/organize-sources/{id}",
scope: "admin",
summary: "保存整理来源",
params: IdParam,
body: organizeSources.OrganizeSourceInput,
response: organizeSources.OrganizeSourceView,
}),
remove: ep({
method: "delete",
path: "/organize-sources/{id}",
scope: "admin",
summary: "删除整理来源",
params: IdParam,
response: z.object({ deleted: z.boolean() }),
}),
stock: ep({
method: "post",
path: "/organize-sources/{id}/stock",
scope: "organize.execute",
summary: "扫描存量文件",
params: IdParam,
body: organizeSources.OrganizeStockInput,
response: organizeSources.OrganizeStockResult,
}),
},
files: {
roots: ep({
method: "get",
path: "/files/roots",
scope: "organize.read",
summary: "可浏览的根(库根 + 下载根),含实时健康与空间",
response: files.FileRootList,
}),
list: ep({
method: "get",
path: "/files",
scope: "organize.read",
summary: "列目录(分页/排序/过滤/截断,行内业务状态);根离线 503",
query: files.FilesListQuery,
response: files.FilesListResponse,
}),
stat: ep({
method: "get",
path: "/files/stat",
scope: "organize.read",
summary: "单项详情:realpath/inode/nlink/sidecars/路径绑定",
query: files.FileStatQuery,
response: files.FileStat,
}),
mkdir: ep({
method: "post",
path: "/files/mkdir",
scope: "organize.execute",
summary: "新建目录(非递归,已存在 409,仅 writable 根)",
body: files.FilesMkdirRequest,
response: files.FilesMkdirResult,
}),
rename: ep({
method: "post",
path: "/files/rename",
scope: "organize.execute",
summary: "同目录改名(不覆盖;同步 asset_location 与 path_binding 修订;做种中 409)",
body: files.FilesRenameRequest,
response: files.FilesRenameResult,
}),
move: ep({
method: "post",
path: "/files/move",
scope: "organize.execute",
summary: "移动/复制到目录:dryRun 返回 planId,执行回传 planId(源变更 409 plan_stale)",
body: files.FilesMoveRequest,
response: files.FilesTransferResult,
}),
copy: ep({
method: "post",
path: "/files/copy",
scope: "organize.execute",
summary: "复制到目录(同 /files/move mode=copy)",
body: files.FilesCopyRequest,
response: files.FilesTransferResult,
}),
organize: ep({
method: "post",
path: "/files/organize",
scope: "organize.execute",
summary: "按作品分组整理(每组走 planImport,与 /organization-records 同实现)",
body: files.FilesOrganizeRequest,
response: files.FilesOrganizeResult,
}),
delete: ep({
method: "post",
path: "/files/delete",
scope: "organize.execute",
summary: "删除(默认进回收站;引用检查:做种/资产/绑定;permanent 仅 admin 会话)",
body: files.FilesDeleteRequest,
response: files.FilesDeleteResult,
}),
trash: ep({
method: "get",
path: "/files/trash",
scope: "organize.read",
summary: "回收站列表",
query: files.FilesTrashQuery,
response: files.FilesTrashList,
}),
trashRestore: ep({
method: "post",
path: "/files/trash/restore",
scope: "organize.execute",
summary: "从回收站还原(原位被占的项失败 conflict)",
body: files.FilesTrashRestoreRequest,
response: files.FilesTrashRestoreResult,
}),
trashPurge: ep({
method: "post",
path: "/files/trash/purge",
scope: "admin",
auth: "session",
summary: "清空回收站(缺省只清过期项;仅管理员控制台会话,API Key 与 DEV key 一律 403)",
body: files.FilesTrashPurgeRequest,
response: files.FilesTrashPurgeResult,
}),
identify: ep({
method: "post",
path: "/files/identify",
scope: "organize.read",
summary: "识别(只读):解析 + 本地/TMDB 召回前 5 候选,不落库",
body: files.FilesIdentifyRequest,
response: files.FilesIdentifyResult,
}),
scrape: ep({
method: "post",
path: "/files/scrape",
scope: "organize.execute",
summary:
"刮削:对目录或视频文件就地写 NFO 与图片(按路径识别作品,不要求已登记;认不准时返回候选,选定后再提交)",
body: files.FilesScrapeRequest,
response: files.FilesScrapeResult,
}),
scrapeRecords: ep({
method: "get",
path: "/files/scrape-records",
scope: "organize.read",
summary: "按 id 查刮削记录(目录、识别结果、逐文件结果)",
query: files.FilesScrapeRecordsQuery,
response: files.FilesScrapeRecordsResult,
}),
probe: ep({
method: "post",
path: "/files/probe",
scope: "organize.execute",
summary: "ffprobe 摘要(≤20 项;已登记资产写回 media_info,所以要 organize.execute)",
body: files.FilesProbeRequest,
response: files.FilesProbeResult,
}),
download: ep({
method: "get",
path: "/files/download",
scope: "admin",
auth: "session",
summary: "下载小文件(nfo/字幕/图片/文本/.torrent,≤20 MiB;流式;仅管理员控制台会话;审计)",
query: files.FilesDownloadQuery,
response: files.FilesDownloadResponse,
}),
preview: ep({
method: "get",
path: "/files/preview",
scope: "admin",
auth: "session",
summary:
"结构化预览(文本/NFO 前 1 MiB 带编码探测、字幕前 200 条、.torrent 文件列表、图片尺寸;仅管理员控制台会话)",
query: files.FilesPreviewQuery,
response: files.FilePreview,
}),
records: ep({
method: "get",
path: "/files/records",
scope: "organize.read",
summary: "按整理记录 id 或 planId 回查整理记录(整理页预填)",
query: files.FilesRecordsQuery,
response: files.FilesRecordsResult,
}),
// W9-H 文件管理 v2 后端缺口
entityFromCandidate: ep({
method: "post",
path: "/files/entity-from-candidate",
scope: "organize.execute",
summary: "按 TMDB 候选就地建本地作品 + 外部映射(accepted/manual)+ 季集单元;幂等",
body: files.FilesEntityFromCandidateRequest,
response: files.FilesEntityFromCandidateResult,
}),
scrapeBatch: ep({
method: "post",
path: "/files/scrape-batch",
scope: "organize.execute",
summary: "批量刷新元数据:按作品去重入队 refreshMetadata(锁定字段不变)",
body: files.FilesScrapeBatchRequest,
response: files.FilesScrapeBatchResult,
}),
tree: ep({
method: "get",
path: "/files/tree",
scope: "organize.read",
summary: "目录摘要:上一级、子目录(是否有下级)、视频/已登记计数(树与上一级用)",
query: files.FilesTreeQuery,
response: files.FilesTreeResult,
}),
recordSources: ep({
method: "get",
path: "/files/record-sources",
scope: "organize.read",
summary: "整理记录的源路径换算成登记目录 FileRef(重新整理用)",
query: files.FilesRecordSourcesQuery,
response: files.FilesRecordSourcesResult,
}),
suggestName: ep({
method: "post",
path: "/files/suggest-name",
scope: "organize.read",
summary: "按命名模板(作品 > 库根 > 全局)生成推荐名;识别不可信时 name=null + 原因",
body: files.FilesSuggestNameRequest,
response: files.FilesSuggestNameResult,
}),
},
} as const;