feat: unify job creation — tous les types créent N jobs par librairie côté backend

- metadata_batch, metadata_refresh, reading_status_match, reading_status_push,
  download_detection : library_id devient optionnel, la boucle passe côté API
- rebuild (index_jobs.rs), thumbnail_rebuild, thumbnail_regenerate : même logique,
  suppression du job unique library_id=NULL au profit d'un job par lib
- Backoffice simplifié : suppression des boucles frontend, les Server Actions
  appellent directement l'API sans library_id pour le cas "toutes les librairies"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 09:16:24 +01:00
parent 8f48c6a876
commit f08fc6b6a6
9 changed files with 436 additions and 149 deletions

View File

@@ -63,152 +63,41 @@ export default async function JobsPage({ searchParams }: { searchParams: Promise
async function triggerMetadataBatch(formData: FormData) {
"use server";
const libraryId = formData.get("library_id") as string;
if (libraryId) {
let result;
try {
result = await startMetadataBatch(libraryId);
} catch {
// Library may have metadata disabled — ignore silently
return;
}
revalidatePath("/jobs");
redirect(`/jobs?highlight=${result.id}`);
} else {
// All libraries — skip those with metadata disabled
const allLibraries = await fetchLibraries().catch(() => [] as LibraryDto[]);
let lastId: string | undefined;
for (const lib of allLibraries) {
if (lib.metadata_provider === "none") continue;
try {
const result = await startMetadataBatch(lib.id);
if (result.status !== "already_running") lastId = result.id;
} catch {
// Library may have metadata disabled or other issue — skip
}
}
revalidatePath("/jobs");
redirect(lastId ? `/jobs?highlight=${lastId}` : "/jobs");
}
const result = await startMetadataBatch(libraryId || undefined);
revalidatePath("/jobs");
redirect(result.id ? `/jobs?highlight=${result.id}` : "/jobs");
}
async function triggerMetadataRefresh(formData: FormData) {
"use server";
const libraryId = formData.get("library_id") as string;
if (libraryId) {
let result;
try {
result = await startMetadataRefresh(libraryId);
} catch {
revalidatePath("/jobs");
redirect("/jobs");
}
revalidatePath("/jobs");
redirect(`/jobs?highlight=${result.id}`);
} else {
// All libraries — skip those with metadata disabled
const allLibraries = await fetchLibraries().catch(() => [] as LibraryDto[]);
let lastId: string | undefined;
for (const lib of allLibraries) {
if (lib.metadata_provider === "none") continue;
try {
const result = await startMetadataRefresh(lib.id);
if (result.status !== "already_running") lastId = result.id;
} catch {
// Library may have metadata disabled or no approved links — skip
}
}
revalidatePath("/jobs");
redirect(lastId ? `/jobs?highlight=${lastId}` : "/jobs");
}
const result = await startMetadataRefresh(libraryId || undefined);
revalidatePath("/jobs");
redirect(result.id ? `/jobs?highlight=${result.id}` : "/jobs");
}
async function triggerReadingStatusMatch(formData: FormData) {
"use server";
const libraryId = formData.get("library_id") as string;
if (libraryId) {
let result;
try {
result = await startReadingStatusMatch(libraryId);
} catch {
return;
}
revalidatePath("/jobs");
redirect(`/jobs?highlight=${result.id}`);
} else {
// All libraries — only those with reading_status_provider configured
const allLibraries = await fetchLibraries().catch(() => [] as LibraryDto[]);
let lastId: string | undefined;
for (const lib of allLibraries) {
if (!lib.reading_status_provider) continue;
try {
const result = await startReadingStatusMatch(lib.id);
if (result.status !== "already_running") lastId = result.id;
} catch {
// Skip libraries with errors
}
}
revalidatePath("/jobs");
redirect(lastId ? `/jobs?highlight=${lastId}` : "/jobs");
}
const result = await startReadingStatusMatch(libraryId || undefined);
revalidatePath("/jobs");
redirect(result.id ? `/jobs?highlight=${result.id}` : "/jobs");
}
async function triggerReadingStatusPush(formData: FormData) {
"use server";
const libraryId = formData.get("library_id") as string;
if (libraryId) {
let result;
try {
result = await startReadingStatusPush(libraryId);
} catch {
return;
}
revalidatePath("/jobs");
redirect(`/jobs?highlight=${result.id}`);
} else {
// All libraries — only those with reading_status_provider configured
const allLibraries = await fetchLibraries().catch(() => [] as LibraryDto[]);
let lastId: string | undefined;
for (const lib of allLibraries) {
if (!lib.reading_status_provider) continue;
try {
const result = await startReadingStatusPush(lib.id);
if (result.status !== "already_running") lastId = result.id;
} catch {
// Skip libraries with errors
}
}
revalidatePath("/jobs");
redirect(lastId ? `/jobs?highlight=${lastId}` : "/jobs");
}
const result = await startReadingStatusPush(libraryId || undefined);
revalidatePath("/jobs");
redirect(result.id ? `/jobs?highlight=${result.id}` : "/jobs");
}
async function triggerDownloadDetection(formData: FormData) {
"use server";
const libraryId = formData.get("library_id") as string;
if (libraryId) {
let result;
try {
result = await startDownloadDetection(libraryId);
} catch {
return;
}
revalidatePath("/jobs");
redirect(`/jobs?highlight=${result.id}`);
} else {
// All libraries
const allLibraries = await fetchLibraries().catch(() => [] as LibraryDto[]);
let lastId: string | undefined;
for (const lib of allLibraries) {
try {
const result = await startDownloadDetection(lib.id);
if (result.status !== "already_running") lastId = result.id;
} catch {
// Skip libraries with errors (e.g. Prowlarr not configured)
}
}
revalidatePath("/jobs");
redirect(lastId ? `/jobs?highlight=${lastId}` : "/jobs");
}
const result = await startDownloadDetection(libraryId || undefined);
revalidatePath("/jobs");
redirect(result.id ? `/jobs?highlight=${result.id}` : "/jobs");
}
return (

View File

@@ -1061,24 +1061,24 @@ export type MetadataBatchResultDto = {
error_message: string | null;
};
export async function startMetadataBatch(libraryId: string) {
return apiFetch<{ id: string; status: string }>("/metadata/batch", {
export async function startMetadataBatch(libraryId?: string) {
return apiFetch<{ id: string | null; status: string }>("/metadata/batch", {
method: "POST",
body: JSON.stringify({ library_id: libraryId }),
body: JSON.stringify(libraryId ? { library_id: libraryId } : {}),
});
}
export async function startMetadataRefresh(libraryId: string) {
return apiFetch<{ id: string; status: string }>("/metadata/refresh", {
export async function startMetadataRefresh(libraryId?: string) {
return apiFetch<{ id: string | null; status: string }>("/metadata/refresh", {
method: "POST",
body: JSON.stringify({ library_id: libraryId }),
body: JSON.stringify(libraryId ? { library_id: libraryId } : {}),
});
}
export async function startReadingStatusMatch(libraryId: string) {
return apiFetch<{ id: string; status: string }>("/reading-status/match", {
export async function startReadingStatusMatch(libraryId?: string) {
return apiFetch<{ id: string | null; status: string }>("/reading-status/match", {
method: "POST",
body: JSON.stringify({ library_id: libraryId }),
body: JSON.stringify(libraryId ? { library_id: libraryId } : {}),
});
}
@@ -1111,10 +1111,10 @@ export async function getReadingStatusMatchResults(jobId: string) {
return apiFetch<ReadingStatusMatchResultDto[]>(`/reading-status/match/${jobId}/results`);
}
export async function startReadingStatusPush(libraryId: string) {
return apiFetch<{ id: string; status: string }>("/reading-status/push", {
export async function startReadingStatusPush(libraryId?: string) {
return apiFetch<{ id: string | null; status: string }>("/reading-status/push", {
method: "POST",
body: JSON.stringify({ library_id: libraryId }),
body: JSON.stringify(libraryId ? { library_id: libraryId } : {}),
});
}
@@ -1148,10 +1148,10 @@ export async function getReadingStatusPushResults(jobId: string) {
return apiFetch<ReadingStatusPushResultDto[]>(`/reading-status/push/${jobId}/results`);
}
export async function startDownloadDetection(libraryId: string) {
return apiFetch<{ id: string; status: string }>("/download-detection/start", {
export async function startDownloadDetection(libraryId?: string) {
return apiFetch<{ id: string | null; status: string }>("/download-detection/start", {
method: "POST",
body: JSON.stringify({ library_id: libraryId }),
body: JSON.stringify(libraryId ? { library_id: libraryId } : {}),
});
}