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:
@ -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 }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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] });
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user