feat(organize): record filters, stats and delete with files (HIS-1)
List query gains kind/since/organizeOnly and wildcard/hash search; new GET /organization-records/stats and POST /organization-records/delete (optional source/library files to trash via files delete). Public record adds posterPath, sizeBytes and download summary. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f60254e265
commit
51e82f1cd9
8 files changed
+850
-17
No files matched your search
@@ -1431,6 +1431,22 @@ export const Api = {
|
||||
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,
|
||||
}),
|
||||
create: ep({
|
||||
method: "post",
|
||||
path: "/organization-records",
|
||||
|
||||
@@ -228,6 +228,8 @@ export const PublicOrganizationRecord = z.object({
|
||||
episodeNumber: z.number().int().nullable(),
|
||||
/** 已确认的外部身份,如 tmdb:movie:693134 */
|
||||
externalIds: z.array(z.object({ provider: z.string(), entityType: z.string(), externalId: z.string() })),
|
||||
/** HIS-1:作品海报(TMDB poster_path,如 /abc.jpg);没有为 null */
|
||||
posterPath: z.string().nullable().optional(),
|
||||
})
|
||||
.nullable(),
|
||||
trigger: OrganizationTrigger,
|
||||
@@ -240,6 +242,13 @@ export const PublicOrganizationRecord = z.object({
|
||||
target: PublicPathRef,
|
||||
reason: PublicReason.nullable(),
|
||||
counts: z.object({ total: NonNegInt, succeeded: NonNegInt, failed: NonNegInt, skipped: NonNegInt }),
|
||||
/** HIS-1:逐文件源大小之和(字节);没有文件明细为 null */
|
||||
sizeBytes: NonNegInt.nullable().optional(),
|
||||
/** HIS-1:来源下载任务摘要(下载器名称、种子 hash);手动整理本地路径时为 null */
|
||||
download: z
|
||||
.object({ taskId: Id, name: z.string(), hash: z.string(), downloaderName: z.string().nullable() })
|
||||
.nullable()
|
||||
.optional(),
|
||||
/** includeFiles=false 时为 null */
|
||||
files: z.array(PublicOrganizationFile).nullable(),
|
||||
createdAt: Timestamp,
|
||||
@@ -280,10 +289,87 @@ export const OrganizationListQuery = z.object({
|
||||
trigger: OrganizationTrigger.optional(),
|
||||
mediaEntityId: Id.optional(),
|
||||
downloadTaskId: Id.optional(),
|
||||
/** 作品标题、源/目标路径、种子 hash;支持 * 与 ? 通配 */
|
||||
q: z.string().max(200).optional(),
|
||||
/** HIS-1:作品类型;none = 没关联作品(未识别) */
|
||||
kind: z.enum(["movie", "series", "none"]).optional(),
|
||||
/** HIS-1:只看这之后创建的记录 */
|
||||
since: Timestamp.optional(),
|
||||
/** HIS-1:只看整理类记录(复制/移动/硬链接),不含文件管理的改名、删除、还原 */
|
||||
organizeOnly: z.union([z.boolean(), z.enum(["true", "false"]).transform((v) => v === "true")]).optional(),
|
||||
});
|
||||
export type OrganizationListQuery = z.infer<typeof OrganizationListQuery>;
|
||||
|
||||
/** HIS-1:统计与列表同一套筛选(不含状态,状态按计数分开给) */
|
||||
export const OrganizationStatsQuery = OrganizationListQuery.omit({ cursor: true, limit: true, status: true });
|
||||
export type OrganizationStatsQuery = z.infer<typeof OrganizationStatsQuery>;
|
||||
export const OrganizationStats = z.object({
|
||||
total: NonNegInt,
|
||||
succeeded: NonNegInt,
|
||||
partial: NonNegInt,
|
||||
failed: NonNegInt,
|
||||
/** 排队 + 执行中 */
|
||||
running: NonNegInt,
|
||||
cancelled: NonNegInt,
|
||||
});
|
||||
export type OrganizationStats = z.infer<typeof OrganizationStats>;
|
||||
|
||||
/**
|
||||
* HIS-1:删除整理记录,可选同时删源文件 / 媒体库里的文件(对照 MP deletesrc / deletedest)。
|
||||
* 文件一律移入回收站(同文件管理删除,可在回收站还原);下载器里的任务不动。
|
||||
* 删文件只允许管理员(控制台会话或 DEV key);API Key 删文件请走 /files/delete(一次性审批)。
|
||||
*/
|
||||
export const OrganizationDeleteRequest = z.object({
|
||||
ids: z.array(Id).min(1).max(200),
|
||||
deleteSource: z.boolean().default(false),
|
||||
deleteTarget: z.boolean().default(false),
|
||||
/** true 只核对会删什么,不动文件与记录 */
|
||||
dryRun: z.boolean().default(false),
|
||||
});
|
||||
export type OrganizationDeleteRequest = z.infer<typeof OrganizationDeleteRequest>;
|
||||
|
||||
/** 单条记录不能删的原因:running 还在整理 / not_found 记录不存在 / 其余同文件管理删除的 blocked 码 */
|
||||
export const OrganizationDeleteBlock = z.enum([
|
||||
"running",
|
||||
"not_found",
|
||||
"download_active",
|
||||
"needs_unbind",
|
||||
"is_root",
|
||||
"root_unavailable",
|
||||
"escape",
|
||||
"outside_roots",
|
||||
"failed",
|
||||
]);
|
||||
export type OrganizationDeleteBlock = z.infer<typeof OrganizationDeleteBlock>;
|
||||
|
||||
export const OrganizationDeleteResult = z.object({
|
||||
dryRun: z.boolean(),
|
||||
items: z.array(
|
||||
z.object({
|
||||
id: Id,
|
||||
/** 会移入回收站的源文件 / 媒体库文件数(已不存在的不算) */
|
||||
sourceFiles: NonNegInt,
|
||||
targetFiles: NonNegInt,
|
||||
bytes: NonNegInt,
|
||||
blocked: OrganizationDeleteBlock.nullable(),
|
||||
/** blocked 时的一句说明(路径 + 原因) */
|
||||
message: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
/** 执行后真正删掉的记录 */
|
||||
removed: z.array(Id),
|
||||
sourceFiles: NonNegInt,
|
||||
targetFiles: NonNegInt,
|
||||
bytes: NonNegInt,
|
||||
/** 删掉的媒体库文件里已入库的文件数(同时从媒体库移除) */
|
||||
assets: NonNegInt,
|
||||
/** 删记录后会被自动整理重新处理的下载任务数(该下载任务没有别的整理记录了) */
|
||||
autoRetry: NonNegInt,
|
||||
/** 文件移入回收站时写的「移入回收站」整理记录 */
|
||||
trashRecordId: Id.nullable(),
|
||||
});
|
||||
export type OrganizationDeleteResult = z.infer<typeof OrganizationDeleteResult>;
|
||||
|
||||
/** 手动发起整理(organize.execute):返回 record/task ID,由任务页追踪 */
|
||||
export const OrganizeRequest = z
|
||||
.object({
|
||||
|
||||
Reference in new issue
Block a user