import { BaseApiService } from "./base-api.service"; import type { KomgaBook, KomgaBookWithPages } from "@/types/komga"; import type { ImageResponse } from "./image.service"; import { ImageService } from "./image.service"; import { PreferencesService } from "./preferences.service"; import { ERROR_CODES } from "../../constants/errorCodes"; import { AppError } from "../../utils/errors"; import { SeriesService } from "./series.service"; export class BookService extends BaseApiService { static async getBook(bookId: string): Promise { try { return this.fetchWithCache( `book-${bookId}`, async () => { // Récupération parallèle des détails du tome et des pages const [book, pages] = await Promise.all([ this.fetchFromApi({ path: `books/${bookId}` }), this.fetchFromApi<{ number: number }[]>({ path: `books/${bookId}/pages` }) ]); return { book, pages: pages.map((page: any) => page.number), }; }, "BOOKS" ); } catch (error) { throw new AppError(ERROR_CODES.BOOK.NOT_FOUND, {}, error); } } public static async getNextBook(bookId: string, seriesId: string): Promise { const books = await SeriesService.getAllSeriesBooks(seriesId); const currentIndex = books.findIndex((book) => book.id === bookId); return books[currentIndex + 1] || null; } static async updateReadProgress( bookId: string, page: number, completed: boolean = false ): Promise { try { const config = await this.getKomgaConfig(); const url = this.buildUrl(config, `books/${bookId}/read-progress`); const headers = this.getAuthHeaders(config); headers.set("Content-Type", "application/json"); const response = await fetch(url, { method: "PATCH", headers, body: JSON.stringify({ page, completed }), }); if (!response.ok) { throw new AppError(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR); } } catch (error) { if (error instanceof AppError) { throw error; } throw new AppError(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR, {}, error); } } static async deleteReadProgress(bookId: string): Promise { try { const config = await this.getKomgaConfig(); const url = this.buildUrl(config, `books/${bookId}/read-progress`); const headers = this.getAuthHeaders(config); headers.set("Content-Type", "application/json"); const response = await fetch(url, { method: "DELETE", headers, }); if (!response.ok) { throw new AppError(ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR); } } catch (error) { if (error instanceof AppError) { throw error; } throw new AppError(ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR, {}, error); } } static async getPage(bookId: string, pageNumber: number): Promise { try { // Ajuster le numéro de page pour l'API Komga (zero-based) const adjustedPageNumber = pageNumber - 1; const response: ImageResponse = await ImageService.getImage( `books/${bookId}/pages/${adjustedPageNumber}?zero_based=true` ); // Convertir le Buffer Node.js en ArrayBuffer proprement const arrayBuffer = response.buffer.buffer.slice( response.buffer.byteOffset, response.buffer.byteOffset + response.buffer.byteLength ); return new Response(arrayBuffer, { headers: { "Content-Type": response.contentType || "image/jpeg", "Cache-Control": "public, max-age=31536000, immutable", }, }); } catch (error) { throw new AppError(ERROR_CODES.BOOK.PAGES_FETCH_ERROR, {}, error); } } static async getCover(bookId: 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 la miniature if (preferences.showThumbnails) { const response: ImageResponse = await ImageService.getImage(`books/${bookId}/thumbnail`); return new Response(response.buffer.buffer as ArrayBuffer, { 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 new AppError(ERROR_CODES.BOOK.PAGES_FETCH_ERROR, {}, error); } } static getPageUrl(bookId: string, pageNumber: number): string { return `/api/komga/images/books/${bookId}/pages/${pageNumber}`; } static getPageThumbnailUrl(bookId: string, pageNumber: number): string { return `/api/komga/images/books/${bookId}/pages/${pageNumber}/thumbnail`; } static async getPageThumbnail(bookId: string, pageNumber: number): Promise { try { const response: ImageResponse = await ImageService.getImage( `books/${bookId}/pages/${pageNumber}/thumbnail?zero_based=true` ); return new Response(response.buffer.buffer as ArrayBuffer, { headers: { "Content-Type": response.contentType || "image/jpeg", "Cache-Control": "public, max-age=31536000, immutable", }, }); } catch (error) { throw new AppError(ERROR_CODES.BOOK.PAGES_FETCH_ERROR, {}, error); } } static getCoverUrl(bookId: string): string { return `/api/komga/images/books/${bookId}/thumbnail`; } }