refactor: streamline book and series services by removing deprecated methods and enhancing API calls for fetching books and series data

This commit is contained in:
Julien Froidefond
2025-12-07 13:09:29 +01:00
parent 10b903a136
commit feb8444b35
3 changed files with 54 additions and 138 deletions

View File

@@ -4,10 +4,9 @@ import type { ImageResponse } from "./image.service";
import { ImageService } from "./image.service"; import { ImageService } from "./image.service";
import { PreferencesService } from "./preferences.service"; import { PreferencesService } from "./preferences.service";
import { ConfigDBService } from "./config-db.service"; import { ConfigDBService } from "./config-db.service";
import { SeriesService } from "./series.service";
import { ERROR_CODES } from "../../constants/errorCodes"; import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors"; import { AppError } from "../../utils/errors";
import { SeriesService } from "./series.service";
import type { Series } from "@/types/series";
import logger from "@/lib/logger"; import logger from "@/lib/logger";
export class BookService extends BaseApiService { export class BookService extends BaseApiService {
@@ -43,10 +42,23 @@ export class BookService extends BaseApiService {
throw new AppError(ERROR_CODES.BOOK.NOT_FOUND, {}, error); throw new AppError(ERROR_CODES.BOOK.NOT_FOUND, {}, error);
} }
} }
public static async getNextBook(bookId: string, seriesId: string): Promise<KomgaBook | null> { public static async getNextBook(bookId: string, _seriesId: string): Promise<KomgaBook | null> {
const books = await SeriesService.getAllSeriesBooks(seriesId); try {
const currentIndex = books.findIndex((book) => book.id === bookId); // Utiliser l'endpoint natif Komga pour obtenir le livre suivant
return books[currentIndex + 1] || null; return await this.fetchFromApi<KomgaBook>({ path: `books/${bookId}/next` });
} catch (error) {
// Si le livre suivant n'existe pas, Komga retourne 404
// On retourne null dans ce cas
if (
error instanceof AppError &&
error.code === ERROR_CODES.KOMGA.HTTP_ERROR &&
(error as any).context?.status === 404
) {
return null;
}
// Pour les autres erreurs, on les propage
throw error;
}
} }
static async updateReadProgress( static async updateReadProgress(
@@ -191,34 +203,7 @@ export class BookService extends BaseApiService {
const { LibraryService } = await import("./library.service"); const { LibraryService } = await import("./library.service");
// Essayer d'abord d'utiliser le cache des bibliothèques // Faire une requête légère : prendre une page de séries d'une bibliothèque au hasard
const allSeriesFromCache: Series[] = [];
for (const libraryId of libraryIds) {
try {
// Essayer de récupérer les séries depuis le cache (rapide si en cache)
const series = await LibraryService.getAllLibrarySeries(libraryId);
allSeriesFromCache.push(...series);
} catch {
// Si erreur, on continue avec les autres bibliothèques
}
}
if (allSeriesFromCache.length > 0) {
// Choisir une série au hasard parmi toutes celles trouvées
const randomSeriesIndex = Math.floor(Math.random() * allSeriesFromCache.length);
const randomSeries = allSeriesFromCache[randomSeriesIndex];
// Récupérer les books de cette série
const books = await SeriesService.getAllSeriesBooks(randomSeries.id);
if (books.length > 0) {
const randomBookIndex = Math.floor(Math.random() * books.length);
return books[randomBookIndex].id;
}
}
// Si pas de cache, faire une requête légère : prendre une page de séries d'une bibliothèque au hasard
const randomLibraryIndex = Math.floor(Math.random() * libraryIds.length); const randomLibraryIndex = Math.floor(Math.random() * libraryIds.length);
const randomLibraryId = libraryIds[randomLibraryIndex]; const randomLibraryId = libraryIds[randomLibraryIndex];
@@ -235,17 +220,17 @@ export class BookService extends BaseApiService {
const randomSeriesIndex = Math.floor(Math.random() * seriesResponse.content.length); const randomSeriesIndex = Math.floor(Math.random() * seriesResponse.content.length);
const randomSeries = seriesResponse.content[randomSeriesIndex]; const randomSeries = seriesResponse.content[randomSeriesIndex];
// Récupérer les books de cette série // Récupérer les books de cette série avec pagination
const books = await SeriesService.getAllSeriesBooks(randomSeries.id); const booksResponse = await SeriesService.getSeriesBooks(randomSeries.id, 0, 100);
if (books.length === 0) { if (booksResponse.content.length === 0) {
throw new AppError(ERROR_CODES.BOOK.NOT_FOUND, { throw new AppError(ERROR_CODES.BOOK.NOT_FOUND, {
message: "Aucun livre trouvé dans la série", message: "Aucun livre trouvé dans la série",
}); });
} }
const randomBookIndex = Math.floor(Math.random() * books.length); const randomBookIndex = Math.floor(Math.random() * booksResponse.content.length);
return books[randomBookIndex].id; return booksResponse.content[randomBookIndex].id;
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {
throw error; throw error;

View File

@@ -35,45 +35,6 @@ export class LibraryService extends BaseApiService {
} }
} }
static async getAllLibrarySeries(libraryId: string): Promise<Series[]> {
try {
const headers = { "Content-Type": "application/json" };
const searchBody = {
condition: {
libraryId: {
operator: "is",
value: libraryId,
},
},
};
const cacheKey = `library-${libraryId}-all-series`;
const response = await this.fetchWithCache<LibraryResponse<Series>>(
cacheKey,
async () =>
this.fetchFromApi<LibraryResponse<Series>>(
{
path: "series/list",
params: {
size: "5000", // On récupère un maximum de livres
},
},
headers,
{
method: "POST",
body: JSON.stringify(searchBody),
}
),
"SERIES"
);
return response.content;
} catch (error) {
throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error);
}
}
static async getLibrarySeries( static async getLibrarySeries(
libraryId: string, libraryId: string,
page: number = 0, page: number = 0,
@@ -88,7 +49,7 @@ export class LibraryService extends BaseApiService {
let condition: any; let condition: any;
if (unreadOnly) { if (unreadOnly) {
// Utiliser allOf pour combiner les conditions // Utiliser allOf pour combiner libraryId avec anyOf pour UNREAD ou IN_PROGRESS
condition = { condition = {
allOf: [ allOf: [
{ {
@@ -97,12 +58,22 @@ export class LibraryService extends BaseApiService {
value: libraryId, value: libraryId,
}, },
}, },
{
anyOf: [
{ {
readStatus: { readStatus: {
operator: "is", operator: "is",
value: "UNREAD", value: "UNREAD",
}, },
}, },
{
readStatus: {
operator: "is",
value: "IN_PROGRESS",
},
},
],
},
], ],
}; };
} else { } else {
@@ -166,8 +137,6 @@ export class LibraryService extends BaseApiService {
// Invalider toutes les clés de cache pour cette bibliothèque // Invalider toutes les clés de cache pour cette bibliothèque
// Format: library-{id}-series-p{page}-s{size}-u{unread}-q{search} // Format: library-{id}-series-p{page}-s{size}-u{unread}-q{search}
await cacheService.deleteAll(`library-${libraryId}-series-`); await cacheService.deleteAll(`library-${libraryId}-series-`);
// Invalider aussi l'ancienne clé pour compatibilité
await cacheService.delete(`library-${libraryId}-all-series`);
} catch (error) { } catch (error) {
throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error); throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error);
} }

View File

@@ -45,52 +45,6 @@ export class SeriesService extends BaseApiService {
} }
} }
static async getAllSeriesBooks(seriesId: string): Promise<KomgaBook[]> {
try {
const headers = { "Content-Type": "application/json" };
const searchBody = {
condition: {
seriesId: {
operator: "is",
value: seriesId,
},
},
};
const cacheKey = `series-${seriesId}-all-books`;
const response = await this.fetchWithCache<LibraryResponse<KomgaBook>>(
cacheKey,
async () =>
this.fetchFromApi<LibraryResponse<KomgaBook>>(
{
path: "books/list",
params: {
size: "1000", // On récupère un maximum de livres
},
},
headers,
{
method: "POST",
body: JSON.stringify(searchBody),
}
),
"BOOKS"
);
if (!response.content.length) {
throw new AppError(ERROR_CODES.SERIES.NO_BOOKS_FOUND);
}
return response.content;
} catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error);
}
}
static async getSeriesBooks( static async getSeriesBooks(
seriesId: string, seriesId: string,
page: number = 0, page: number = 0,
@@ -104,7 +58,7 @@ export class SeriesService extends BaseApiService {
let condition: any; let condition: any;
if (unreadOnly) { if (unreadOnly) {
// Utiliser allOf pour combiner les conditions // Utiliser allOf pour combiner seriesId avec anyOf pour UNREAD ou IN_PROGRESS
condition = { condition = {
allOf: [ allOf: [
{ {
@@ -113,12 +67,22 @@ export class SeriesService extends BaseApiService {
value: seriesId, value: seriesId,
}, },
}, },
{
anyOf: [
{ {
readStatus: { readStatus: {
operator: "is", operator: "is",
value: "UNREAD", value: "UNREAD",
}, },
}, },
{
readStatus: {
operator: "is",
value: "IN_PROGRESS",
},
},
],
},
], ],
}; };
} else { } else {
@@ -175,8 +139,6 @@ export class SeriesService extends BaseApiService {
// Invalider toutes les clés de cache pour cette série // Invalider toutes les clés de cache pour cette série
// Format: series-{id}-books-p{page}-s{size}-u{unread} // Format: series-{id}-books-p{page}-s{size}-u{unread}
await cacheService.deleteAll(`series-${seriesId}-books-`); await cacheService.deleteAll(`series-${seriesId}-books-`);
// Invalider aussi l'ancienne clé pour compatibilité
await cacheService.delete(`series-${seriesId}-all-books`);
} catch (error) { } catch (error) {
throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error); throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error);
} }