feat(web): page admin des sources de métadonnées et de l'automatisation

Configuration des providers (activation, priorité, clé API), réglages
d'automatisation (watch des bibliothèques, auto-enrichissement,
planifications scan/enrichissement) et déclenchement manuel, avec
client API, mocks et styles associés.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Git Agent
2026-08-23 13:10:50 +02:00
parent 48e9459cf3
commit 62abf890e0
10 changed files with 905 additions and 7 deletions

View File

@ -30,4 +30,58 @@ describe("api fallback helpers", () => {
expect(init.method).toBe("DELETE");
expect(headers.has("Content-Type")).toBe(false);
});
it("sends metadata source updates to the admin endpoint", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ isbnPriorityEnabled: false, sources: [] }), {
status: 200,
headers: { "Content-Type": "application/json" }
})
);
vi.stubGlobal("fetch", fetchMock);
await api.updateMetadataSources({
isbnPriorityEnabled: false,
sources: [{ provider: "openlibrary", enabled: true, priority: 1 }]
});
expect(fetchMock.mock.calls[0][0]).toBe("/admin/metadata-sources");
const init = fetchMock.mock.calls[0][1] as RequestInit;
expect(init.method).toBe("PUT");
expect(JSON.parse(init.body as string)).toEqual({
isbnPriorityEnabled: false,
sources: [{ provider: "openlibrary", enabled: true, priority: 1 }]
});
});
it("sends automation settings to the admin endpoint", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(
JSON.stringify({
watchLibraries: true,
autoEnrichNewBooks: true,
scanSchedule: { frequency: "daily", time: "03:00", dayOfWeek: 1 },
enrichSchedule: { frequency: "disabled", time: "04:00", dayOfWeek: 1 }
}),
{
status: 200,
headers: { "Content-Type": "application/json" }
}
)
);
vi.stubGlobal("fetch", fetchMock);
await api.updateAutomationSettings({
watchLibraries: true,
scanSchedule: { frequency: "daily", time: "03:00", dayOfWeek: 1 }
});
expect(fetchMock.mock.calls[0][0]).toBe("/admin/automation");
const init = fetchMock.mock.calls[0][1] as RequestInit;
expect(init.method).toBe("PUT");
expect(JSON.parse(init.body as string)).toEqual({
watchLibraries: true,
scanSchedule: { frequency: "daily", time: "03:00", dayOfWeek: 1 }
});
});
});

View File

@ -2,17 +2,30 @@ import type {
BookDto,
BookQueryDto,
AuthStatusDto,
AutomationSettingsDto,
BootstrapAdminDto,
CreateLibraryDto,
JobDto,
LibraryDto,
LoginDto,
MetadataSourcesConfigDto,
ProgressDto,
UpdateAutomationSettingsDto,
UpdateAccountDto,
UpdateMetadataSourcesConfigDto,
UpdateProgressDto,
UserDto
} from "@readabook/shared";
import { mockBooks, mockContinue, mockJobs, mockLibraries, mockProgress, mockUser } from "./mockData";
import {
mockAutomationSettings,
mockBooks,
mockContinue,
mockJobs,
mockLibraries,
mockMetadataSources,
mockProgress,
mockUser
} from "./mockData";
import type { CbzPagesDto, ContinueItem, Session } from "./types";
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "";
@ -187,5 +200,31 @@ export const api = {
},
async users(): Promise<UserDto[]> {
return request<UserDto[]>("/admin/users", { fallback: [mockUser] });
},
async metadataSources(): Promise<MetadataSourcesConfigDto> {
return request<MetadataSourcesConfigDto>("/admin/metadata-sources", { fallback: mockMetadataSources });
},
async updateMetadataSources(input: UpdateMetadataSourcesConfigDto): Promise<MetadataSourcesConfigDto> {
return request<MetadataSourcesConfigDto>("/admin/metadata-sources", {
method: "PUT",
body: JSON.stringify(input),
fallback: mockMetadataSources
});
},
async automationSettings(): Promise<AutomationSettingsDto> {
return request<AutomationSettingsDto>("/admin/automation", { fallback: mockAutomationSettings });
},
async updateAutomationSettings(input: UpdateAutomationSettingsDto): Promise<AutomationSettingsDto> {
return request<AutomationSettingsDto>("/admin/automation", {
method: "PUT",
body: JSON.stringify(input),
fallback: mockAutomationSettings
});
},
async runAutomationScan(): Promise<JobDto> {
return request<JobDto>("/admin/automation/run-scan", { method: "POST", fallback: mockJobs[0] });
},
async runAutomationEnrich(): Promise<JobDto> {
return request<JobDto>("/admin/automation/run-enrich", { method: "POST", fallback: mockJobs[0] });
}
};

View File

@ -1,4 +1,4 @@
import type { BookDto, JobDto, LibraryDto, ProgressDto, UserDto } from "@readabook/shared";
import type { AutomationSettingsDto, BookDto, JobDto, LibraryDto, MetadataSourcesConfigDto, ProgressDto, UserDto } from "@readabook/shared";
import type { ContinueItem } from "./types";
const now = new Date().toISOString();
@ -24,6 +24,7 @@ export const mockBooks: BookDto[] = [
author: "M. Valrose",
description: "Fragments, croquis et notes rassemblees autour d'automates introuvables.",
isbn: null,
isbn13: null,
language: "fr",
publisher: "Cabinet ReadaBook",
publishedDate: "1908",
@ -42,6 +43,7 @@ export const mockBooks: BookDto[] = [
author: "I. Nadir",
description: "Un atlas annote ou chaque page devient une vitrine de lecture.",
isbn: null,
isbn13: null,
language: "fr",
publisher: "ReadaBook",
publishedDate: "1921",
@ -60,6 +62,7 @@ export const mockBooks: BookDto[] = [
author: "A. Muze",
description: "Un recit graphique indexe comme archive CBZ.",
isbn: null,
isbn13: null,
language: "fr",
publisher: "ReadaBook",
publishedDate: "1934",
@ -78,6 +81,7 @@ export const mockBooks: BookDto[] = [
author: "L. Rar",
description: "Archive CBR lue avec le même parcours paginé que les comics CBZ.",
isbn: null,
isbn13: null,
language: "fr",
publisher: "ReadaBook",
publishedDate: "1937",
@ -106,3 +110,20 @@ export const mockContinue: ContinueItem[] = mockProgress.map((progress) => ({
export const mockJobs: JobDto[] = [
{ id: 1, type: "scan-library", status: "succeeded", detail: "2 ouvrages indexes", error: null, createdAt: now, updatedAt: now }
];
export const mockMetadataSources: MetadataSourcesConfigDto = {
isbnPriorityEnabled: true,
sources: [
{ provider: "local", enabled: true, priority: 0, hasApiKey: false },
{ provider: "openlibrary", enabled: true, priority: 1, hasApiKey: false },
{ provider: "googlebooks", enabled: false, priority: 2, hasApiKey: false },
{ provider: "bnf", enabled: false, priority: 3, hasApiKey: false }
]
};
export const mockAutomationSettings: AutomationSettingsDto = {
watchLibraries: false,
autoEnrichNewBooks: true,
scanSchedule: { frequency: "disabled", time: "03:00", dayOfWeek: 1 },
enrichSchedule: { frequency: "weekly", time: "04:00", dayOfWeek: 1 }
};