feat(api,web): providers ComicVine + MangaDex et pilotage de l'enrichissement
- Adaptateurs comic-vine et mangadex avec helper de fetch partagé, timeouts et fallback durcis sur les providers existants - Scoring des correspondances amélioré, normalisation de la date de publication, chaîne de résolution des providers étendue - Scanner : statuts par livre (scan/enrichissement) et page admin d'automatisation alignée Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -4,16 +4,21 @@ import type {
|
||||
AutomationFrequency,
|
||||
AutomationScheduleDto,
|
||||
AutomationSettingsDto,
|
||||
MetadataProviderId,
|
||||
MetadataSourcesConfigDto
|
||||
} from "@readabook/shared";
|
||||
import { api, getApiFallback } from "../api/client";
|
||||
import { ErrorRibbon, LoadingState, Panel } from "../components/ui";
|
||||
import {
|
||||
type AdminMetadataProviderId,
|
||||
type AdminMetadataSourcesConfig,
|
||||
defaultMetadataSources,
|
||||
metadataSourcesPayload,
|
||||
moveSource,
|
||||
normalizeMetadataSources,
|
||||
providerLabels,
|
||||
providerUiMessage,
|
||||
providerUiState,
|
||||
providerUiStateLabel,
|
||||
scheduleDays,
|
||||
scheduleSummary
|
||||
} from "./adminAutomation";
|
||||
@ -28,14 +33,9 @@ type ApiState<T> = {
|
||||
success?: string;
|
||||
};
|
||||
|
||||
const defaultMetadataConfig: MetadataSourcesConfigDto = {
|
||||
const defaultMetadataConfig: AdminMetadataSourcesConfig = {
|
||||
isbnPriorityEnabled: true,
|
||||
sources: [
|
||||
{ provider: "local", enabled: true, priority: 0, hasApiKey: false },
|
||||
{ provider: "openlibrary", enabled: false, priority: 1, hasApiKey: false },
|
||||
{ provider: "googlebooks", enabled: false, priority: 2, hasApiKey: false },
|
||||
{ provider: "bnf", enabled: false, priority: 3, hasApiKey: false }
|
||||
]
|
||||
sources: defaultMetadataSources
|
||||
};
|
||||
|
||||
const defaultAutomationSettings: AutomationSettingsDto = {
|
||||
@ -47,7 +47,7 @@ const defaultAutomationSettings: AutomationSettingsDto = {
|
||||
|
||||
export function AdminAutomationPage() {
|
||||
const [tab, setTab] = useState<AdminAutomationTab>("sources");
|
||||
const [metadataState, setMetadataState] = useState<ApiState<MetadataSourcesConfigDto>>({
|
||||
const [metadataState, setMetadataState] = useState<ApiState<AdminMetadataSourcesConfig>>({
|
||||
initial: null,
|
||||
draft: null,
|
||||
loading: true,
|
||||
@ -59,7 +59,7 @@ export function AdminAutomationPage() {
|
||||
loading: true,
|
||||
saving: false
|
||||
});
|
||||
const [apiKeys, setApiKeys] = useState<Partial<Record<MetadataProviderId, string>>>({});
|
||||
const [apiKeys, setApiKeys] = useState<Partial<Record<AdminMetadataProviderId, string>>>({});
|
||||
|
||||
const metadataDirty = useMemo(
|
||||
() => Boolean(metadataState.initial && metadataState.draft && JSON.stringify(metadataState.initial) !== JSON.stringify(metadataState.draft)),
|
||||
@ -78,7 +78,7 @@ export function AdminAutomationPage() {
|
||||
setMetadataState({ initial: next, draft: next, loading: false, saving: false });
|
||||
setApiKeys({});
|
||||
} catch (error) {
|
||||
const fallback = getApiFallback<MetadataSourcesConfigDto>(error);
|
||||
const fallback = getApiFallback<MetadataSourcesConfigDto | AdminMetadataSourcesConfig>(error);
|
||||
const next = normalizeMetadataSources(fallback ?? defaultMetadataConfig);
|
||||
setMetadataState({
|
||||
initial: next,
|
||||
@ -226,11 +226,11 @@ function MetadataSourcesPanel({
|
||||
onSubmit,
|
||||
onRefresh
|
||||
}: {
|
||||
state: ApiState<MetadataSourcesConfigDto>;
|
||||
state: ApiState<AdminMetadataSourcesConfig>;
|
||||
dirty: boolean;
|
||||
apiKeys: Partial<Record<MetadataProviderId, string>>;
|
||||
setApiKeys: (next: Partial<Record<MetadataProviderId, string>>) => void;
|
||||
onChange: (draft: MetadataSourcesConfigDto) => void;
|
||||
apiKeys: Partial<Record<AdminMetadataProviderId, string>>;
|
||||
setApiKeys: (next: Partial<Record<AdminMetadataProviderId, string>>) => void;
|
||||
onChange: (draft: AdminMetadataSourcesConfig) => void;
|
||||
onSubmit: (event: FormEvent) => void;
|
||||
onRefresh: () => Promise<void>;
|
||||
}) {
|
||||
@ -297,14 +297,25 @@ function MetadataSourcesPanel({
|
||||
<small>{source.enabled ? "active" : "inactive"}</small>
|
||||
</span>
|
||||
</label>
|
||||
<label>
|
||||
Cle API
|
||||
<input
|
||||
value={apiKeys[source.provider] ?? ""}
|
||||
onChange={(event) => setApiKeys({ ...apiKeys, [source.provider]: event.target.value })}
|
||||
placeholder={source.hasApiKey ? "cle conservee" : "optionnelle"}
|
||||
/>
|
||||
</label>
|
||||
<div className="provider-config">
|
||||
<div className="provider-state-line">
|
||||
<span className={`status-pill provider-state-${providerUiState(source)}`}>{providerUiStateLabel(source)}</span>
|
||||
<small>{providerUiMessage(source)}</small>
|
||||
</div>
|
||||
{source.provider === "comicvine" && (
|
||||
<p className="provider-warning">
|
||||
Usage non commercial uniquement. Verifier la compatibilite avec l'usage de ReadaBook.
|
||||
</p>
|
||||
)}
|
||||
<label>
|
||||
Cle API
|
||||
<input
|
||||
value={apiKeys[source.provider] ?? ""}
|
||||
onChange={(event) => setApiKeys({ ...apiKeys, [source.provider]: event.target.value })}
|
||||
placeholder={source.hasApiKey ? "cle conservee" : source.requiresCredentials ? "requise" : "optionnelle"}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="provider-actions">
|
||||
<button
|
||||
className="ghost-button icon-button"
|
||||
|
||||
Reference in New Issue
Block a user