diff --git a/src/app/api/komga/libraries/[libraryId]/scan/route.ts b/src/app/api/komga/libraries/[libraryId]/scan/route.ts
new file mode 100644
index 0000000..83112a9
--- /dev/null
+++ b/src/app/api/komga/libraries/[libraryId]/scan/route.ts
@@ -0,0 +1,45 @@
+import { NextResponse } from "next/server";
+import { LibraryService } from "@/lib/services/library.service";
+import { ERROR_CODES } from "@/constants/errorCodes";
+import { AppError } from "@/utils/errors";
+import { getErrorMessage } from "@/utils/errors";
+import type { NextRequest } from "next/server";
+
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ libraryId: string }> }
+) {
+ try {
+ const libraryId: string = (await params).libraryId;
+
+ // Scan library with deep=false
+ await LibraryService.scanLibrary(libraryId, false);
+
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ console.error("API Library Scan - Erreur:", error);
+ if (error instanceof AppError) {
+ return NextResponse.json(
+ {
+ error: {
+ code: error.code,
+ name: "Library scan error",
+ message: getErrorMessage(error.code),
+ },
+ },
+ { status: 500 }
+ );
+ }
+ return NextResponse.json(
+ {
+ error: {
+ code: ERROR_CODES.LIBRARY.SCAN_ERROR,
+ name: "Library scan error",
+ message: getErrorMessage(ERROR_CODES.LIBRARY.SCAN_ERROR),
+ },
+ },
+ { status: 500 }
+ );
+ }
+}
+
diff --git a/src/components/library/LibraryHeader.tsx b/src/components/library/LibraryHeader.tsx
index 1e19597..5b1fcca 100644
--- a/src/components/library/LibraryHeader.tsx
+++ b/src/components/library/LibraryHeader.tsx
@@ -1,9 +1,10 @@
"use client";
import { useMemo } from "react";
-import { Library, BookOpen } from "lucide-react";
+import { Library } from "lucide-react";
import type { KomgaLibrary, KomgaSeries } from "@/types/komga";
import { RefreshButton } from "./RefreshButton";
+import { ScanButton } from "./ScanButton";
import { useTranslate } from "@/hooks/useTranslate";
import { StatusBadge } from "@/components/ui/status-badge";
import { SeriesCover } from "@/components/ui/series-cover";
@@ -84,14 +85,8 @@ export const LibraryHeader = ({ library, seriesCount, series, refreshLibrary }:
}
-
- {library.booksCount === 1
- ? t("library.header.books", { count: library.booksCount })
- : t("library.header.books_plural", { count: library.booksCount })
- }
-
-
+
{library.unavailable && (
diff --git a/src/components/library/ScanButton.tsx b/src/components/library/ScanButton.tsx
new file mode 100644
index 0000000..3bd6200
--- /dev/null
+++ b/src/components/library/ScanButton.tsx
@@ -0,0 +1,88 @@
+"use client";
+
+import { useState } from "react";
+import { FolderSearch } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { useToast } from "@/components/ui/use-toast";
+import { cn } from "@/lib/utils";
+import { useTranslation } from "react-i18next";
+import { useRouter } from "next/navigation";
+
+interface ScanButtonProps {
+ libraryId: string;
+}
+
+export function ScanButton({ libraryId }: ScanButtonProps) {
+ const [isScanning, setIsScanning] = useState(false);
+ const { toast } = useToast();
+ const { t } = useTranslation();
+ const router = useRouter();
+
+ const handleScan = async () => {
+ setIsScanning(true);
+ try {
+ const response = await fetch(`/api/komga/libraries/${libraryId}/scan`, {
+ method: "POST",
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to scan library");
+ }
+
+ toast({
+ title: t("library.scan.success.title"),
+ description: t("library.scan.success.description"),
+ });
+
+ // Attendre 5 secondes pour que le scan se termine, puis invalider le cache et rafraîchir
+ setTimeout(async () => {
+ try {
+ // Invalider le cache
+ await fetch(`/api/komga/libraries/${libraryId}/series`, {
+ method: "DELETE",
+ });
+
+ // Rafraîchir la page pour voir les changements
+ router.refresh();
+
+ // Toast pour indiquer que l'analyse est terminée
+ toast({
+ title: t("library.scan.complete.title"),
+ description: t("library.scan.complete.description"),
+ });
+ } catch (error) {
+ console.error("Error invalidating cache after scan:", error);
+ toast({
+ variant: "destructive",
+ title: t("library.scan.error.title"),
+ description: t("library.scan.error.refresh"),
+ });
+ } finally {
+ setIsScanning(false);
+ }
+ }, 5000);
+ } catch (error) {
+ setIsScanning(false);
+ toast({
+ variant: "destructive",
+ title: t("library.scan.error.title"),
+ description:
+ error instanceof Error ? error.message : t("library.scan.error.description"),
+ });
+ }
+ };
+
+ return (
+
+ );
+}
+
diff --git a/src/constants/errorCodes.ts b/src/constants/errorCodes.ts
index 0c8d5ce..fda6694 100644
--- a/src/constants/errorCodes.ts
+++ b/src/constants/errorCodes.ts
@@ -36,6 +36,7 @@ export const ERROR_CODES = {
LIBRARY: {
NOT_FOUND: "LIBRARY_NOT_FOUND",
FETCH_ERROR: "LIBRARY_FETCH_ERROR",
+ SCAN_ERROR: "LIBRARY_SCAN_ERROR",
},
SERIES: {
FETCH_ERROR: "SERIES_FETCH_ERROR",
diff --git a/src/constants/errorMessages.ts b/src/constants/errorMessages.ts
index 3e0b03a..2a9c99f 100644
--- a/src/constants/errorMessages.ts
+++ b/src/constants/errorMessages.ts
@@ -32,6 +32,7 @@ export const ERROR_MESSAGES: Record = {
// Library
[ERROR_CODES.LIBRARY.NOT_FOUND]: "📚 Library {libraryId} not found",
[ERROR_CODES.LIBRARY.FETCH_ERROR]: "📚 Error fetching libraries",
+ [ERROR_CODES.LIBRARY.SCAN_ERROR]: "🔍 Error scanning library",
// Series
[ERROR_CODES.SERIES.FETCH_ERROR]: "📖 Error fetching series",
diff --git a/src/i18n/messages/en/common.json b/src/i18n/messages/en/common.json
index 15ee1b4..386f970 100644
--- a/src/i18n/messages/en/common.json
+++ b/src/i18n/messages/en/common.json
@@ -198,6 +198,23 @@
"description": "An error occurred"
}
},
+ "scan": {
+ "button": "Scan library",
+ "buttonLabel": "Scan",
+ "success": {
+ "title": "Library scan started",
+ "description": "The library is being scanned"
+ },
+ "complete": {
+ "title": "Scan complete",
+ "description": "The library has been scanned and updated"
+ },
+ "error": {
+ "title": "Error",
+ "description": "An error occurred while scanning the library",
+ "refresh": "An error occurred while updating the library data"
+ }
+ },
"header": {
"series": "{{count}} series",
"series_plural": "{{count}} series",
@@ -363,6 +380,7 @@
"LIBRARY_NOT_FOUND": "Library not found",
"LIBRARY_FETCH_ERROR": "Error fetching library",
+ "LIBRARY_SCAN_ERROR": "Error scanning library",
"SERIES_FETCH_ERROR": "Error fetching series",
"SERIES_NO_BOOKS_FOUND": "No books found in series",
diff --git a/src/i18n/messages/fr/common.json b/src/i18n/messages/fr/common.json
index b983574..54041c9 100644
--- a/src/i18n/messages/fr/common.json
+++ b/src/i18n/messages/fr/common.json
@@ -198,6 +198,23 @@
"description": "Une erreur est survenue"
}
},
+ "scan": {
+ "button": "Analyser la bibliothèque",
+ "buttonLabel": "Analyser",
+ "success": {
+ "title": "Analyse lancée",
+ "description": "La bibliothèque est en cours d'analyse"
+ },
+ "complete": {
+ "title": "Analyse terminée",
+ "description": "La bibliothèque a été analysée et mise à jour"
+ },
+ "error": {
+ "title": "Erreur",
+ "description": "Une erreur est survenue lors de l'analyse de la bibliothèque",
+ "refresh": "Une erreur est survenue lors de la mise à jour des données"
+ }
+ },
"header": {
"series": "{{count}} série",
"series_plural": "{{count}} séries",
@@ -361,6 +378,7 @@
"LIBRARY_NOT_FOUND": "Bibliothèque introuvable",
"LIBRARY_FETCH_ERROR": "Erreur lors de la récupération de la bibliothèque",
+ "LIBRARY_SCAN_ERROR": "Erreur lors de l'analyse de la bibliothèque",
"SERIES_FETCH_ERROR": "Erreur lors de la récupération des séries",
"SERIES_NO_BOOKS_FOUND": "Aucun livre trouvé dans la série",
diff --git a/src/lib/services/base-api.service.ts b/src/lib/services/base-api.service.ts
index 6a6c8e1..ccc7187 100644
--- a/src/lib/services/base-api.service.ts
+++ b/src/lib/services/base-api.service.ts
@@ -13,6 +13,7 @@ export type { CacheType };
interface KomgaRequestInit extends RequestInit {
isImage?: boolean;
+ noJson?: boolean;
}
interface KomgaUrlBuilder {
@@ -175,7 +176,15 @@ export abstract class BaseApiService {
});
}
- return options.isImage ? (response as T) : response.json();
+ if (options.isImage) {
+ return response as T;
+ }
+
+ if (options.noJson) {
+ return undefined as T;
+ }
+
+ return response.json();
} catch (error) {
throw error;
} finally {
diff --git a/src/lib/services/library.service.ts b/src/lib/services/library.service.ts
index 55cfd7f..79844f8 100644
--- a/src/lib/services/library.service.ts
+++ b/src/lib/services/library.service.ts
@@ -159,4 +159,15 @@ export class LibraryService extends BaseApiService {
throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error);
}
}
+
+ static async scanLibrary(libraryId: string, deep: boolean = false): Promise {
+ try {
+ await this.fetchFromApi({
+ path: `libraries/${libraryId}/scan`,
+ params: { deep: String(deep) }
+ }, {}, { method: "POST", noJson: true });
+ } catch (error) {
+ throw new AppError(ERROR_CODES.LIBRARY.SCAN_ERROR, { libraryId }, error);
+ }
+ }
}