feat(identity): open identity_unresolved decisions for resources with no candidates
no_match resources (and lookup failures after auto-review gives up) now
open a decision in the inbox with options to set a TMDB ID, pick a local
work, or dismiss; ambiguous decisions get the same manual options.
Resolve accepts identity {tmdb | mediaEntityId} and assigns with a manual
lock. The inbox option dialog collects the TMDB type/ID or searches local
works; the identity pool links each pending item to its inbox filter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a0b0a64246
commit
fb7727cf66
14 files changed
+280
-37
No files matched your search
@@ -142,6 +142,8 @@ export const DecisionKind = z.enum([
|
||||
"metadata_conflict",
|
||||
"source_unreachable",
|
||||
"other",
|
||||
/** 待识别资源没有可选候选(no_match / 复查耗尽的查询失败):手动指定 TMDB id、选本地作品或忽略 */
|
||||
"identity_unresolved",
|
||||
]);
|
||||
export type DecisionKind = z.infer<typeof DecisionKind>;
|
||||
export const DecisionStatus = z.enum(["open", "investigating", "awaiting_user", "resolved", "dismissed", "expired"]);
|
||||
@@ -220,6 +222,24 @@ export const DecisionResolveInput = z.object({
|
||||
optionKey: z.string().min(1).optional(),
|
||||
dismiss: z.boolean().optional(),
|
||||
note: z.string().max(2000).optional(),
|
||||
/**
|
||||
* 身份类事项的手动指定(选项 input.requires 标明需要哪一项):
|
||||
* requires=tmdb → tmdb {type, id};requires=mediaEntityId → mediaEntityId
|
||||
*/
|
||||
identity: z
|
||||
.object({
|
||||
mediaEntityId: Id.optional(),
|
||||
tmdb: z
|
||||
.object({
|
||||
type: z.enum(["movie", "tv"]),
|
||||
id: z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^\d{1,10}$/),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
export type DecisionResolveInput = z.infer<typeof DecisionResolveInput>;
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Kysely } from "kysely";
|
||||
import { execDdl, oneOf } from "../src/migration-helpers.ts";
|
||||
|
||||
// W8-A 待识别资源没有匹配候选(no_match / 复查耗尽的查询失败)也进待处理中心:decision.kind 增加 identity_unresolved,
|
||||
// 处理选项为手动指定 TMDB id / 选本地作品 / 忽略。
|
||||
|
||||
const OLD_KINDS = [
|
||||
"identity_ambiguous",
|
||||
"download_stalled",
|
||||
"download_failed",
|
||||
"path_conflict",
|
||||
"space_insufficient",
|
||||
"approval_required",
|
||||
"metadata_conflict",
|
||||
"source_unreachable",
|
||||
"other",
|
||||
];
|
||||
const NEW_KINDS = [...OLD_KINDS, "identity_unresolved"];
|
||||
|
||||
const swap = (values: readonly string[]) => `
|
||||
alter table decision drop constraint decision_kind_check;
|
||||
alter table decision add constraint decision_kind_check ${oneOf("kind", values)};
|
||||
`;
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(db, swap(NEW_KINDS));
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 同上
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(db, `update decision set kind = 'other' where kind = 'identity_unresolved';\n${swap(OLD_KINDS)}`);
|
||||
}
|
||||
@@ -104,7 +104,8 @@ export type DecisionKind =
|
||||
| "approval_required"
|
||||
| "metadata_conflict"
|
||||
| "source_unreachable"
|
||||
| "other";
|
||||
| "other"
|
||||
| "identity_unresolved";
|
||||
export type DecisionStatus = "open" | "investigating" | "awaiting_user" | "resolved" | "dismissed" | "expired";
|
||||
export type DecisionSeverity = "info" | "warning" | "critical";
|
||||
export type DownloadTaskOrigin = "submitted" | "adopted";
|
||||
|
||||
Reference in new issue
Block a user