Files
UHD_Media_Tool/apps/server/src/modules/file-manager/candidate.ts
T
rootandClaude Opus 5.5 13132e7ad8 feat(file-manager): v2 backend gaps — TMDB candidate entities, min size, on-existing, naming source (W9-H)
- G1 createEntityFromTmdbCandidate (sources-identity service): entity + accepted
  manual external identity + season/episode units, idempotent per TMDB id;
  POST /files/entity-from-candidate and organize items with `external`
- G2 minSizeBytes for organize (excluded below_min_size) and identify
  (skipped + excludedBySize)
- G3 onExisting passed to planImport; dryRun marks each file
  (same/skip/replace/conflict); replace = explicit grant
- G4 naming template|keep; planImport namingMode/namingSource, frozen
  plan.naming {mode, templateSource, templateVersion}; targetSegments
- G5 identify prior-record hints, GET /files/record-sources, organize
  refreshMetadata, POST /files/scrape-batch (dedupe by entity)
- G8 GET /files/tree (parent, subdirs hasChildren, video/registered counts)
- G9 POST /files/suggest-name returns 501 until the naming engine lands

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:08:17 +08:00

35 lines
1.5 KiB
TypeScript

import type { FilesEntityFromCandidateResult, FilesExternalCandidate } from "@umt/contracts";
import type { Principal } from "../../http/auth.ts";
import { CandidateError, createEntityFromTmdbCandidate } from "../sources-identity/service/entity-from-candidate.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 r = await createEntityFromTmdbCandidate(d.ctx.db, d.tmdb, ref, actorOf(principal));
if (r.created) await audit(d, principal, "entity_from_candidate", { ...ref, mediaEntityId: 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}`;