From 4b710cbac29acd97fe5b52db31990017039deb34 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Tue, 25 Feb 2025 06:39:19 +0100 Subject: [PATCH] refacto: error and error codes in services --- src/constants/errorCodes.ts | 72 ++++++++++++++ src/constants/errorMessages.ts | 69 +++++++++++++ src/lib/mongodb.ts | 8 +- src/lib/services/auth-server.service.ts | 12 ++- src/lib/services/base-api.service.ts | 21 ++-- src/lib/services/book.service.ts | 24 +++-- src/lib/services/config-db.service.ts | 124 +++++++++++++++--------- src/lib/services/debug.service.ts | 22 +++-- src/lib/services/favorite.service.ts | 10 +- src/lib/services/home.service.ts | 18 ++-- src/lib/services/image.service.ts | 4 +- src/lib/services/library.service.ts | 34 +++++-- src/lib/services/preferences.service.ts | 16 ++- src/lib/services/series.service.ts | 31 ++++-- src/lib/services/test.service.ts | 15 +-- src/utils/errors.ts | 34 +++++++ 16 files changed, 389 insertions(+), 125 deletions(-) create mode 100644 src/constants/errorCodes.ts create mode 100644 src/constants/errorMessages.ts create mode 100644 src/utils/errors.ts diff --git a/src/constants/errorCodes.ts b/src/constants/errorCodes.ts new file mode 100644 index 0000000..ed06af8 --- /dev/null +++ b/src/constants/errorCodes.ts @@ -0,0 +1,72 @@ +export const ERROR_CODES = { + MONGODB: { + MISSING_URI: "MONGODB_MISSING_URI", + CONNECTION_FAILED: "MONGODB_CONNECTION_FAILED", + }, + AUTH: { + UNAUTHENTICATED: "AUTH_UNAUTHENTICATED", + INVALID_CREDENTIALS: "AUTH_INVALID_CREDENTIALS", + PASSWORD_NOT_STRONG: "AUTH_PASSWORD_NOT_STRONG", + EMAIL_EXISTS: "AUTH_EMAIL_EXISTS", + INVALID_USER_DATA: "AUTH_INVALID_USER_DATA", + }, + KOMGA: { + MISSING_CONFIG: "KOMGA_MISSING_CONFIG", + MISSING_CREDENTIALS: "KOMGA_MISSING_CREDENTIALS", + CONNECTION_ERROR: "KOMGA_CONNECTION_ERROR", + HTTP_ERROR: "KOMGA_HTTP_ERROR", + SERVER_UNREACHABLE: "KOMGA_SERVER_UNREACHABLE", + }, + CONFIG: { + SAVE_ERROR: "CONFIG_SAVE_ERROR", + FETCH_ERROR: "CONFIG_FETCH_ERROR", + TTL_SAVE_ERROR: "CONFIG_TTL_SAVE_ERROR", + TTL_FETCH_ERROR: "CONFIG_TTL_FETCH_ERROR", + }, + LIBRARY: { + NOT_FOUND: "LIBRARY_NOT_FOUND", + FETCH_ERROR: "LIBRARY_FETCH_ERROR", + }, + SERIES: { + FETCH_ERROR: "SERIES_FETCH_ERROR", + NO_BOOKS_FOUND: "SERIES_NO_BOOKS_FOUND", + }, + BOOK: { + NOT_FOUND: "BOOK_NOT_FOUND", + PROGRESS_UPDATE_ERROR: "BOOK_PROGRESS_UPDATE_ERROR", + PROGRESS_DELETE_ERROR: "BOOK_PROGRESS_DELETE_ERROR", + PAGES_FETCH_ERROR: "BOOK_PAGES_FETCH_ERROR", + DOWNLOAD_CANCELLED: "BOOK_DOWNLOAD_CANCELLED", + }, + FAVORITE: { + ADD_ERROR: "FAVORITE_ADD_ERROR", + DELETE_ERROR: "FAVORITE_DELETE_ERROR", + FETCH_ERROR: "FAVORITE_FETCH_ERROR", + UPDATE_ERROR: "FAVORITE_UPDATE_ERROR", + }, + PREFERENCES: { + FETCH_ERROR: "PREFERENCES_FETCH_ERROR", + UPDATE_ERROR: "PREFERENCES_UPDATE_ERROR", + CONTEXT_ERROR: "PREFERENCES_CONTEXT_ERROR", + }, + CACHE: { + DELETE_ERROR: "CACHE_DELETE_ERROR", + }, + UI: { + TABS_TRIGGER_ERROR: "UI_TABS_TRIGGER_ERROR", + TABS_CONTENT_ERROR: "UI_TABS_CONTENT_ERROR", + }, + IMAGE: { + FETCH_ERROR: "IMAGE_FETCH_ERROR", + }, + HOME: { + FETCH_ERROR: "HOME_FETCH_ERROR", + }, +} as const; + +type ValueOf = T[keyof T]; +type ErrorCodeValues = ValueOf<{ + [K in keyof typeof ERROR_CODES]: ValueOf<(typeof ERROR_CODES)[K]>; +}>; + +export type ErrorCode = ErrorCodeValues; diff --git a/src/constants/errorMessages.ts b/src/constants/errorMessages.ts new file mode 100644 index 0000000..6389bc2 --- /dev/null +++ b/src/constants/errorMessages.ts @@ -0,0 +1,69 @@ +import { ERROR_CODES } from "./errorCodes"; + +export const ERROR_MESSAGES: Record = { + // MongoDB + [ERROR_CODES.MONGODB.MISSING_URI]: + "Veuillez définir la variable d'environnement MONGODB_URI dans votre fichier .env", + [ERROR_CODES.MONGODB.CONNECTION_FAILED]: "La connexion à MongoDB a échoué", + + // Auth + [ERROR_CODES.AUTH.UNAUTHENTICATED]: "Utilisateur non authentifié", + [ERROR_CODES.AUTH.INVALID_CREDENTIALS]: "Identifiants invalides", + [ERROR_CODES.AUTH.PASSWORD_NOT_STRONG]: "Le mot de passe n'est pas assez fort", + [ERROR_CODES.AUTH.EMAIL_EXISTS]: "Cette adresse email est déjà utilisée", + [ERROR_CODES.AUTH.INVALID_USER_DATA]: "Données utilisateur invalides", + + // Komga + [ERROR_CODES.KOMGA.MISSING_CONFIG]: "Configuration Komga non trouvée", + [ERROR_CODES.KOMGA.MISSING_CREDENTIALS]: "Credentials Komga manquants", + [ERROR_CODES.KOMGA.CONNECTION_ERROR]: "Erreur lors du test de connexion", + [ERROR_CODES.KOMGA.HTTP_ERROR]: "Erreur HTTP: {status} {statusText}", + [ERROR_CODES.KOMGA.SERVER_UNREACHABLE]: + "Impossible de se connecter au serveur. Vérifiez l'URL et que le serveur est accessible.", + + // Library + [ERROR_CODES.LIBRARY.NOT_FOUND]: "Bibliothèque {libraryId} non trouvée", + [ERROR_CODES.LIBRARY.FETCH_ERROR]: "Erreur lors de la récupération des bibliothèques", + + // Series + [ERROR_CODES.SERIES.FETCH_ERROR]: "Erreur lors de la récupération des séries", + [ERROR_CODES.SERIES.NO_BOOKS_FOUND]: "Aucun livre trouvé dans la série", + + // Book + [ERROR_CODES.BOOK.NOT_FOUND]: "Livre non trouvé", + [ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR]: "Erreur lors de la mise à jour de la progression", + [ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR]: "Erreur lors de la suppression de la progression", + [ERROR_CODES.BOOK.PAGES_FETCH_ERROR]: "Erreur lors de la récupération des pages", + [ERROR_CODES.BOOK.DOWNLOAD_CANCELLED]: "Téléchargement annulé", + + // Favorite + [ERROR_CODES.FAVORITE.ADD_ERROR]: "Erreur lors de l'ajout aux favoris", + [ERROR_CODES.FAVORITE.DELETE_ERROR]: "Erreur lors de la suppression des favoris", + [ERROR_CODES.FAVORITE.FETCH_ERROR]: "Erreur lors de la récupération des favoris", + [ERROR_CODES.FAVORITE.UPDATE_ERROR]: "Erreur lors de la modification des favoris", + + // Preferences + [ERROR_CODES.PREFERENCES.FETCH_ERROR]: "Erreur lors de la récupération des préférences", + [ERROR_CODES.PREFERENCES.UPDATE_ERROR]: "Erreur lors de la mise à jour des préférences", + [ERROR_CODES.PREFERENCES.CONTEXT_ERROR]: + "usePreferences doit être utilisé dans un PreferencesProvider", + + // Cache + [ERROR_CODES.CACHE.DELETE_ERROR]: "Erreur lors de la suppression du cache", + + // UI + [ERROR_CODES.UI.TABS_TRIGGER_ERROR]: "TabsTrigger doit être utilisé dans un composant Tabs", + [ERROR_CODES.UI.TABS_CONTENT_ERROR]: "TabsContent doit être utilisé dans un composant Tabs", + + // Image + [ERROR_CODES.IMAGE.FETCH_ERROR]: "Erreur lors de la récupération de l'image", + + // Home + [ERROR_CODES.HOME.FETCH_ERROR]: "Erreur lors de la récupération des données de la page d'accueil", + + // Config + [ERROR_CODES.CONFIG.SAVE_ERROR]: "Erreur lors de la sauvegarde de la configuration", + [ERROR_CODES.CONFIG.FETCH_ERROR]: "Erreur lors de la récupération de la configuration", + [ERROR_CODES.CONFIG.TTL_SAVE_ERROR]: "Erreur lors de la sauvegarde de la configuration TTL", + [ERROR_CODES.CONFIG.TTL_FETCH_ERROR]: "Erreur lors de la récupération de la configuration TTL", +} as const; diff --git a/src/lib/mongodb.ts b/src/lib/mongodb.ts index af5f061..2532ff5 100644 --- a/src/lib/mongodb.ts +++ b/src/lib/mongodb.ts @@ -1,11 +1,11 @@ import mongoose from "mongoose"; +import { ERROR_CODES } from "../constants/errorCodes"; +import { AppError } from "../utils/errors"; const MONGODB_URI = process.env.MONGODB_URI; if (!MONGODB_URI) { - throw new Error( - "Veuillez définir la variable d'environnement MONGODB_URI dans votre fichier .env" - ); + throw new AppError(ERROR_CODES.MONGODB.MISSING_URI); } interface MongooseCache { @@ -42,7 +42,7 @@ async function connectDB(): Promise { cached.conn = await cached.promise; } catch (e) { cached.promise = null; - throw e; + throw new AppError(ERROR_CODES.MONGODB.CONNECTION_FAILED, {}, e); } return cached.conn; diff --git a/src/lib/services/auth-server.service.ts b/src/lib/services/auth-server.service.ts index 4ea7369..e143805 100644 --- a/src/lib/services/auth-server.service.ts +++ b/src/lib/services/auth-server.service.ts @@ -2,6 +2,8 @@ import { cookies } from "next/headers"; import connectDB from "@/lib/mongodb"; import { UserModel } from "@/lib/models/user.model"; import bcrypt from "bcrypt"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; interface UserData { id: string; @@ -18,13 +20,13 @@ export class AuthServerService { //check if password is strong if (!AuthServerService.isPasswordStrong(password)) { - throw new Error("PASSWORD_NOT_STRONG"); + throw new AppError(ERROR_CODES.AUTH.PASSWORD_NOT_STRONG); } // Check if user already exists const existingUser = await UserModel.findOne({ email: email.toLowerCase() }); if (existingUser) { - throw new Error("EMAIL_EXISTS"); + throw new AppError(ERROR_CODES.AUTH.EMAIL_EXISTS); } // Hash password @@ -98,13 +100,15 @@ export class AuthServerService { await connectDB(); const user = await UserModel.findOne({ email: email.toLowerCase() }); + if (!user) { - throw new Error("INVALID_CREDENTIALS"); + throw new AppError(ERROR_CODES.AUTH.INVALID_CREDENTIALS); } const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { - throw new Error("INVALID_CREDENTIALS"); + throw new AppError(ERROR_CODES.AUTH.INVALID_CREDENTIALS); } const userData: UserData = { diff --git a/src/lib/services/base-api.service.ts b/src/lib/services/base-api.service.ts index e94b459..75e50ec 100644 --- a/src/lib/services/base-api.service.ts +++ b/src/lib/services/base-api.service.ts @@ -2,6 +2,8 @@ import { AuthConfig } from "@/types/auth"; import { getServerCacheService } from "./server-cache.service"; import { ConfigDBService } from "./config-db.service"; import { DebugService } from "./debug.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; // Types de cache disponibles export type CacheType = "DEFAULT" | "HOME" | "LIBRARIES" | "SERIES" | "BOOKS" | "IMAGES"; @@ -25,13 +27,13 @@ export abstract class BaseApiService { }; } catch (error) { console.error("Erreur lors de la récupération de la configuration:", error); - throw new Error("Configuration Komga non trouvée"); + throw new AppError(ERROR_CODES.KOMGA.MISSING_CONFIG, {}, error); } } protected static getAuthHeaders(config: AuthConfig): Headers { if (!config.authHeader) { - throw new Error("Credentials Komga manquants"); + throw new AppError(ERROR_CODES.KOMGA.MISSING_CREDENTIALS); } return new Headers({ @@ -56,16 +58,6 @@ export abstract class BaseApiService { } } - protected static handleError(error: unknown, defaultMessage: string): never { - console.error("API Error:", error); - - if (error instanceof Error) { - throw error; - } - - throw new Error(defaultMessage); - } - protected static buildUrl( config: AuthConfig, path: string, @@ -114,7 +106,10 @@ export abstract class BaseApiService { }); if (!response.ok) { - throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`); + throw new AppError(ERROR_CODES.KOMGA.HTTP_ERROR, { + status: response.status, + statusText: response.statusText, + }); } return options.isImage ? response : response.json(); diff --git a/src/lib/services/book.service.ts b/src/lib/services/book.service.ts index 74219f5..f1d2591 100644 --- a/src/lib/services/book.service.ts +++ b/src/lib/services/book.service.ts @@ -2,6 +2,8 @@ import { BaseApiService } from "./base-api.service"; import { KomgaBook } from "@/types/komga"; import { ImageService } from "./image.service"; import { PreferencesService } from "./preferences.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; export class BookService extends BaseApiService { static async getBook(bookId: string): Promise<{ book: KomgaBook; pages: number[] }> { @@ -25,7 +27,7 @@ export class BookService extends BaseApiService { "BOOKS" ); } catch (error) { - return this.handleError(error, "Impossible de récupérer le tome"); + throw new AppError(ERROR_CODES.BOOK.NOT_FOUND, {}, error); } } @@ -47,10 +49,13 @@ export class BookService extends BaseApiService { }); if (!response.ok) { - throw new Error("Erreur lors de la mise à jour de la progression"); + throw new AppError(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR); } } catch (error) { - return this.handleError(error, "Impossible de mettre à jour la progression"); + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR, {}, error); } } @@ -67,10 +72,13 @@ export class BookService extends BaseApiService { }); if (!response.ok) { - throw new Error("Erreur lors de la suppression de la progression"); + throw new AppError(ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR); } } catch (error) { - return this.handleError(error, "Impossible de supprimer la progression"); + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR, {}, error); } } @@ -88,7 +96,7 @@ export class BookService extends BaseApiService { }, }); } catch (error) { - throw this.handleError(error, "Impossible de récupérer la page"); + throw new AppError(ERROR_CODES.BOOK.PAGES_FETCH_ERROR, {}, error); } } @@ -111,7 +119,7 @@ export class BookService extends BaseApiService { // 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 couverture"); + throw new AppError(ERROR_CODES.BOOK.PAGES_FETCH_ERROR, {}, error); } } @@ -135,7 +143,7 @@ export class BookService extends BaseApiService { }, }); } catch (error) { - throw this.handleError(error, "Impossible de récupérer la miniature de la page"); + throw new AppError(ERROR_CODES.BOOK.PAGES_FETCH_ERROR, {}, error); } } diff --git a/src/lib/services/config-db.service.ts b/src/lib/services/config-db.service.ts index 4f44eab..e60488b 100644 --- a/src/lib/services/config-db.service.ts +++ b/src/lib/services/config-db.service.ts @@ -3,6 +3,8 @@ import { KomgaConfig } from "@/lib/models/config.model"; import { TTLConfig } from "@/lib/models/ttl-config.model"; import { DebugService } from "./debug.service"; import { AuthServerService } from "./auth-server.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; interface User { id: string; @@ -29,66 +31,94 @@ export class ConfigDBService { private static getCurrentUser(): User { const user = AuthServerService.getCurrentUser(); if (!user) { - throw new Error("Utilisateur non authentifié"); + throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED); } return user; } static async saveConfig(data: KomgaConfigData) { - const user = this.getCurrentUser(); - await connectDB(); + try { + const user = this.getCurrentUser(); + await connectDB(); - const authHeader = Buffer.from(`${data.username}:${data.password}`).toString("base64"); + const authHeader = Buffer.from(`${data.username}:${data.password}`).toString("base64"); - const config = await KomgaConfig.findOneAndUpdate( - { userId: user.id }, - { - userId: user.id, - url: data.url, - username: data.username, - // password: data.password, - authHeader, - }, - { upsert: true, new: true } - ); - - return config; - } - - static async getConfig() { - const user = this.getCurrentUser(); - await connectDB(); - - return DebugService.measureMongoOperation("getConfig", async () => { - const config = await KomgaConfig.findOne({ userId: user.id }); - return config; - }); - } - - static async getTTLConfig() { - const user = this.getCurrentUser(); - await connectDB(); - - return DebugService.measureMongoOperation("getTTLConfig", async () => { - const config = await TTLConfig.findOne({ userId: user.id }); - return config; - }); - } - - static async saveTTLConfig(data: TTLConfigData) { - const user = this.getCurrentUser(); - await connectDB(); - - return DebugService.measureMongoOperation("saveTTLConfig", async () => { - const config = await TTLConfig.findOneAndUpdate( + const config = await KomgaConfig.findOneAndUpdate( { userId: user.id }, { userId: user.id, - ...data, + url: data.url, + username: data.username, + // password: data.password, + authHeader, }, { upsert: true, new: true } ); + return config; - }); + } catch (error) { + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.CONFIG.SAVE_ERROR, {}, error); + } + } + + static async getConfig() { + try { + const user = this.getCurrentUser(); + await connectDB(); + + return DebugService.measureMongoOperation("getConfig", async () => { + const config = await KomgaConfig.findOne({ userId: user.id }); + return config; + }); + } catch (error) { + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.CONFIG.FETCH_ERROR, {}, error); + } + } + + static async getTTLConfig() { + try { + const user = this.getCurrentUser(); + await connectDB(); + + return DebugService.measureMongoOperation("getTTLConfig", async () => { + const config = await TTLConfig.findOne({ userId: user.id }); + return config; + }); + } catch (error) { + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.CONFIG.TTL_FETCH_ERROR, {}, error); + } + } + + static async saveTTLConfig(data: TTLConfigData) { + try { + const user = this.getCurrentUser(); + await connectDB(); + + return DebugService.measureMongoOperation("saveTTLConfig", async () => { + const config = await TTLConfig.findOneAndUpdate( + { userId: user.id }, + { + userId: user.id, + ...data, + }, + { upsert: true, new: true } + ); + return config; + }); + } catch (error) { + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.CONFIG.TTL_SAVE_ERROR, {}, error); + } } } diff --git a/src/lib/services/debug.service.ts b/src/lib/services/debug.service.ts index d553963..4d3afc2 100644 --- a/src/lib/services/debug.service.ts +++ b/src/lib/services/debug.service.ts @@ -3,6 +3,8 @@ import path from "path"; import { CacheType } from "./base-api.service"; import { AuthServerService } from "./auth-server.service"; import { PreferencesService } from "./preferences.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; interface RequestTiming { url: string; @@ -26,7 +28,7 @@ export class DebugService { private static getCurrentUserId(): string { const user = AuthServerService.getCurrentUser(); if (!user) { - throw new Error("Utilisateur non authentifié"); + throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED); } return user.id; } @@ -73,7 +75,7 @@ export class DebugService { await fs.writeFile(filePath, JSON.stringify(logs, null, 2)); } catch (error) { - // Ignore les erreurs de logging + // On ignore les erreurs de logging mais on les trace quand même console.error("Erreur lors de l'enregistrement du log:", error); } } @@ -84,7 +86,10 @@ export class DebugService { const filePath = this.getLogFilePath(userId); const content = await fs.readFile(filePath, "utf-8"); return JSON.parse(content); - } catch { + } catch (error) { + if (error instanceof AppError) { + throw error; + } return []; } } @@ -94,8 +99,11 @@ export class DebugService { const userId = await this.getCurrentUserId(); const filePath = this.getLogFilePath(userId); await fs.writeFile(filePath, "[]"); - } catch { - // Ignore les erreurs si le fichier n'existe pas + } catch (error) { + if (error instanceof AppError) { + throw error; + } + // On ignore les autres erreurs si le fichier n'existe pas } } @@ -136,8 +144,8 @@ export class DebugService { await fs.writeFile(filePath, JSON.stringify(logs, null, 2)); } catch (error) { - // Ignore les erreurs de logging - console.error("Erreur lors de l'enregistrement du log de page:", error); + // On ignore les erreurs de logging mais on les trace quand même + console.error("Erreur lors de l'enregistrement du log de rendu:", error); } } diff --git a/src/lib/services/favorite.service.ts b/src/lib/services/favorite.service.ts index 7bd682c..114115b 100644 --- a/src/lib/services/favorite.service.ts +++ b/src/lib/services/favorite.service.ts @@ -2,6 +2,8 @@ import connectDB from "@/lib/mongodb"; import { FavoriteModel } from "@/lib/models/favorite.model"; import { DebugService } from "./debug.service"; import { AuthServerService } from "./auth-server.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; interface User { id: string; @@ -21,7 +23,7 @@ export class FavoriteService { private static getCurrentUser(): User { const user = AuthServerService.getCurrentUser(); if (!user) { - throw new Error("Utilisateur non authentifié"); + throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED); } return user; } @@ -65,8 +67,7 @@ export class FavoriteService { this.dispatchFavoritesChanged(); } catch (error) { - console.error("Erreur lors de l'ajout aux favoris:", error); - throw new Error("Erreur lors de l'ajout aux favoris"); + throw new AppError(ERROR_CODES.FAVORITE.ADD_ERROR, {}, error); } } @@ -87,8 +88,7 @@ export class FavoriteService { this.dispatchFavoritesChanged(); } catch (error) { - console.error("Erreur lors de la suppression des favoris:", error); - throw new Error("Erreur lors de la suppression des favoris"); + throw new AppError(ERROR_CODES.FAVORITE.DELETE_ERROR, {}, error); } } diff --git a/src/lib/services/home.service.ts b/src/lib/services/home.service.ts index caa9d7c..5cc46aa 100644 --- a/src/lib/services/home.service.ts +++ b/src/lib/services/home.service.ts @@ -2,6 +2,8 @@ import { BaseApiService } from "./base-api.service"; import { KomgaBook, KomgaSeries } from "@/types/komga"; import { LibraryResponse } from "@/types/library"; import { getServerCacheService } from "./server-cache.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; interface HomeData { ongoing: KomgaSeries[]; @@ -78,15 +80,19 @@ export class HomeService extends BaseApiService { latestSeries: latestSeries.content || [], }; } catch (error) { - return this.handleError(error, "Impossible de récupérer les données de la page d'accueil"); + throw new AppError(ERROR_CODES.HOME.FETCH_ERROR, {}, error); } } static async invalidateHomeCache(): Promise { - const cacheService = await getServerCacheService(); - await cacheService.delete("home-ongoing"); - await cacheService.delete("home-recently-read"); - await cacheService.delete("home-on-deck"); - await cacheService.delete("home-latest-series"); + try { + const cacheService = await getServerCacheService(); + await cacheService.delete("home-ongoing"); + await cacheService.delete("home-recently-read"); + await cacheService.delete("home-on-deck"); + await cacheService.delete("home-latest-series"); + } catch (error) { + throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error); + } } } diff --git a/src/lib/services/image.service.ts b/src/lib/services/image.service.ts index 97f2ba6..89146fc 100644 --- a/src/lib/services/image.service.ts +++ b/src/lib/services/image.service.ts @@ -1,4 +1,6 @@ import { BaseApiService } from "./base-api.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; interface ImageResponse { buffer: Buffer; @@ -27,7 +29,7 @@ export class ImageService extends BaseApiService { ); } catch (error) { console.error("Erreur lors de la récupération de l'image:", error); - return this.handleError(error, "Impossible de récupérer l'image"); + throw new AppError(ERROR_CODES.IMAGE.FETCH_ERROR, {}, error); } } diff --git a/src/lib/services/library.service.ts b/src/lib/services/library.service.ts index e053436..7fac1e0 100644 --- a/src/lib/services/library.service.ts +++ b/src/lib/services/library.service.ts @@ -2,6 +2,8 @@ import { BaseApiService } from "./base-api.service"; import { Library, LibraryResponse } from "@/types/library"; import { Series } from "@/types/series"; import { getServerCacheService } from "./server-cache.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; export class LibraryService extends BaseApiService { static async getLibraries(): Promise { @@ -12,17 +14,24 @@ export class LibraryService extends BaseApiService { "LIBRARIES" ); } catch (error) { - return this.handleError(error, "Impossible de récupérer les bibliothèques"); + throw new AppError(ERROR_CODES.LIBRARY.FETCH_ERROR, {}, error); } } static async getLibrary(libraryId: string): Promise { - const libraries = await this.getLibraries(); - const library = libraries.find((library) => library.id === libraryId); - if (!library) { - throw new Error(`Bibliothèque ${libraryId} non trouvée`); + try { + const libraries = await this.getLibraries(); + const library = libraries.find((library) => library.id === libraryId); + if (!library) { + throw new AppError(ERROR_CODES.LIBRARY.NOT_FOUND, { libraryId }); + } + return library; + } catch (error) { + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.LIBRARY.FETCH_ERROR, {}, error); } - return library; } static async getAllLibrarySeries(libraryId: string): Promise { @@ -60,7 +69,7 @@ export class LibraryService extends BaseApiService { return response.content; } catch (error) { - return this.handleError(error, "Impossible de récupérer toutes les séries"); + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } @@ -130,12 +139,17 @@ export class LibraryService extends BaseApiService { totalPages, }; } catch (error) { - return this.handleError(error, "Impossible de récupérer les séries"); + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } static async invalidateLibrarySeriesCache(libraryId: string): Promise { - const cacheService = await getServerCacheService(); - await cacheService.delete(`library-${libraryId}-all-series`); + try { + const cacheService = await getServerCacheService(); + const cacheKey = `library-${libraryId}-all-series`; + await cacheService.delete(cacheKey); + } catch (error) { + throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error); + } } } diff --git a/src/lib/services/preferences.service.ts b/src/lib/services/preferences.service.ts index 6658e48..5eba08c 100644 --- a/src/lib/services/preferences.service.ts +++ b/src/lib/services/preferences.service.ts @@ -1,5 +1,7 @@ import { PreferencesModel } from "@/lib/models/preferences.model"; import { AuthServerService } from "./auth-server.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; interface User { id: string; @@ -24,7 +26,7 @@ export class PreferencesService { static getCurrentUser(): User { const user = AuthServerService.getCurrentUser(); if (!user) { - throw new Error("Utilisateur non authentifié"); + throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED); } return user; } @@ -41,8 +43,10 @@ export class PreferencesService { ...preferences.toObject(), }; } catch (error) { - console.error("Error getting preferences:", error); - return defaultPreferences; + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.PREFERENCES.FETCH_ERROR, {}, error); } } @@ -61,8 +65,10 @@ export class PreferencesService { }; return result; } catch (error) { - console.error("Error updating preferences:", error); - throw error; + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.PREFERENCES.UPDATE_ERROR, {}, error); } } } diff --git a/src/lib/services/series.service.ts b/src/lib/services/series.service.ts index b2de200..2077a3b 100644 --- a/src/lib/services/series.service.ts +++ b/src/lib/services/series.service.ts @@ -5,6 +5,8 @@ import { BookService } from "./book.service"; import { ImageService } from "./image.service"; import { PreferencesService } from "./preferences.service"; import { getServerCacheService } from "./server-cache.service"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; export class SeriesService extends BaseApiService { static async getSeries(seriesId: string): Promise { @@ -15,13 +17,17 @@ export class SeriesService extends BaseApiService { "SERIES" ); } catch (error) { - return this.handleError(error, "Impossible de récupérer la série"); + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } static async invalidateSeriesCache(seriesId: string): Promise { - const cacheService = await getServerCacheService(); - await cacheService.delete(`series-${seriesId}`); + try { + const cacheService = await getServerCacheService(); + await cacheService.delete(`series-${seriesId}`); + } catch (error) { + throw new AppError(ERROR_CODES.CACHE.DELETE_ERROR, {}, error); + } } static async getAllSeriesBooks(seriesId: string): Promise { @@ -57,9 +63,16 @@ export class SeriesService extends BaseApiService { "BOOKS" ); + if (!response.content.length) { + throw new AppError(ERROR_CODES.SERIES.NO_BOOKS_FOUND); + } + return response.content; } catch (error) { - return this.handleError(error, "Impossible de récupérer tous les tomes"); + if (error instanceof AppError) { + throw error; + } + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } @@ -122,7 +135,7 @@ export class SeriesService extends BaseApiService { totalPages, }; } catch (error) { - return this.handleError(error, "Impossible de récupérer les tomes"); + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } @@ -141,7 +154,7 @@ export class SeriesService extends BaseApiService { params: { page: "0", size: "1" }, }); if (!data.content || data.content.length === 0) { - throw new Error("Aucun livre trouvé dans la série"); + throw new AppError(ERROR_CODES.SERIES.NO_BOOKS_FOUND); } return data.content[0].id; @@ -150,7 +163,7 @@ export class SeriesService extends BaseApiService { ); } catch (error) { console.error("Erreur lors de la récupération du premier livre:", error); - return this.handleError(error, "Impossible de récupérer le premier livre"); + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } @@ -175,7 +188,7 @@ 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 couverture"); + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } @@ -189,7 +202,7 @@ export class SeriesService extends BaseApiService { const series = await Promise.all(seriesPromises); return series.filter(Boolean); } catch (error) { - return this.handleError(error, "Impossible de récupérer les séries"); + throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR, {}, error); } } } diff --git a/src/lib/services/test.service.ts b/src/lib/services/test.service.ts index 3aabf1a..5e4dcbf 100644 --- a/src/lib/services/test.service.ts +++ b/src/lib/services/test.service.ts @@ -1,6 +1,8 @@ import { BaseApiService } from "./base-api.service"; import { AuthConfig } from "@/types/auth"; import { KomgaLibrary } from "@/types/komga"; +import { ERROR_CODES } from "../../constants/errorCodes"; +import { AppError } from "../../utils/errors"; export class TestService extends BaseApiService { static async testConnection(config: AuthConfig): Promise<{ libraries: KomgaLibrary[] }> { @@ -12,19 +14,20 @@ export class TestService extends BaseApiService { if (!response.ok) { const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || "Erreur lors du test de connexion"); + throw new AppError(ERROR_CODES.KOMGA.CONNECTION_ERROR, { message: errorData.message }); } const libraries = await response.json(); return { libraries }; } catch (error) { console.error("Erreur lors du test de connexion:", error); - if (error instanceof Error && error.message.includes("fetch")) { - throw new Error( - "Impossible de se connecter au serveur. Vérifiez l'URL et que le serveur est accessible." - ); + if (error instanceof AppError) { + throw error; } - throw error instanceof Error ? error : new Error("Erreur lors du test de connexion"); + if (error instanceof Error && error.message.includes("fetch")) { + throw new AppError(ERROR_CODES.KOMGA.SERVER_UNREACHABLE); + } + throw new AppError(ERROR_CODES.KOMGA.CONNECTION_ERROR, {}, error); } } } diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..f642b69 --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,34 @@ +import { ERROR_MESSAGES } from "../constants/errorMessages"; +import { ErrorCode } from "../constants/errorCodes"; + +export class AppError extends Error { + constructor( + public code: ErrorCode, + public params: Record = {}, + public originalError?: unknown + ) { + let message = ERROR_MESSAGES[code]; + + // Replace parameters in message + Object.entries(params).forEach(([key, value]) => { + message = message.replace(`{${key}}`, String(value)); + }); + + super(message); + this.name = "AppError"; + } +} + +export const getErrorMessage = ( + code: ErrorCode, + params: Record = {} +): string => { + let message = ERROR_MESSAGES[code]; + + // Replace parameters in message + Object.entries(params).forEach(([key, value]) => { + message = message.replace(`{${key}}`, String(value)); + }); + + return message; +};