From b7883eaf4f6523c502ddd693e4d5aa635676278b Mon Sep 17 00:00:00 2001 From: root Date: Tue, 13 Oct 2026 20:24:07 +0800 Subject: [PATCH] feat(organize): expose recovery facts and retain original request on retries --- packages/contracts/src/files.ts | 7 ++++- packages/contracts/src/organization.ts | 41 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/contracts/src/files.ts b/packages/contracts/src/files.ts index fddf5167..6892521a 100644 --- a/packages/contracts/src/files.ts +++ b/packages/contracts/src/files.ts @@ -3,7 +3,7 @@ import { Id, JsonObject, NonNegInt, Timestamp } from "./common.ts"; import { MediaKind } from "./content.ts"; import { DownloadStatus } from "./execution.ts"; import { InstanceHealth, LibraryMediaKind } from "./instances.ts"; -import { OrganizationStatus, PublicOrganizationRecord } from "./organization.ts"; +import { OrganizationRecovery, OrganizationStatus, PublicOrganizationRecord } from "./organization.ts"; import { SlotStatus } from "./policy.ts"; import { ScrapeMode, ScrapeRecord } from "./scrape.ts"; @@ -400,6 +400,8 @@ export const FilesOrganizeEpisodeStructure = z.object({ export type FilesOrganizeEpisodeStructure = z.infer; export const FilesOrganizeRequest = z.object({ + /** 重新整理沿用此记录的已知业务选项,表单明确选择覆盖它们。 */ + recoveryRecordId: Id.optional(), sourceConfigId: Id.optional(), idempotencyKey: IdempotencyKey, items: z.array(FilesOrganizeItem).min(1).max(500), @@ -970,6 +972,9 @@ export type FilesTreeResult = z.infer; export const FilesRecordSourcesQuery = z.object({ recordId: Id }); export type FilesRecordSourcesQuery = z.infer; export const FilesRecordSourcesResult = z.object({ + recovery: OrganizationRecovery.optional(), + /** 已核对登记库根和目录边界的原目标,前端无需从本机绝对路径推算。 */ + targetDir: FileRef.optional(), /** 原整理计划保存的来源策略;不存在时由当前入口重新匹配。 */ sourceConfigId: Id.optional(), recordId: Id, diff --git a/packages/contracts/src/organization.ts b/packages/contracts/src/organization.ts index 1326cb7b..4d387bdf 100644 --- a/packages/contracts/src/organization.ts +++ b/packages/contracts/src/organization.ts @@ -239,7 +239,48 @@ export const RecordAttention = z.object({ }); export type RecordAttention = z.infer; +/** 历史整理可以沿用的事实与需补齐的设置;只在详情和重新整理入口返回。 */ +export const OrganizationRecoveryRequest = z.object({ + downloadTaskId: Id.optional(), + sourcePaths: z.array(z.string()).optional(), + mediaEntityId: Id.optional(), + contentUnitId: Id.optional(), + action: z.enum(["copy", "move", "in_place"]).optional(), + targetLibraryRootId: Id.optional(), + targetDir: z.string().optional(), + sourceConfigId: Id.optional(), + slotId: Id.optional(), + onExisting: z.enum(["skip", "conflict", "replace_with_verify"]).optional(), + namingMode: z.enum(["template", "keep"]).optional(), + minSizeBytes: NonNegInt.optional(), + scrapeAfterImport: z.boolean().optional(), + skipSuccessfulRecords: z.boolean().optional(), + keepBoth: z.boolean().optional(), + importMode: z.enum(["normal", "replace"]).optional(), + replacementAssetIds: z.array(Id).optional(), + fileOverrides: z + .record( + z.string(), + z.object({ + season: NonNegInt.optional(), + episodes: z.object({ start: NonNegInt, end: NonNegInt }).optional(), + contentUnitIds: z.array(Id).optional(), + }), + ) + .optional(), + episodeStructure: z.object({ groupId: z.string().nullable(), appliedToEntity: z.boolean().optional() }).optional(), +}); +export type OrganizationRecoveryRequest = z.infer; +export const OrganizationRecovery = z.object({ + ready: z.boolean(), + source: z.enum(["request", "task", "plan", "record"]), + missing: z.array(z.enum(["sourcePaths", "options", "episodeStructure"])), + request: OrganizationRecoveryRequest, +}); +export type OrganizationRecovery = z.infer; + export const PublicOrganizationRecord = z.object({ + recovery: OrganizationRecovery.optional(), /** 稳定记录 ID */ id: Id, taskId: Id.nullable(),