fix(web): subscribe from a TMDB candidate by creating the entity first
The search page 订阅 button and the new-subscription wizard threw away the TMDB id and created a title-only subscription, which stays pending (metadata_pending) with no seasons, episode totals or coverage matrix until some release happens to be identified. Call /files/entity-from-candidate first and subscribe the entity; fall back to title tracking only if that fails. Picking a TMDB row in the wizard now creates the library entity even if the dialog is then cancelled (idempotent per TMDB id). Found in REAL-1b on the v0.2.2 test stack; needs an image rebuild to take effect. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
775344f597
commit
39f5897df6
2 files changed
+27
-9
No files matched your search
@@ -2,6 +2,7 @@ import type { TmdbCandidate } from "@umt/contracts";
|
|||||||
import { ArrowRight, Film, Plus } from "lucide-react";
|
import { ArrowRight, Film, Plus } from "lucide-react";
|
||||||
import { Link, useNavigate } from "react-router";
|
import { Link, useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useEntityFromCandidate } from "~/api/files.ts";
|
||||||
import { useSearch } from "~/api/search.ts";
|
import { useSearch } from "~/api/search.ts";
|
||||||
import { useCreateSubscription } from "~/api/subscriptions.ts";
|
import { useCreateSubscription } from "~/api/subscriptions.ts";
|
||||||
import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
|
import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
|
||||||
@@ -22,17 +23,24 @@ function SectionTitle({ id, title, extra }: { id: string; title: string; extra?:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从 TMDB 候选建订阅:按标题追踪(建档后自动关联作品) */
|
/** 从 TMDB 候选建订阅:先按 TMDB id 建档(季/集与总集数随之就位)再订阅作品;建档失败退回按标题追踪 */
|
||||||
export function useSubscribeTmdb() {
|
export function useSubscribeTmdb() {
|
||||||
const create = useCreateSubscription();
|
const create = useCreateSubscription();
|
||||||
|
const entity = useEntityFromCandidate();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return {
|
return {
|
||||||
pending: create.isPending,
|
pending: create.isPending || entity.isPending,
|
||||||
subscribe: (c: TmdbCandidate, onDone?: () => void) => {
|
subscribe: async (c: TmdbCandidate, onDone?: () => void) => {
|
||||||
const kind = c.entityType === "movie" ? "movie" : "series";
|
const kind = c.entityType === "movie" ? "movie" : "series";
|
||||||
|
const mediaEntityId =
|
||||||
|
c.localEntityId ??
|
||||||
|
(await entity
|
||||||
|
.mutateAsync({ provider: "tmdb", externalId: c.externalId, kind: c.entityType === "movie" ? "movie" : "tv" })
|
||||||
|
.then((r) => r.mediaEntityId)
|
||||||
|
.catch(() => null));
|
||||||
create.mutate(
|
create.mutate(
|
||||||
{
|
{
|
||||||
titleQuery: c.year ? `${c.title} ${c.year}` : c.title,
|
...(mediaEntityId ? { mediaEntityId } : { titleQuery: c.year ? `${c.title} ${c.year}` : c.title }),
|
||||||
mediaKind: kind,
|
mediaKind: kind,
|
||||||
mode: kind === "movie" ? "once" : "continuous",
|
mode: kind === "movie" ? "once" : "continuous",
|
||||||
seasonScope: kind === "movie" ? "current_season" : "following_seasons",
|
seasonScope: kind === "movie" ? "current_season" : "following_seasons",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { ExternalLink, Search, X } from "lucide-react";
|
|||||||
import { type ReactNode, useEffect, useId, useMemo, useState } from "react";
|
import { type ReactNode, useEffect, useId, useMemo, useState } from "react";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useEntityFromCandidate } from "~/api/files.ts";
|
||||||
import { downloaders, useSiteOptions } from "~/api/instances.ts";
|
import { downloaders, useSiteOptions } from "~/api/instances.ts";
|
||||||
import { useEntityDetail, useLibraryRoots } from "~/api/library.ts";
|
import { useEntityDetail, useLibraryRoots } from "~/api/library.ts";
|
||||||
import { usePolicyRevisions } from "~/api/policies.ts";
|
import { usePolicyRevisions } from "~/api/policies.ts";
|
||||||
@@ -141,11 +142,19 @@ function WorkPicker({ onPick, error }: { onPick: (t: Target) => void; error?: st
|
|||||||
year: h.entity.year,
|
year: h.entity.year,
|
||||||
mediaKind: toSubKind(h.entity.kind),
|
mediaKind: toSubKind(h.entity.kind),
|
||||||
});
|
});
|
||||||
const pickTmdb = (c: TmdbCandidate) =>
|
const entity = useEntityFromCandidate();
|
||||||
c.localEntityId
|
const pickTmdb = async (c: TmdbCandidate) => {
|
||||||
|
// 库里没有:先按 TMDB id 建档(季/集与总集数随之就位),失败才退回按标题追踪
|
||||||
|
const id =
|
||||||
|
c.localEntityId ??
|
||||||
|
(await entity
|
||||||
|
.mutateAsync({ provider: "tmdb", externalId: c.externalId, kind: c.entityType === "movie" ? "movie" : "tv" })
|
||||||
|
.then((r) => r.mediaEntityId)
|
||||||
|
.catch(() => null));
|
||||||
|
return id
|
||||||
? onPick({
|
? onPick({
|
||||||
kind: "entity",
|
kind: "entity",
|
||||||
id: c.localEntityId,
|
id,
|
||||||
title: c.title,
|
title: c.title,
|
||||||
year: c.year,
|
year: c.year,
|
||||||
mediaKind: c.entityType === "movie" ? "movie" : "series",
|
mediaKind: c.entityType === "movie" ? "movie" : "series",
|
||||||
@@ -154,8 +163,9 @@ function WorkPicker({ onPick, error }: { onPick: (t: Target) => void; error?: st
|
|||||||
kind: "title",
|
kind: "title",
|
||||||
titleQuery: c.year ? `${c.title} ${c.year}` : c.title,
|
titleQuery: c.year ? `${c.title} ${c.year}` : c.title,
|
||||||
mediaKind: c.entityType === "movie" ? "movie" : "series",
|
mediaKind: c.entityType === "movie" ? "movie" : "series",
|
||||||
note: `TMDB ${c.externalId},建档后自动关联`,
|
note: `TMDB ${c.externalId},建档失败,按标题追踪`,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
const row = (key: string, main: ReactNode, sub: string, onClick: () => void) => (
|
const row = (key: string, main: ReactNode, sub: string, onClick: () => void) => (
|
||||||
<li key={key}>
|
<li key={key}>
|
||||||
<button
|
<button
|
||||||
@@ -405,7 +415,7 @@ export function SubscriptionFormDialog({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{editing ? "编辑订阅" : "新建订阅"}</DialogTitle>
|
<DialogTitle>{editing ? "编辑订阅" : "新建订阅"}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{editing ? titleText : "从媒体库或 TMDB 选作品;库里没有的按标题追踪,建档后自动关联。"}
|
{editing ? titleText : "从媒体库或 TMDB 选作品;选 TMDB 条目会先建档。"}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{dup ? (
|
{dup ? (
|
||||||
|
|||||||
Reference in new issue
Block a user