- adapters: overviewLanguages() limits overview/tagline chain to Chinese variants (zh-CN→zh-SG→zh-TW→zh-HK); titles keep full chain; season supplement fetch skips languages useless for overview - setting metadata.overviewNonChineseFallback (default off) + UI switch - media detail shows "暂无简介" for empty entity/episode overview - scripts/overview-recompute.ts: recompute stored non-Chinese overviews from kv_cache only (no TMDB requests), dry-run by default, --apply - tests: chain unit tests, Heart Signal (103147) fixture, server recompute test, MP differential for episode overview - runbook + CHANGELOG Co-Authored-By: Claude Opus 5.5 <[email protected]>
48 lines
1.9 KiB
TypeScript
48 lines
1.9 KiB
TypeScript
import type { FilesEntityFromCandidateResult, FilesExternalCandidate } from "@umt/contracts";
|
|
import type { Principal } from "../../http/auth.ts";
|
|
import {
|
|
CandidateError,
|
|
createEntityFromTmdbCandidate,
|
|
enqueueNewEntityRefresh,
|
|
} from "../sources-identity/service/entity-from-candidate.ts";
|
|
import { metadataLanguages, metadataTextOptions } from "../sources-identity/service/metadata-rich.ts";
|
|
import { actorOfP, FilesError, type FmDeps } from "./common.ts";
|
|
import { audit } from "./ledger.ts";
|
|
|
|
// G1:整理向导选中"仅 TMDB 候选"作品 → 就地建本地作品 + 外部映射(accepted/manual)+ 季集单元,返回 entityId。
|
|
// 幂等(同 TMDB id 已有映射即复用);/files/organize 的 external 项与独立端点共用。
|
|
|
|
const actorOf = (p: Principal | undefined) => ({
|
|
type: p?.kind === "api_key" ? ("api_key" as const) : ("user" as const),
|
|
id: actorOfP(p),
|
|
});
|
|
|
|
export async function entityFromCandidate(
|
|
d: FmDeps,
|
|
principal: Principal | undefined,
|
|
ref: FilesExternalCandidate,
|
|
): Promise<FilesEntityFromCandidateResult> {
|
|
try {
|
|
const languages = await metadataLanguages(d.ctx);
|
|
const r = await createEntityFromTmdbCandidate(d.ctx.db, d.tmdb, ref, actorOf(principal), {
|
|
languages,
|
|
...(await metadataTextOptions(d.ctx)),
|
|
});
|
|
if (r.created) {
|
|
await audit(d, principal, "entity_from_candidate", { ...ref, mediaEntityId: r.mediaEntityId });
|
|
// 订阅建档 / 搜索建档 / 整理向导都走这里:建档即入队 full 刷新(每集信息、富字段)
|
|
await enqueueNewEntityRefresh(d.ctx, r.mediaEntityId);
|
|
}
|
|
return r;
|
|
} catch (e) {
|
|
if (e instanceof CandidateError)
|
|
throw new FilesError(
|
|
e.reason === "not_found" ? "not_found" : e.reason === "upstream" ? "upstream" : "bad_request",
|
|
e.message,
|
|
);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export const externalKey = (x: FilesExternalCandidate) => `${x.provider}:${x.kind}:${x.externalId}`;
|