feat: debugmode full request

This commit is contained in:
Julien Froidefond
2025-02-23 15:12:59 +01:00
parent 1cffe6913a
commit 6b19f5b54b
18 changed files with 721 additions and 133 deletions

View File

@@ -0,0 +1,16 @@
import { DebugService } from "@/lib/services/debug.service";
type PageComponent = (props: any) => Promise<JSX.Element> | JSX.Element;
export function withPageTiming(pageName: string, Component: PageComponent) {
return async function PageWithTiming(props: any) {
const start = performance.now();
const result = await Promise.resolve(Component(props));
const duration = performance.now() - start;
// Log le temps de rendu
await DebugService.logPageRender(pageName, duration);
return result;
};
}

View File

@@ -1,6 +1,7 @@
import { AuthConfig } from "@/types/auth";
import { serverCacheService } from "./server-cache.service";
import { ConfigDBService } from "./config-db.service";
import { DebugService } from "./debug.service";
// Types de cache disponibles
export type CacheType = "DEFAULT" | "HOME" | "LIBRARIES" | "SERIES" | "BOOKS" | "IMAGES";
@@ -51,7 +52,36 @@ export abstract class BaseApiService {
fetcher: () => Promise<T>,
type: CacheType = "DEFAULT"
): Promise<T> {
return serverCacheService.getOrSet(key, fetcher, type);
const startTime = performance.now();
try {
const result = await serverCacheService.getOrSet(key, fetcher, type);
const endTime = performance.now();
// Log la requête avec l'indication du cache
await DebugService.logRequest({
url: key,
startTime,
endTime,
fromCache: true,
cacheType: type,
});
return result;
} catch (error) {
const endTime = performance.now();
// Log aussi les erreurs
await DebugService.logRequest({
url: key,
startTime,
endTime,
fromCache: true,
cacheType: type,
});
throw error;
}
}
protected static handleError(error: unknown, defaultMessage: string): never {
@@ -87,6 +117,7 @@ export abstract class BaseApiService {
headersOptions = {},
options: KomgaRequestInit = {}
): Promise<T> {
const startTime = performance.now();
const config = await this.getKomgaConfig();
const { path, params } = urlBuilder;
const url = this.buildUrl(config, path, params);
@@ -97,16 +128,36 @@ export abstract class BaseApiService {
headers.set(key as string, value as string);
}
}
// console.log("🛜 Fetching from", url);
// console.log("Headers", headers);
// console.log("headersOptions", headersOptions);
// console.log("options", options);
const response = await fetch(url, { headers, ...options });
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
try {
const response = await fetch(url, { headers, ...options });
const endTime = performance.now();
// Log la requête
await DebugService.logRequest({
url: path,
startTime,
endTime,
fromCache: false,
});
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
}
return options.isImage ? response : response.json();
} catch (error) {
const endTime = performance.now();
// Log aussi les erreurs
await DebugService.logRequest({
url: path,
startTime,
endTime,
fromCache: false,
});
throw error;
}
return options.isImage ? response : response.json();
}
}

View File

