diff --git a/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts b/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts index 88031e6..380cb50 100644 --- a/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts +++ b/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts @@ -15,15 +15,7 @@ export async function GET( } const response = await BookService.getPageThumbnail(params.bookId, pageNumber); - const buffer = await response.arrayBuffer(); - const headers = new Headers(); - headers.set("Content-Type", response.headers.get("Content-Type") || "image/jpeg"); - headers.set("Cache-Control", "public, max-age=31536000"); // Cache for 1 year - - return new NextResponse(buffer, { - status: 200, - headers, - }); + return response; } catch (error) { console.error("API Book Page Thumbnail - Erreur:", error); return NextResponse.json( diff --git a/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts b/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts index 0c9831e..ef228fe 100644 --- a/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts +++ b/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts @@ -3,7 +3,7 @@ import { BookService } from "@/lib/services/book.service"; export async function GET(request: NextRequest, { params }: { params: { bookId: string } }) { try { - const response = await BookService.getThumbnail(params.bookId); + const response = await BookService.getCover(params.bookId); return response; } catch (error) { console.error("Erreur lors de la récupération de la miniature du livre:", error); diff --git a/src/app/api/komga/images/series/[seriesId]/first-page/route.ts b/src/app/api/komga/images/series/[seriesId]/first-page/route.ts index 5d2bf80..a8b62fc 100644 --- a/src/app/api/komga/images/series/[seriesId]/first-page/route.ts +++ b/src/app/api/komga/images/series/[seriesId]/first-page/route.ts @@ -5,10 +5,10 @@ export const dynamic = "force-dynamic"; export async function GET(request: NextRequest, { params }: { params: { seriesId: string } }) { try { - const response = await SeriesService.getFirstPage(params.seriesId); + const response = await SeriesService.getCover(params.seriesId); return response; } catch (error) { - console.error("Erreur lors de la récupération de la première page de la série:", error); - return new NextResponse("Erreur lors de la récupération de l'image", { status: 500 }); + console.error("Erreur lors de la récupération de la couverture de la série:", error); + return new NextResponse("Erreur lors de la récupération de la couverture", { status: 500 }); } } diff --git a/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts b/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts index bf568cd..23472d9 100644 --- a/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts +++ b/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts @@ -3,7 +3,7 @@ import { SeriesService } from "@/lib/services/series.service"; export async function GET(request: NextRequest, { params }: { params: { seriesId: string } }) { try { - const response = await SeriesService.getThumbnail(params.seriesId); + const response = await SeriesService.getCover(params.seriesId); return response; } catch (error) { console.error("Erreur lors de la récupération de la miniature de la série:", error); diff --git a/src/components/home/HeroSection.tsx b/src/components/home/HeroSection.tsx index 92f7e25..c537b13 100644 --- a/src/components/home/HeroSection.tsx +++ b/src/components/home/HeroSection.tsx @@ -1,11 +1,7 @@ "use client"; import { KomgaSeries } from "@/types/komga"; -import Image from "next/image"; -import { useState } from "react"; -import { ImageOff } from "lucide-react"; -import { cn } from "@/lib/utils"; -import { ImageLoader } from "@/components/ui/image-loader"; +import { Cover } from "@/components/ui/cover"; interface HeroSectionProps { series: KomgaSeries[]; @@ -22,7 +18,18 @@ export function HeroSection({ series }: HeroSectionProps) { {/* Grille de couvertures en arrière-plan */}
{series?.map((series) => ( - +
+ +
))}
@@ -41,40 +48,3 @@ export function HeroSection({ series }: HeroSectionProps) { ); } - -interface CoverImageProps { - series: KomgaSeries; -} - -function CoverImage({ series }: CoverImageProps) { - const [imageError, setImageError] = useState(false); - const [imageLoading, setImageLoading] = useState(true); - - return ( -
- {!imageError ? ( - <> - - {`Couverture setImageError(true)} - onLoad={() => setImageLoading(false)} - /> - - ) : ( -
- -
- )} -
- ); -} diff --git a/src/components/home/MediaRow.tsx b/src/components/home/MediaRow.tsx index e01e051..adfe2e1 100644 --- a/src/components/home/MediaRow.tsx +++ b/src/components/home/MediaRow.tsx @@ -1,11 +1,9 @@ "use client"; import { KomgaBook, KomgaSeries } from "@/types/komga"; -import { ChevronLeft, ChevronRight, ImageOff } from "lucide-react"; -import Image from "next/image"; +import { ChevronLeft, ChevronRight } from "lucide-react"; import { useRef, useState } from "react"; -import { cn } from "@/lib/utils"; -import { ImageLoader } from "@/components/ui/image-loader"; +import { Cover } from "@/components/ui/cover"; interface MediaRowProps { title: string; @@ -82,9 +80,6 @@ interface MediaCardProps { } function MediaCard({ item, onClick }: MediaCardProps) { - const [imageError, setImageError] = useState(false); - const [imageLoading, setImageLoading] = useState(true); - // Déterminer si c'est une série ou un livre const isSeries = "booksCount" in item; const title = isSeries @@ -107,32 +102,13 @@ function MediaCard({ item, onClick }: MediaCardProps) { > {/* Image de couverture */}
- {!imageError ? ( - <> - - {`Couverture setImageError(true)} - onLoad={() => setImageLoading(false)} - /> - - ) : ( -
- -
- )} - + {/* Overlay avec les informations au survol */}

{title}

diff --git a/src/components/library/LibraryGrid.tsx b/src/components/library/LibraryGrid.tsx index cae0a57..9322d24 100644 --- a/src/components/library/LibraryGrid.tsx +++ b/src/components/library/LibraryGrid.tsx @@ -1,12 +1,10 @@ import { KomgaLibrary } from "@/types/komga"; -import { Book, ImageOff } from "lucide-react"; -import Image from "next/image"; -import { useState } from "react"; +import { Book } from "lucide-react"; +import { Cover } from "@/components/ui/cover"; interface LibraryGridProps { libraries: KomgaLibrary[]; onLibraryClick?: (library: KomgaLibrary) => void; - getLibraryThumbnailUrl: (libraryId: string) => string; } // Fonction utilitaire pour formater la date de manière sécurisée @@ -27,11 +25,7 @@ const formatDate = (dateString: string): string => { } }; -export function LibraryGrid({ - libraries, - onLibraryClick, - getLibraryThumbnailUrl, -}: LibraryGridProps) { +export function LibraryGrid({ libraries, onLibraryClick }: LibraryGridProps) { if (!libraries.length) { return (
@@ -43,12 +37,7 @@ export function LibraryGrid({ return (
{libraries.map((library) => ( - onLibraryClick?.(library)} - getLibraryThumbnailUrl={getLibraryThumbnailUrl} - /> + onLibraryClick?.(library)} /> ))}
); @@ -57,12 +46,9 @@ export function LibraryGrid({ interface LibraryCardProps { library: KomgaLibrary; onClick?: () => void; - getLibraryThumbnailUrl: (libraryId: string) => string; } -function LibraryCard({ library, onClick, getLibraryThumbnailUrl }: LibraryCardProps) { - const [imageError, setImageError] = useState(false); - +function LibraryCard({ library, onClick }: LibraryCardProps) { return (
{/* Contenu */} diff --git a/src/components/library/SeriesGrid.tsx b/src/components/library/SeriesGrid.tsx index a7a0387..b1067ce 100644 --- a/src/components/library/SeriesGrid.tsx +++ b/src/components/library/SeriesGrid.tsx @@ -1,32 +1,33 @@ "use client"; import { KomgaSeries } from "@/types/komga"; -import { Book, ImageOff, Loader2 } from "lucide-react"; -import Image from "next/image"; -import { useState } from "react"; import { useRouter } from "next/navigation"; -import { ImageLoader } from "@/components/ui/image-loader"; import { cn } from "@/lib/utils"; +import { Cover } from "@/components/ui/cover"; interface SeriesGridProps { series: KomgaSeries[]; } -// Fonction utilitaire pour obtenir les informations de lecture d'une série +// Fonction utilitaire pour obtenir les informations de statut de lecture const getReadingStatusInfo = (series: KomgaSeries) => { - const { booksCount, booksReadCount, booksUnreadCount } = series; - const booksInProgressCount = booksCount - (booksReadCount + booksUnreadCount); + if (series.booksCount === 0) { + return { + label: "Pas de tomes", + className: "bg-yellow-500/10 text-yellow-500", + }; + } - if (booksReadCount === booksCount) { + if (series.booksCount === series.booksReadCount) { return { label: "Lu", className: "bg-green-500/10 text-green-500", }; } - if (booksInProgressCount > 0 || (booksReadCount > 0 && booksReadCount < booksCount)) { + if (series.booksReadCount > 0) { return { - label: `${booksReadCount}/${booksCount}`, + label: `${series.booksReadCount}/${series.booksCount}`, className: "bg-blue-500/10 text-blue-500", }; } @@ -51,74 +52,37 @@ export function SeriesGrid({ series }: SeriesGridProps) { return (
{series.map((series) => ( - router.push(`/series/${series.id}`)} - /> + className={cn( + "group relative aspect-[2/3] overflow-hidden rounded-lg bg-muted", + series.booksCount === series.booksReadCount && "opacity-50" + )} + > + +
+

{series.metadata.title}

+
+ + {getReadingStatusInfo(series).label} + + + {series.booksCount} tome{series.booksCount > 1 ? "s" : ""} + +
+
+ ))}
); } - -interface SeriesCardProps { - series: KomgaSeries; - onClick?: () => void; -} - -function SeriesCard({ series, onClick }: SeriesCardProps) { - const [imageError, setImageError] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const statusInfo = getReadingStatusInfo(series); - const isCompleted = series.booksCount === series.booksReadCount; - - return ( - - ); -} diff --git a/src/components/series/BookGrid.tsx b/src/components/series/BookGrid.tsx index edc5efd..2914c18 100644 --- a/src/components/series/BookGrid.tsx +++ b/src/components/series/BookGrid.tsx @@ -1,12 +1,8 @@ "use client"; import { KomgaBook } from "@/types/komga"; -import { ImageOff, Loader2 } from "lucide-react"; -import Image from "next/image"; -import { useState } from "react"; import { formatDate } from "@/lib/utils"; -import { ImageLoader } from "@/components/ui/image-loader"; -import { cn } from "@/lib/utils"; +import { Cover } from "@/components/ui/cover"; interface BookGridProps { books: KomgaBook[]; @@ -62,7 +58,12 @@ export function BookGrid({ books, onBookClick }: BookGridProps) { onClick={() => onBookClick(book)} className="group relative aspect-[2/3] overflow-hidden rounded-lg bg-muted hover:opacity-100 transition-all" > - +

{book.metadata.title || `Tome ${book.metadata.number}`} @@ -79,131 +80,3 @@ export function BookGrid({ books, onBookClick }: BookGridProps) {

); } - -interface BookImageProps { - book: KomgaBook; - isCompleted?: boolean; -} - -function BookImage({ book, isCompleted }: BookImageProps) { - const [imageError, setImageError] = useState(false); - const [isLoading, setIsLoading] = useState(true); - - if (imageError) { - return ( -
- -
- ); - } - - return ( - <> - - {`Couverture setImageError(true)} - onLoad={() => setIsLoading(false)} - loading="lazy" - quality={80} - unoptimized - priority={false} - fetchPriority="low" - /> - - ); -} - -interface BookCardProps { - book: KomgaBook; - onClick?: () => void; - getBookThumbnailUrl: (bookId: string) => string; -} - -function BookCard({ book, onClick, getBookThumbnailUrl }: BookCardProps) { - const [imageError, setImageError] = useState(false); - - const getReadingStatusInfo = () => { - if (!book.readProgress) { - return { - label: "Non lu", - className: "bg-yellow-500/10 text-yellow-500", - }; - } - - if (book.readProgress.completed) { - const readDate = book.readProgress.readDate ? formatDate(book.readProgress.readDate) : null; - return { - label: readDate ? `Lu le ${readDate}` : "Lu", - className: "bg-green-500/10 text-green-500", - }; - } - - if (book.readProgress.page > 0) { - return { - label: `Page ${book.readProgress.page}/${book.media.pagesCount}`, - className: "bg-blue-500/10 text-blue-500", - }; - } - - return { - label: "Non lu", - className: "bg-yellow-500/10 text-yellow-500", - }; - }; - - const statusInfo = getReadingStatusInfo(); - - return ( - - ); -} diff --git a/src/components/series/SeriesHeader.tsx b/src/components/series/SeriesHeader.tsx index 478eb04..19b7652 100644 --- a/src/components/series/SeriesHeader.tsx +++ b/src/components/series/SeriesHeader.tsx @@ -1,70 +1,30 @@ "use client"; -import Image from "next/image"; -import { ImageOff, Book, BookOpen, BookMarked, Star, StarOff } from "lucide-react"; +import { Book, BookOpen, BookMarked, Star, StarOff } from "lucide-react"; import { KomgaSeries } from "@/types/komga"; import { useState, useEffect } from "react"; import { Button } from "../ui/button"; import { useToast } from "@/components/ui/use-toast"; import { cn } from "@/lib/utils"; -import { ImageLoader } from "@/components/ui/image-loader"; +import { Cover } from "@/components/ui/cover"; interface SeriesHeaderProps { series: KomgaSeries; onSeriesUpdate?: (series: KomgaSeries) => void; } -interface ReadingStatusInfo { - label: string; - className: string; - icon: React.ElementType; -} - -// Fonction utilitaire pour obtenir les informations de lecture d'une série -const getReadingStatusInfo = (series: KomgaSeries): ReadingStatusInfo => { - const { booksCount, booksReadCount, booksUnreadCount } = series; - const booksInProgressCount = booksCount - (booksReadCount + booksUnreadCount); - - if (booksReadCount === booksCount) { - return { - label: "Lu", - className: "bg-green-500/10 text-green-500", - icon: BookOpen, - }; - } - - if (booksInProgressCount > 0 || (booksReadCount > 0 && booksReadCount < booksCount)) { - return { - label: `${booksReadCount}/${booksCount}`, - className: "bg-blue-500/10 text-blue-500", - icon: BookMarked, - }; - } - - return { - label: "Non lu", - className: "bg-yellow-500/10 text-yellow-500", - icon: Book, - }; -}; - export const SeriesHeader = ({ series, onSeriesUpdate }: SeriesHeaderProps) => { const { toast } = useToast(); - const [languageDisplay, setLanguageDisplay] = useState(series.metadata.language); - const [imageError, setImageError] = useState(false); - const [isLoading, setIsLoading] = useState(true); const [isFavorite, setIsFavorite] = useState(false); - const [mounted, setMounted] = useState(false); - const statusInfo = getReadingStatusInfo(series); - // Vérifier si la série est dans les favoris au chargement useEffect(() => { + // Vérifier si la série est dans les favoris const checkFavorite = async () => { try { - const response = await fetch("/api/komga/favorites"); + const response = await fetch(`/api/komga/series/${series.id}/favorite`); if (response.ok) { - const favoriteIds = await response.json(); - setIsFavorite(favoriteIds.includes(series.id)); + const data = await response.json(); + setIsFavorite(data.favorite); } } catch (error) { console.error("Erreur lors de la vérification des favoris:", error); @@ -72,117 +32,94 @@ export const SeriesHeader = ({ series, onSeriesUpdate }: SeriesHeaderProps) => { }; checkFavorite(); - setMounted(true); }, [series.id]); - useEffect(() => { - try { - if (series.metadata.language) { - const displayNames = new Intl.DisplayNames([navigator.language || "fr-FR"], { - type: "language", - }); - setLanguageDisplay(displayNames.of(series.metadata.language) || series.metadata.language); - } - } catch (error) { - console.error("Erreur lors de la traduction de la langue:", error); - setLanguageDisplay(series.metadata.language); - } - }, [series.metadata.language]); - const handleToggleFavorite = async () => { try { - setIsLoading(true); - const response = await fetch("/api/komga/favorites", { - method: isFavorite ? "DELETE" : "POST", + const response = await fetch(`/api/komga/series/${series.id}/favorite`, { + method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ seriesId: series.id }), + body: JSON.stringify({ favorite: !isFavorite }), }); - if (!response.ok) { + if (response.ok) { + setIsFavorite(!isFavorite); + toast({ + title: !isFavorite ? "Ajouté aux favoris" : "Retiré des favoris", + description: series.metadata.title, + }); + } else { throw new Error("Erreur lors de la modification des favoris"); } - - setIsFavorite(!isFavorite); - if (onSeriesUpdate) { - onSeriesUpdate({ ...series, favorite: !isFavorite }); - } - - // Dispatch l'événement pour notifier les autres composants - window.dispatchEvent(new Event("favoritesChanged")); - - toast({ - title: isFavorite ? "Retiré des favoris" : "Ajouté aux favoris", - variant: "default", - }); } catch (error) { + console.error("Erreur lors de la modification des favoris:", error); toast({ - title: "Une erreur est survenue", + title: "Erreur", + description: "Impossible de modifier les favoris", variant: "destructive", }); - } finally { - setIsLoading(false); } }; + const getReadingStatusInfo = () => { + const { booksCount, booksReadCount, booksUnreadCount } = series; + const booksInProgressCount = booksCount - (booksReadCount + booksUnreadCount); + + if (booksReadCount === booksCount) { + return { + label: "Lu", + className: "bg-green-500/10 text-green-500", + icon: BookMarked, + }; + } + + if (booksInProgressCount > 0 || (booksReadCount > 0 && booksReadCount < booksCount)) { + return { + label: `${booksReadCount}/${booksCount}`, + className: "bg-blue-500/10 text-blue-500", + icon: BookOpen, + }; + } + + return { + label: "Non lu", + className: "bg-yellow-500/10 text-yellow-500", + icon: Book, + }; + }; + + const statusInfo = getReadingStatusInfo(); + return (
{/* Image de fond */} - {!imageError ? ( - <> - - {`Couverture setImageError(true)} - onLoad={() => setIsLoading(false)} - quality={60} - priority - unoptimized - /> - - ) : ( -
- -
- )} +
+ +
{/* Contenu */}
{/* Image principale */}
- {!imageError ? ( - <> - - {`Couverture setImageError(true)} - onLoad={() => setIsLoading(false)} - quality={90} - priority - unoptimized - /> - - ) : ( -
- -
- )} +
{/* Informations */} diff --git a/src/components/ui/cover.tsx b/src/components/ui/cover.tsx new file mode 100644 index 0000000..dbdbc93 --- /dev/null +++ b/src/components/ui/cover.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { ImageOff } from "lucide-react"; +import Image from "next/image"; +import { useState } from "react"; +import { cn } from "@/lib/utils"; +import { ImageLoader } from "@/components/ui/image-loader"; + +interface CoverProps { + type: "series" | "book"; + id: string; + alt?: string; + className?: string; + priority?: boolean; + quality?: number; + sizes?: string; + isCompleted?: boolean; +} + +export function Cover({ + type, + id, + alt = "Image de couverture", + className, + priority = false, + quality = 80, + sizes = "(max-width: 640px) 33vw, (max-width: 1024px) 20vw, 20vw", + isCompleted = false, +}: CoverProps) { + const [imageError, setImageError] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + const getImageUrl = () => { + if (type === "series") { + return `/api/komga/images/series/${id}/thumbnail`; + } + return `/api/komga/images/books/${id}/thumbnail`; + }; + + if (imageError) { + return ( +
+ +
+ ); + } + + return ( +
+ + {alt} setImageError(true)} + onLoad={() => setIsLoading(false)} + loading={priority ? "eager" : "lazy"} + quality={quality} + priority={priority} + /> +
+ ); +} diff --git a/src/lib/services/book.service.ts b/src/lib/services/book.service.ts index 54dc358..12f7659 100644 --- a/src/lib/services/book.service.ts +++ b/src/lib/services/book.service.ts @@ -67,14 +67,6 @@ export class BookService extends BaseApiService { static async getPage(bookId: string, pageNumber: number): Promise { try { - // Récupérer les préférences de l'utilisateur - const preferences = await PreferencesService.getPreferences(); - - // Si l'utilisateur préfère les vignettes, utiliser getPageThumbnail - if (preferences.showThumbnails) { - return this.getPageThumbnail(bookId, pageNumber); - } - // Ajuster le numéro de page pour l'API Komga (zero-based) const adjustedPageNumber = pageNumber - 1; const response = await ImageService.getImage( @@ -83,6 +75,7 @@ export class BookService extends BaseApiService { return new Response(response.buffer, { headers: { "Content-Type": response.contentType || "image/jpeg", + "Cache-Control": "public, max-age=31536000, immutable", }, }); } catch (error) { @@ -90,35 +83,26 @@ export class BookService extends BaseApiService { } } - static async getPageThumbnail(bookId: string, pageNumber: number): Promise { + static async getCover(bookId: string): Promise { try { - // Ajuster le numéro de page pour l'API Komga (zero-based) - const adjustedPageNumber = pageNumber; - const response = await ImageService.getImage( - `books/${bookId}/pages/${adjustedPageNumber}/thumbnail?zero_based=true` - ); - return new Response(response.buffer, { - headers: { - "Content-Type": response.contentType || "image/jpeg", - "Cache-Control": "public, max-age=31536000, immutable", - }, - }); - } catch (error) { - throw this.handleError(error, "Impossible de récupérer la miniature"); - } - } + // Récupérer les préférences de l'utilisateur + const preferences = await PreferencesService.getPreferences(); - static async getThumbnail(bookId: string): Promise { - try { - const response = await ImageService.getImage(`books/${bookId}/thumbnail`); - return new Response(response.buffer, { - headers: { - "Content-Type": response.contentType || "image/jpeg", - "Cache-Control": "public, max-age=31536000, immutable", - }, - }); + // Si l'utilisateur préfère les vignettes, utiliser la miniature + if (preferences.showThumbnails) { + const response = await ImageService.getImage(`books/${bookId}/thumbnail`); + return new Response(response.buffer, { + headers: { + "Content-Type": response.contentType || "image/jpeg", + "Cache-Control": "public, max-age=31536000, immutable", + }, + }); + } + + // Sinon, récupérer la première page + return this.getPage(bookId, 1); } catch (error) { - throw this.handleError(error, "Impossible de récupérer la miniature du livre"); + throw this.handleError(error, "Impossible de récupérer la couverture"); } } @@ -130,7 +114,23 @@ export class BookService extends BaseApiService { return `/api/komga/images/books/${bookId}/pages/${pageNumber}/thumbnail`; } - static getThumbnailUrl(bookId: string): string { - return ImageService.getBookThumbnailUrl(bookId); + static async getPageThumbnail(bookId: string, pageNumber: number): Promise { + try { + const response = await ImageService.getImage( + `books/${bookId}/pages/${pageNumber}/thumbnail?zero_based=true` + ); + return new Response(response.buffer, { + headers: { + "Content-Type": response.contentType || "image/jpeg", + "Cache-Control": "public, max-age=31536000, immutable", + }, + }); + } catch (error) { + throw this.handleError(error, "Impossible de récupérer la miniature de la page"); + } + } + + static getCoverUrl(bookId: string): string { + return `/api/komga/images/books/${bookId}/thumbnail`; } } diff --git a/src/lib/services/series.service.ts b/src/lib/services/series.service.ts index de67307..138b47f 100644 --- a/src/lib/services/series.service.ts +++ b/src/lib/services/series.service.ts @@ -77,14 +77,20 @@ export class SeriesService extends BaseApiService { } } - static async getFirstPage(seriesId: string): Promise { + static async getCover(seriesId: string): Promise { try { // Récupérer les préférences de l'utilisateur const preferences = await PreferencesService.getPreferences(); - // Si l'utilisateur préfère les vignettes, utiliser getThumbnail + // Si l'utilisateur préfère les vignettes, utiliser la miniature if (preferences.showThumbnails) { - return this.getThumbnail(seriesId); + const response = await ImageService.getImage(`series/${seriesId}/thumbnail`); + return new Response(response.buffer, { + headers: { + "Content-Type": response.contentType || "image/jpeg", + "Cache-Control": "public, max-age=31536000, immutable", + }, + }); } // Sinon, récupérer la première page @@ -92,21 +98,11 @@ export class SeriesService extends BaseApiService { const response = await BookService.getPage(firstBookId, 1); return response; } catch (error) { - throw this.handleError(error, "Impossible de récupérer la première page"); + throw this.handleError(error, "Impossible de récupérer la couverture"); } } - static async getThumbnail(seriesId: string): Promise { - try { - const response = await ImageService.getImage(`series/${seriesId}/thumbnail`); - return new Response(response.buffer, { - headers: { - "Content-Type": response.contentType || "image/jpeg", - "Cache-Control": "public, max-age=31536000, immutable", - }, - }); - } catch (error) { - throw this.handleError(error, "Impossible de récupérer la miniature de la série"); - } + static getCoverUrl(seriesId: string): string { + return `/api/komga/images/series/${seriesId}/thumbnail`; } }