refacto: error and error codes in services
This commit is contained in:
72
src/constants/errorCodes.ts
Normal file
72
src/constants/errorCodes.ts
Normal file
@@ -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> = T[keyof T];
|
||||
type ErrorCodeValues = ValueOf<{
|
||||
[K in keyof typeof ERROR_CODES]: ValueOf<(typeof ERROR_CODES)[K]>;
|
||||
}>;
|
||||
|
||||
export type ErrorCode = ErrorCodeValues;
|
||||
69
src/constants/errorMessages.ts
Normal file
69
src/constants/errorMessages.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ERROR_CODES } from "./errorCodes";
|
||||
|
||||
export const ERROR_MESSAGES: Record<string, string> = {
|
||||
// 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;
|
||||
@@ -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<typeof mongoose> {
|
||||
cached.conn = await cached.promise;
|
||||
} catch (e) {
|
||||
cached.promise = null;
|
||||
throw e;
|
||||
throw new AppError(ERROR_CODES.MONGODB.CONNECTION_FAILED, {}, e);
|
||||
}
|
||||
|
||||
return cached.conn;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Library[]> {
|
||||
@@ -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<Library> {
|
||||
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<Series[]> {
|
||||
@@ -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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<KomgaSeries> {
|
||||
@@ -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<void> {
|
||||
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<KomgaBook[]> {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
src/utils/errors.ts
Normal file
34
src/utils/errors.ts
Normal file
@@ -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<string, string | number> = {},
|
||||
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, string | number> = {}
|
||||
): string => {
|
||||
let message = ERROR_MESSAGES[code];
|
||||
|
||||
// Replace parameters in message
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
message = message.replace(`{${key}}`, String(value));
|
||||
});
|
||||
|
||||
return message;
|
||||
};
|
||||
Reference in New Issue
Block a user