From 6b19f5b54b3250f5abae94547d0b76e0d67269cb Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 23 Feb 2025 15:12:59 +0100 Subject: [PATCH] feat: debugmode full request --- debug-logs/67b8a2ccd785e367b3c5e37b.json | 31 +++ debug-logs/julienfroidefond@gmail.com.json | 23 +++ src/app/api/debug/route.ts | 30 +++ src/app/books/[bookId]/page.tsx | 5 +- src/app/downloads/page.tsx | 5 +- src/app/layout.tsx | 7 +- src/app/libraries/[libraryId]/page.tsx | 5 +- src/app/libraries/page.tsx | 22 +++ src/app/page.tsx | 5 +- src/app/series/[seriesId]/page.tsx | 5 +- src/components/debug/DebugInfo.tsx | 209 +++++++++++++++++++++ src/components/debug/DebugWrapper.tsx | 14 ++ src/lib/hoc/withPageTiming.tsx | 16 ++ src/lib/services/base-api.service.ts | 71 ++++++- src/lib/services/config-db.service.ts | 116 +++++------- src/lib/services/debug.service.ts | 181 ++++++++++++++++++ src/lib/services/favorite.service.ts | 89 +++++---- src/lib/services/preferences.service.ts | 20 +- 18 files changed, 721 insertions(+), 133 deletions(-) create mode 100644 debug-logs/67b8a2ccd785e367b3c5e37b.json create mode 100644 debug-logs/julienfroidefond@gmail.com.json create mode 100644 src/app/api/debug/route.ts create mode 100644 src/app/libraries/page.tsx create mode 100644 src/components/debug/DebugInfo.tsx create mode 100644 src/components/debug/DebugWrapper.tsx create mode 100644 src/lib/hoc/withPageTiming.tsx create mode 100644 src/lib/services/debug.service.ts diff --git a/debug-logs/67b8a2ccd785e367b3c5e37b.json b/debug-logs/67b8a2ccd785e367b3c5e37b.json new file mode 100644 index 0000000..44da55e --- /dev/null +++ b/debug-logs/67b8a2ccd785e367b3c5e37b.json @@ -0,0 +1,31 @@ +[ + { + "url": "books/ondeck", + "startTime": 2885483.439666003, + "endTime": 2886906.0345830023, + "fromCache": false, + "duration": 1422.59491699934, + "timestamp": "2025-02-23T14:10:34.889Z" + }, + { + "url": "home-on-deck", + "startTime": 2885483.3969579935, + "endTime": 2886915.6758749783, + "fromCache": true, + "cacheType": "HOME", + "duration": 1432.2789169847965, + "timestamp": "2025-02-23T14:10:34.901Z" + }, + { + "url": "Page Render: HomePage", + "startTime": 2885485.3594990075, + "endTime": 2886935.9214159846, + "duration": 1450.5619169771671, + "timestamp": "2025-02-23T14:10:34.912Z", + "fromCache": false, + "pageRender": { + "page": "HomePage", + "duration": 1450.5619169771671 + } + } +] \ No newline at end of file diff --git a/debug-logs/julienfroidefond@gmail.com.json b/debug-logs/julienfroidefond@gmail.com.json new file mode 100644 index 0000000..f824c1d --- /dev/null +++ b/debug-logs/julienfroidefond@gmail.com.json @@ -0,0 +1,23 @@ +[ + { + "url": "series/list", + "startTime": 1555818.8649999797, + "endTime": 1556150.4556659758, + "duration": 331.5906659960747, + "timestamp": "2025-02-23T12:27:42.757Z" + }, + { + "url": "libraries", + "startTime": 1556210.246457994, + "endTime": 1556425.7659159899, + "duration": 215.51945799589157, + "timestamp": "2025-02-23T12:27:43.141Z" + }, + { + "url": "series/0GNTC1Q53EM9R", + "startTime": 1590829.9988330007, + "endTime": 1591134.537375003, + "duration": 304.5385420024395, + "timestamp": "2025-02-23T12:28:17.719Z" + } +] \ No newline at end of file diff --git a/src/app/api/debug/route.ts b/src/app/api/debug/route.ts new file mode 100644 index 0000000..a88daec --- /dev/null +++ b/src/app/api/debug/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from "next/server"; +import { DebugService } from "@/lib/services/debug.service"; + +export async function GET() { + try { + const logs = await DebugService.getRequestLogs(); + return NextResponse.json(logs); + } catch (error) { + return NextResponse.json({ error: "Erreur lors de la récupération des logs" }, { status: 500 }); + } +} + +export async function POST(request: NextRequest) { + try { + const timing = await request.json(); + await DebugService.logRequest(timing); + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json({ error: "Erreur lors de l'enregistrement du log" }, { status: 500 }); + } +} + +export async function DELETE() { + try { + await DebugService.clearLogs(); + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json({ error: "Erreur lors de la suppression des logs" }, { status: 500 }); + } +} diff --git a/src/app/books/[bookId]/page.tsx b/src/app/books/[bookId]/page.tsx index f9f103d..0e20446 100644 --- a/src/app/books/[bookId]/page.tsx +++ b/src/app/books/[bookId]/page.tsx @@ -3,8 +3,9 @@ import { ClientBookWrapper } from "@/components/reader/ClientBookWrapper"; import { BookSkeleton } from "@/components/skeletons/BookSkeleton"; import { BookService } from "@/lib/services/book.service"; import { notFound } from "next/navigation"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; -export default async function BookPage({ params }: { params: { bookId: string } }) { +async function BookPage({ params }: { params: { bookId: string } }) { try { const data = await BookService.getBook(params.bookId); @@ -18,3 +19,5 @@ export default async function BookPage({ params }: { params: { bookId: string } notFound(); } } + +export default withPageTiming("BookPage", BookPage); diff --git a/src/app/downloads/page.tsx b/src/app/downloads/page.tsx index 71dc642..5fdfcc0 100644 --- a/src/app/downloads/page.tsx +++ b/src/app/downloads/page.tsx @@ -1,7 +1,8 @@ import { PageHeader } from "@/components/layout/PageHeader"; import { DownloadManager } from "@/components/downloads/DownloadManager"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; -export default function DownloadsPage() { +function DownloadsPage() { return ( <> @@ -9,3 +10,5 @@ export default function DownloadsPage() { ); } + +export default withPageTiming("DownloadsPage", DownloadsPage); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 40ef9be..7b7c7ab 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import { Inter } from "next/font/google"; import "@/styles/globals.css"; import { cn } from "@/lib/utils"; import ClientLayout from "@/components/layout/ClientLayout"; +import { PreferencesProvider } from "@/contexts/PreferencesContext"; +import { DebugWrapper } from "@/components/debug/DebugWrapper"; const inter = Inter({ subsets: ["latin"] }); @@ -113,7 +115,10 @@ export default function RootLayout({ children }: { children: React.ReactNode }) /> - {children} + + {children} + + ); diff --git a/src/app/libraries/[libraryId]/page.tsx b/src/app/libraries/[libraryId]/page.tsx index df7fac2..84ac29a 100644 --- a/src/app/libraries/[libraryId]/page.tsx +++ b/src/app/libraries/[libraryId]/page.tsx @@ -3,6 +3,7 @@ import { LibraryService } from "@/lib/services/library.service"; import { PreferencesService } from "@/lib/services/preferences.service"; import { revalidatePath } from "next/cache"; import { RefreshButton } from "@/components/library/RefreshButton"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; interface PageProps { params: { libraryId: string }; @@ -49,7 +50,7 @@ async function getLibrarySeries( } } -export default async function LibraryPage({ params, searchParams }: PageProps) { +async function LibraryPage({ params, searchParams }: PageProps) { const currentPage = searchParams.page ? parseInt(searchParams.page) : 1; const preferences = await PreferencesService.getPreferences(); @@ -105,3 +106,5 @@ export default async function LibraryPage({ params, searchParams }: PageProps) { ); } } + +export default withPageTiming("LibraryPage", LibraryPage); diff --git a/src/app/libraries/page.tsx b/src/app/libraries/page.tsx new file mode 100644 index 0000000..3e1c005 --- /dev/null +++ b/src/app/libraries/page.tsx @@ -0,0 +1,22 @@ +import { LibrariesContent } from "@/components/libraries/LibrariesContent"; +import { LibraryService } from "@/lib/services/library.service"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; + +async function LibrariesPage() { + try { + const libraries = await LibraryService.getLibraries(); + return ; + } catch (error) { + return ( +
+
+

+ {error instanceof Error ? error.message : "Une erreur est survenue"} +

+
+
+ ); + } +} + +export default withPageTiming("LibrariesPage", LibrariesPage); diff --git a/src/app/page.tsx b/src/app/page.tsx index d751675..3a56703 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,6 +2,7 @@ import { HomeContent } from "@/components/home/HomeContent"; import { HomeService } from "@/lib/services/home.service"; import { redirect } from "next/navigation"; import { revalidatePath } from "next/cache"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; async function refreshHome() { "use server"; @@ -16,7 +17,7 @@ async function refreshHome() { } } -export default async function HomePage() { +async function HomePage() { try { const data = await HomeService.getHomeData(); @@ -38,3 +39,5 @@ export default async function HomePage() { ); } } + +export default withPageTiming("HomePage", HomePage); diff --git a/src/app/series/[seriesId]/page.tsx b/src/app/series/[seriesId]/page.tsx index db8909b..72d4800 100644 --- a/src/app/series/[seriesId]/page.tsx +++ b/src/app/series/[seriesId]/page.tsx @@ -3,6 +3,7 @@ import { SeriesHeader } from "@/components/series/SeriesHeader"; import { SeriesService } from "@/lib/services/series.service"; import { PreferencesService } from "@/lib/services/preferences.service"; import { revalidatePath } from "next/cache"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; interface PageProps { params: { seriesId: string }; @@ -38,7 +39,7 @@ async function refreshSeries(seriesId: string) { } } -export default async function SeriesPage({ params, searchParams }: PageProps) { +async function SeriesPage({ params, searchParams }: PageProps) { const currentPage = searchParams.page ? parseInt(searchParams.page) : 1; const preferences = await PreferencesService.getPreferences(); @@ -76,3 +77,5 @@ export default async function SeriesPage({ params, searchParams }: PageProps) { ); } } + +export default withPageTiming("SeriesPage", SeriesPage); diff --git a/src/components/debug/DebugInfo.tsx b/src/components/debug/DebugInfo.tsx new file mode 100644 index 0000000..796b682 --- /dev/null +++ b/src/components/debug/DebugInfo.tsx @@ -0,0 +1,209 @@ +"use client"; + +import { usePreferences } from "@/contexts/PreferencesContext"; +import { useEffect, useState } from "react"; +import { + X, + Database, + Minimize2, + Maximize2, + Clock, + CircleDot, + Layout, + RefreshCw, + Globe, +} from "lucide-react"; +import { CacheType } from "@/lib/services/base-api.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; + }; +} + +function formatTime(timestamp: string) { + const date = new Date(timestamp); + return date.toLocaleTimeString("fr-FR", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); +} + +function formatDuration(duration: number) { + return Math.round(duration); +} + +export function DebugInfo() { + const { preferences } = usePreferences(); + const [logs, setLogs] = useState([]); + const [isMinimized, setIsMinimized] = useState(false); + const [isRefreshing, setIsRefreshing] = useState(false); + + const fetchLogs = async () => { + try { + setIsRefreshing(true); + const response = await fetch("/api/debug"); + if (response.ok) { + const data = await response.json(); + setLogs(data); + } + } catch (error) { + console.error("Erreur lors de la récupération des logs:", error); + } finally { + setIsRefreshing(false); + } + }; + + useEffect(() => { + fetchLogs(); + }, []); + + const clearLogs = async () => { + try { + await fetch("/api/debug", { method: "DELETE" }); + setLogs([]); + } catch (error) { + console.error("Erreur lors de la suppression des logs:", error); + } + }; + + const sortedLogs = [...logs].reverse(); + + return ( +
+
+

DEBUG !

+
+ + + +
+
+ + {!isMinimized && ( +
+ {sortedLogs.length === 0 ? ( +

Aucune requête enregistrée

+ ) : ( + sortedLogs.map((log, index) => ( +
+
+
+ {log.fromCache && ( +
+ +
+ )} + {log.mongoAccess && ( +
+ +
+ )} + {log.pageRender && ( +
+ +
+ )} + {!log.fromCache && !log.mongoAccess && !log.pageRender && ( +
+ +
+ )} + + {log.url} + +
+
+
+ + {formatTime(log.timestamp)} +
+ + {formatDuration(log.duration)}ms + + {log.mongoAccess && ( + + +{formatDuration(log.mongoAccess.duration)}ms + + )} +
+
+
+
+ {log.mongoAccess && ( +
+ )} +
+
+ )) + )} +
+ )} +
+ ); +} diff --git a/src/components/debug/DebugWrapper.tsx b/src/components/debug/DebugWrapper.tsx new file mode 100644 index 0000000..91120ad --- /dev/null +++ b/src/components/debug/DebugWrapper.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { usePreferences } from "@/contexts/PreferencesContext"; +import { DebugInfo } from "./DebugInfo"; + +export function DebugWrapper() { + const { preferences } = usePreferences(); + console.log(preferences); + if (!preferences.debug) { + return null; + } + + return ; +} diff --git a/src/lib/hoc/withPageTiming.tsx b/src/lib/hoc/withPageTiming.tsx new file mode 100644 index 0000000..c9a4d68 --- /dev/null +++ b/src/lib/hoc/withPageTiming.tsx @@ -0,0 +1,16 @@ +import { DebugService } from "@/lib/services/debug.service"; + +type PageComponent = (props: any) => Promise | 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; + }; +} diff --git a/src/lib/services/base-api.service.ts b/src/lib/services/base-api.service.ts index 29e0bc3..fddb58e 100644 --- a/src/lib/services/base-api.service.ts +++ b/src/lib/services/base-api.service.ts @@ -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, type: CacheType = "DEFAULT" ): Promise { - 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 { + 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(); } } diff --git a/src/lib/services/config-db.service.ts b/src/lib/services/config-db.service.ts index cb66977..d3b1cb7 100644 --- a/src/lib/services/config-db.service.ts +++ b/src/lib/services/config-db.service.ts @@ -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 { - 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; + }); } } diff --git a/src/lib/services/debug.service.ts b/src/lib/services/debug.service.ts new file mode 100644 index 0000000..d553963 --- /dev/null +++ b/src/lib/services/debug.service.ts @@ -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 { + 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) { + 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 { + 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 { + 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(operation: string, func: () => Promise): Promise { + 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; + } + } +} diff --git a/src/lib/services/favorite.service.ts b/src/lib/services/favorite.service.ts index ae18c64..148a760 100644 --- a/src/lib/services/favorite.service.ts +++ b/src/lib/services/favorite.service.ts @@ -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 { - 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 { 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 { 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 { 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 { - 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 { + const user = this.getCurrentUser(); + await connectDB(); + + return DebugService.measureMongoOperation("removeFavorite", async () => { + const result = await FavoriteModel.deleteOne({ userId: user.id, seriesId }); + return result.deletedCount > 0; + }); } } diff --git a/src/lib/services/preferences.service.ts b/src/lib/services/preferences.service.ts index 6dc2c44..f3a6329 100644 --- a/src/lib/services/preferences.service.ts +++ b/src/lib/services/preferences.service.ts @@ -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 { - 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 { 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): Promise { try { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); const updatedPreferences = await PreferencesModel.findOneAndUpdate( { userId: user.id }, { $set: preferences },