@@ -2,6 +2,8 @@ import { cookies } from "next/headers";
import connectDB from "@/lib/mongodb";
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";
interface User {
id: string;
@@ -24,93 +26,67 @@ interface TTLConfigData {
}
export class ConfigDBService {
private static async getCurrentUser(): Promise<User> {
const userCookie = cookies().get("stripUser");
if (!userCookie) {
private static getCurrentUser(): User {
const user = AuthServerService.getCurrentUser();
if (!user) {
throw new Error("Utilisateur non authentifié");
}
try {
return JSON.parse(atob(userCookie.value));
} catch (error) {
console.error("Erreur lors de la récupération de l'utilisateur depuis le cookie:", error);
throw new Error("Utilisateur non authentifié");
}
}
static async saveConfig(data: KomgaConfigData) {
const user = await this.getCurrentUser();
await connectDB();
const config = await KomgaConfig.findOneAndUpdate(
{ userId: user.id },
{
userId: user.id,
url: data.url,
username: data.username,
password: data.password,
},
{ upsert: true, new: true }
);
return config;
return user;
}
static async getConfig() {
const user = await this.getCurrentUser();
const user = this.getCurrentUser();
await connectDB();
const config = await KomgaConfig.findOne({ userId: user.id });
if (!config) {
throw new Error("Configuration non trouvée");
}
return config;
return DebugService.measureMongoOperation("getConfig", async () => {
const config = await KomgaConfig.findOne({ userId: user.id });
return config;
});
}
static async saveTTLConfig(data: TTLConfigData) {
const user = await this.getCurrentUser();
static async saveConfig(data: KomgaConfigData) {
const user = this.getCurrentUser();
await connectDB();
const config = await TTLConfig.findOneAndUpdate(
{ userId: user.id },
{
userId: user.id,
...data,
},
{ upsert: true, new: true }
);
return config;
return DebugService.measureMongoOperation("saveConfig", async () => {
const config = await KomgaConfig.findOneAndUpdate(
{ userId: user.id },
{
userId: user.id,
url: data.url,
username: data.username,
password: data.password,
},
{ upsert: true, new: true }
);
return config;
});
}
static async getTTLConfig() {
const user = await this.getCurrentUser();
const user = this.getCurrentUser();
await connectDB();
const config = await TTLConfig.findOne({ userId: user.id });
return DebugService.measureMongoOperation("getTTLConfig", async () => {
const config = await TTLConfig.findOne({ userId: user.id });
return config;
});
}
if (!config) {
// Retourner la configuration par défaut si aucune configuration n'existe
return {
defaultTTL: 5,
homeTTL: 5,
librariesTTL: 1440,
seriesTTL: 5,
booksTTL: 5,
imagesTTL: 1440,
};
}
static async saveTTLConfig(data: TTLConfigData) {
const user = this.getCurrentUser();
await connectDB();
return {
defaultTTL: config.defaultTTL,
homeTTL: config.homeTTL,
librariesTTL: config.librariesTTL,
seriesTTL: config.seriesTTL,
booksTTL: config.booksTTL,
imagesTTL: config.imagesTTL,
};
return DebugService.measureMongoOperation("saveTTLConfig", async () => {
const config = await TTLConfig.findOneAndUpdate(
{ userId: user.id },
{
userId: user.id,
...data,
},
{ upsert: true, new: true }
);
return config;
});
}
}

View File

@@ -0,0 +1,181 @@
import fs from "fs/promises";
import path from "path";
import { CacheType } from "./base-api.service";
import { AuthServerService } from "./auth-server.service";
import { PreferencesService } from "./preferences.service";
interface RequestTiming {
url: string;
startTime: number;
endTime: number;
duration: number;
timestamp: string;
fromCache: boolean;
cacheType?: CacheType;
mongoAccess?: {
operation: string;
duration: number;
};
pageRender?: {
page: string;
duration: number;
};
}
export class DebugService {
private static getCurrentUserId(): string {
const user = AuthServerService.getCurrentUser();
if (!user) {
throw new Error("Utilisateur non authentifié");
}
return user.id;
}
private static getLogFilePath(userId: string): string {
return path.join(process.cwd(), "debug-logs", `${userId}.json`);
}
private static async ensureDebugDir(): Promise<void> {
const debugDir = path.join(process.cwd(), "debug-logs");
try {
await fs.access(debugDir);
} catch {
await fs.mkdir(debugDir, { recursive: true });
}
}
static async logRequest(timing: Omit<RequestTiming, "duration" | "timestamp">) {
try {
const userId = await this.getCurrentUserId();
const preferences = await PreferencesService.getPreferences();
if (!preferences.debug) {
return;
}
await this.ensureDebugDir();
const filePath = this.getLogFilePath(userId);
let logs: RequestTiming[] = [];
try {
const content = await fs.readFile(filePath, "utf-8");
logs = JSON.parse(content);
} catch {
// Le fichier n'existe pas encore ou est vide
}
const newTiming: RequestTiming = {
...timing,
duration: timing.endTime - timing.startTime,
timestamp: new Date().toISOString(),
};
// Garde les 100 dernières requêtes
logs = [...logs.slice(-99), newTiming];
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:", error);
}
}
static async getRequestLogs(): Promise<RequestTiming[]> {
try {
const userId = await this.getCurrentUserId();
const filePath = this.getLogFilePath(userId);
const content = await fs.readFile(filePath, "utf-8");
return JSON.parse(content);
} catch {
return [];
}
}
static async clearLogs(): Promise<void> {
try {
const userId = await this.getCurrentUserId();
const filePath = this.getLogFilePath(userId);
await fs.writeFile(filePath, "[]");
} catch {
// Ignore les erreurs si le fichier n'existe pas
}
}
static async logPageRender(page: string, duration: number) {
try {
const userId = await this.getCurrentUserId();
const preferences = await PreferencesService.getPreferences();
if (!preferences.debug) {
return;
}
await this.ensureDebugDir();
const filePath = this.getLogFilePath(userId);
let logs: RequestTiming[] = [];
try {
const content = await fs.readFile(filePath, "utf-8");
logs = JSON.parse(content);
} catch {
// Le fichier n'existe pas encore ou est vide
}
const now = performance.now();
const newTiming: RequestTiming = {
url: `Page Render: ${page}`,
startTime: now - duration,
endTime: now,
duration,
timestamp: new Date().toISOString(),
fromCache: false,
pageRender: {
page,
duration,
},
};
// Garde les 100 dernières requêtes
logs = [...logs.slice(-99), newTiming];
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);
}
}
static async measureMongoOperation<T>(operation: string, func: () => Promise<T>): Promise<T> {
const startTime = performance.now();
try {
const preferences = await PreferencesService.getPreferences();
if (!preferences.debug) {
return func();
}
const result = await func();
const endTime = performance.now();
await this.logRequest({
url: `MongoDB: ${operation}`,
startTime,
endTime,
fromCache: false,
mongoAccess: {
operation,
duration: endTime - startTime,
},
});
return result;
} catch (error) {
const endTime = performance.now();
await this.logRequest({
url: `MongoDB Error: ${operation}`,
startTime,
endTime,
fromCache: false,
mongoAccess: {
operation,
duration: endTime - startTime,
},
});
throw error;
}
}
}

View File

@@ -1,6 +1,8 @@
import { cookies } from "next/headers";
import connectDB from "@/lib/mongodb";
import { FavoriteModel } from "@/lib/models/favorite.model";
import { DebugService } from "./debug.service";
import { AuthServerService } from "./auth-server.service";
interface User {
id: string;
@@ -17,19 +19,12 @@ export class FavoriteService {
}
}
private static async getCurrentUser(): Promise<User> {
const userCookie = cookies().get("stripUser");
if (!userCookie) {
throw new Error("Utilisateur non authentifié");
}
try {
return JSON.parse(atob(userCookie.value));
} catch (error) {
console.error("Erreur lors de la récupération de l'utilisateur depuis le cookie:", error);
private static getCurrentUser(): User {
const user = AuthServerService.getCurrentUser();
if (!user) {
throw new Error("Utilisateur non authentifié");
}
return user;
}
/**
@@ -37,15 +32,16 @@ export class FavoriteService {
*/
static async isFavorite(seriesId: string): Promise<boolean> {
try {
const user = await this.getCurrentUser();
const user = this.getCurrentUser();
await connectDB();
const favorite = await FavoriteModel.findOne({
userId: user.id,
seriesId: seriesId,
return DebugService.measureMongoOperation("isFavorite", async () => {
const favorite = await FavoriteModel.findOne({
userId: user.id,
seriesId: seriesId,
});
return !!favorite;
});
return !!favorite;
} catch (error) {
console.error("Erreur lors de la vérification du favori:", error);
return false;
@@ -57,14 +53,16 @@ export class FavoriteService {
*/
static async addToFavorites(seriesId: string): Promise<void> {
try {
const user = await this.getCurrentUser();
const user = this.getCurrentUser();
await connectDB();
await FavoriteModel.findOneAndUpdate(
{ userId: user.id, seriesId },
{ userId: user.id, seriesId },
{ upsert: true }
);
await DebugService.measureMongoOperation("addToFavorites", async () => {
await FavoriteModel.findOneAndUpdate(
{ userId: user.id, seriesId },
{ userId: user.id, seriesId },
{ upsert: true }
);
});
this.dispatchFavoritesChanged();
} catch (error) {
@@ -78,12 +76,14 @@ export class FavoriteService {
*/
static async removeFromFavorites(seriesId: string): Promise<void> {
try {
const user = await this.getCurrentUser();
const user = this.getCurrentUser();
await connectDB();
await FavoriteModel.findOneAndDelete({
userId: user.id,
seriesId,
await DebugService.measureMongoOperation("removeFromFavorites", async () => {
await FavoriteModel.findOneAndDelete({
userId: user.id,
seriesId,
});
});
this.dispatchFavoritesChanged();
@@ -97,15 +97,36 @@ export class FavoriteService {
* Récupère tous les IDs des séries favorites
*/
static async getAllFavoriteIds(): Promise<string[]> {
try {
const user = await this.getCurrentUser();
await connectDB();
const user = this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("getAllFavoriteIds", async () => {
const favorites = await FavoriteModel.find({ userId: user.id });
return favorites.map((favorite) => favorite.seriesId);
} catch (error) {
console.error("Erreur lors de la récupération des favoris:", error);
return [];
}
});
}
static async addFavorite(seriesId: string) {
const user = this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("addFavorite", async () => {
const favorite = await FavoriteModel.findOneAndUpdate(
{ userId: user.id, seriesId },
{ userId: user.id, seriesId },
{ upsert: true, new: true }
);
return favorite;
});
}
static async removeFavorite(seriesId: string): Promise<boolean> {
const user = this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("removeFavorite", async () => {
const result = await FavoriteModel.deleteOne({ userId: user.id, seriesId });
return result.deletedCount > 0;
});
}
}

View File

@@ -1,5 +1,6 @@
import { cookies } from "next/headers";
import { PreferencesModel } from "@/lib/models/preferences.model";
import { AuthServerService } from "./auth-server.service";
interface User {
id: string;
@@ -21,24 +22,17 @@ const defaultPreferences: UserPreferences = {
};
export class PreferencesService {
static async getCurrentUser(): Promise<User> {
const userCookie = cookies().get("stripUser");
if (!userCookie) {
throw new Error("Utilisateur non authentifié");
}
try {
return JSON.parse(atob(userCookie.value));
} catch (error) {
console.error("Erreur lors de la récupération de l'utilisateur depuis le cookie:", error);
static getCurrentUser(): User {
const user = AuthServerService.getCurrentUser();
if (!user) {
throw new Error("Utilisateur non authentifié");
}
return user;
}
static async getPreferences(): Promise<UserPreferences> {
try {
const user = await this.getCurrentUser();
const user = this.getCurrentUser();
const preferences = await PreferencesModel.findOne({ userId: user.id });
if (!preferences) {
return defaultPreferences;
@@ -55,7 +49,7 @@ export class PreferencesService {
static async updatePreferences(preferences: Partial<UserPreferences>): Promise<UserPreferences> {
try {
const user = await this.getCurrentUser();
const user = this.getCurrentUser();
const updatedPreferences = await PreferencesModel.findOneAndUpdate(
{ userId: user.id },
{ $set: preferences },