feat: debugmode full request
This commit is contained in:
16
src/lib/hoc/withPageTiming.tsx
Normal file
16
src/lib/hoc/withPageTiming.tsx
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
181
src/lib/services/debug.service.ts
Normal file
181
src/lib/services/debug.service.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user