feat: debugmode full request
This commit is contained in:
31
debug-logs/67b8a2ccd785e367b3c5e37b.json
Normal file
31
debug-logs/67b8a2ccd785e367b3c5e37b.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
23
debug-logs/julienfroidefond@gmail.com.json
Normal file
23
debug-logs/julienfroidefond@gmail.com.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
30
src/app/api/debug/route.ts
Normal file
30
src/app/api/debug/route.ts
Normal file
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,9 @@ import { ClientBookWrapper } from "@/components/reader/ClientBookWrapper";
|
|||||||
import { BookSkeleton } from "@/components/skeletons/BookSkeleton";
|
import { BookSkeleton } from "@/components/skeletons/BookSkeleton";
|
||||||
import { BookService } from "@/lib/services/book.service";
|
import { BookService } from "@/lib/services/book.service";
|
||||||
import { notFound } from "next/navigation";
|
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 {
|
try {
|
||||||
const data = await BookService.getBook(params.bookId);
|
const data = await BookService.getBook(params.bookId);
|
||||||
|
|
||||||
@@ -18,3 +19,5 @@ export default async function BookPage({ params }: { params: { bookId: string }
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withPageTiming("BookPage", BookPage);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { PageHeader } from "@/components/layout/PageHeader";
|
import { PageHeader } from "@/components/layout/PageHeader";
|
||||||
import { DownloadManager } from "@/components/downloads/DownloadManager";
|
import { DownloadManager } from "@/components/downloads/DownloadManager";
|
||||||
|
import { withPageTiming } from "@/lib/hoc/withPageTiming";
|
||||||
|
|
||||||
export default function DownloadsPage() {
|
function DownloadsPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader title="Téléchargements" description="Gérez vos livres disponibles hors ligne" />
|
<PageHeader title="Téléchargements" description="Gérez vos livres disponibles hors ligne" />
|
||||||
@@ -9,3 +10,5 @@ export default function DownloadsPage() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withPageTiming("DownloadsPage", DownloadsPage);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Inter } from "next/font/google";
|
|||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import ClientLayout from "@/components/layout/ClientLayout";
|
import ClientLayout from "@/components/layout/ClientLayout";
|
||||||
|
import { PreferencesProvider } from "@/contexts/PreferencesContext";
|
||||||
|
import { DebugWrapper } from "@/components/debug/DebugWrapper";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
@@ -113,7 +115,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body className={cn("min-h-screen bg-background font-sans antialiased", inter.className)}>
|
<body className={cn("min-h-screen bg-background font-sans antialiased", inter.className)}>
|
||||||
|
<PreferencesProvider>
|
||||||
<ClientLayout>{children}</ClientLayout>
|
<ClientLayout>{children}</ClientLayout>
|
||||||
|
<DebugWrapper />
|
||||||
|
</PreferencesProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { LibraryService } from "@/lib/services/library.service";
|
|||||||
import { PreferencesService } from "@/lib/services/preferences.service";
|
import { PreferencesService } from "@/lib/services/preferences.service";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { RefreshButton } from "@/components/library/RefreshButton";
|
import { RefreshButton } from "@/components/library/RefreshButton";
|
||||||
|
import { withPageTiming } from "@/lib/hoc/withPageTiming";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: { libraryId: string };
|
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 currentPage = searchParams.page ? parseInt(searchParams.page) : 1;
|
||||||
const preferences = await PreferencesService.getPreferences();
|
const preferences = await PreferencesService.getPreferences();
|
||||||
|
|
||||||
@@ -105,3 +106,5 @@ export default async function LibraryPage({ params, searchParams }: PageProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withPageTiming("LibraryPage", LibraryPage);
|
||||||
|
|||||||
22
src/app/libraries/page.tsx
Normal file
22
src/app/libraries/page.tsx
Normal file
@@ -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 <LibrariesContent libraries={libraries} />;
|
||||||
|
} catch (error) {
|
||||||
|
return (
|
||||||
|
<main className="container mx-auto px-4 py-8">
|
||||||
|
<div className="rounded-md bg-destructive/15 p-4">
|
||||||
|
<p className="text-sm text-destructive">
|
||||||
|
{error instanceof Error ? error.message : "Une erreur est survenue"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withPageTiming("LibrariesPage", LibrariesPage);
|
||||||
@@ -2,6 +2,7 @@ import { HomeContent } from "@/components/home/HomeContent";
|
|||||||
import { HomeService } from "@/lib/services/home.service";
|
import { HomeService } from "@/lib/services/home.service";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { withPageTiming } from "@/lib/hoc/withPageTiming";
|
||||||
|
|
||||||
async function refreshHome() {
|
async function refreshHome() {
|
||||||
"use server";
|
"use server";
|
||||||
@@ -16,7 +17,7 @@ async function refreshHome() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function HomePage() {
|
async function HomePage() {
|
||||||
try {
|
try {
|
||||||
const data = await HomeService.getHomeData();
|
const data = await HomeService.getHomeData();
|
||||||
|
|
||||||
@@ -38,3 +39,5 @@ export default async function HomePage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withPageTiming("HomePage", HomePage);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { SeriesHeader } from "@/components/series/SeriesHeader";
|
|||||||
import { SeriesService } from "@/lib/services/series.service";
|
import { SeriesService } from "@/lib/services/series.service";
|
||||||
import { PreferencesService } from "@/lib/services/preferences.service";
|
import { PreferencesService } from "@/lib/services/preferences.service";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { withPageTiming } from "@/lib/hoc/withPageTiming";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: { seriesId: string };
|
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 currentPage = searchParams.page ? parseInt(searchParams.page) : 1;
|
||||||
const preferences = await PreferencesService.getPreferences();
|
const preferences = await PreferencesService.getPreferences();
|
||||||
|
|
||||||
@@ -76,3 +77,5 @@ export default async function SeriesPage({ params, searchParams }: PageProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withPageTiming("SeriesPage", SeriesPage);
|
||||||
|
|||||||
209
src/components/debug/DebugInfo.tsx
Normal file
209
src/components/debug/DebugInfo.tsx
Normal file
@@ -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<RequestTiming[]>([]);
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
className={`fixed bottom-4 right-4 bg-zinc-900 border border-zinc-700 rounded-lg shadow-lg p-4 text-zinc-100 ${
|
||||||
|
isMinimized ? "w-auto" : "w-[800px] max-h-[50vh] overflow-auto"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-4 sticky top-0 bg-zinc-900 pb-2">
|
||||||
|
<h2 className="font-bold text-lg">DEBUG !</h2>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={fetchLogs}
|
||||||
|
className="hover:bg-zinc-700 rounded-full p-1.5"
|
||||||
|
aria-label="Rafraîchir les logs"
|
||||||
|
disabled={isRefreshing}
|
||||||
|
>
|
||||||
|
<RefreshCw className={`h-5 w-5 ${isRefreshing ? "animate-spin" : ""}`} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsMinimized(!isMinimized)}
|
||||||
|
className="hover:bg-zinc-700 rounded-full p-1.5"
|
||||||
|
aria-label={isMinimized ? "Agrandir" : "Minimiser"}
|
||||||
|
>
|
||||||
|
{isMinimized ? <Maximize2 className="h-5 w-5" /> : <Minimize2 className="h-5 w-5" />}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={clearLogs}
|
||||||
|
className="hover:bg-zinc-700 rounded-full p-1.5"
|
||||||
|
aria-label="Effacer les logs"
|
||||||
|
>
|
||||||
|
<X className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isMinimized && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{sortedLogs.length === 0 ? (
|
||||||
|
<p className="text-sm opacity-75">Aucune requête enregistrée</p>
|
||||||
|
) : (
|
||||||
|
sortedLogs.map((log, index) => (
|
||||||
|
<div key={index} className="text-sm space-y-1.5 bg-zinc-800 p-2 rounded">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||||
|
{log.fromCache && (
|
||||||
|
<div title={`Cache: ${log.cacheType || "DEFAULT"}`} className="flex-shrink-0">
|
||||||
|
<Database className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{log.mongoAccess && (
|
||||||
|
<div
|
||||||
|
title={`MongoDB: ${log.mongoAccess.operation}`}
|
||||||
|
className="flex-shrink-0"
|
||||||
|
>
|
||||||
|
<CircleDot className="h-4 w-4 text-blue-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{log.pageRender && (
|
||||||
|
<div title={`Page Render: ${log.pageRender.page}`} className="flex-shrink-0">
|
||||||
|
<Layout className="h-4 w-4 text-purple-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!log.fromCache && !log.mongoAccess && !log.pageRender && (
|
||||||
|
<div title="API Call" className="flex-shrink-0">
|
||||||
|
<Globe className="h-4 w-4 text-rose-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="font-medium truncate" title={log.url}>
|
||||||
|
{log.url}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 flex-shrink-0">
|
||||||
|
<div className="flex items-center gap-1 text-zinc-400" title="Heure du log">
|
||||||
|
<Clock className="h-3 w-3" />
|
||||||
|
<span>{formatTime(log.timestamp)}</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
log.pageRender
|
||||||
|
? "text-purple-400"
|
||||||
|
: log.mongoAccess
|
||||||
|
? "text-blue-400"
|
||||||
|
: log.fromCache
|
||||||
|
? "text-emerald-400"
|
||||||
|
: "text-rose-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{formatDuration(log.duration)}ms
|
||||||
|
</span>
|
||||||
|
{log.mongoAccess && (
|
||||||
|
<span className="text-blue-400" title="Temps d'accès MongoDB">
|
||||||
|
+{formatDuration(log.mongoAccess.duration)}ms
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-1.5 bg-zinc-700 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className={`h-full ${
|
||||||
|
log.pageRender
|
||||||
|
? "bg-purple-500"
|
||||||
|
: log.fromCache
|
||||||
|
? "bg-emerald-500"
|
||||||
|
: "bg-rose-500"
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
width: `${Math.min((log.duration / 1000) * 100, 100)}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{log.mongoAccess && (
|
||||||
|
<div
|
||||||
|
className="h-full bg-blue-500"
|
||||||
|
style={{
|
||||||
|
width: `${Math.min((log.mongoAccess.duration / 1000) * 100, 100)}%`,
|
||||||
|
marginTop: "-6px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/components/debug/DebugWrapper.tsx
Normal file
14
src/components/debug/DebugWrapper.tsx
Normal file
@@ -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 <DebugInfo />;
|
||||||
|
}
|
||||||
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 { AuthConfig } from "@/types/auth";
|
||||||
import { serverCacheService } from "./server-cache.service";
|
import { serverCacheService } from "./server-cache.service";
|
||||||
import { ConfigDBService } from "./config-db.service";
|
import { ConfigDBService } from "./config-db.service";
|
||||||
|
import { DebugService } from "./debug.service";
|
||||||
|
|
||||||
// Types de cache disponibles
|
// Types de cache disponibles
|
||||||
export type CacheType = "DEFAULT" | "HOME" | "LIBRARIES" | "SERIES" | "BOOKS" | "IMAGES";
|
export type CacheType = "DEFAULT" | "HOME" | "LIBRARIES" | "SERIES" | "BOOKS" | "IMAGES";
|
||||||
@@ -51,7 +52,36 @@ export abstract class BaseApiService {
|
|||||||
fetcher: () => Promise<T>,
|
fetcher: () => Promise<T>,
|
||||||
type: CacheType = "DEFAULT"
|
type: CacheType = "DEFAULT"
|
||||||
): Promise<T> {
|
): 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 {
|
protected static handleError(error: unknown, defaultMessage: string): never {
|
||||||
@@ -87,6 +117,7 @@ export abstract class BaseApiService {
|
|||||||
headersOptions = {},
|
headersOptions = {},
|
||||||
options: KomgaRequestInit = {}
|
options: KomgaRequestInit = {}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
|
const startTime = performance.now();
|
||||||
const config = await this.getKomgaConfig();
|
const config = await this.getKomgaConfig();
|
||||||
const { path, params } = urlBuilder;
|
const { path, params } = urlBuilder;
|
||||||
const url = this.buildUrl(config, path, params);
|
const url = this.buildUrl(config, path, params);
|
||||||
@@ -97,16 +128,36 @@ export abstract class BaseApiService {
|
|||||||
headers.set(key as string, value as string);
|
headers.set(key as string, value as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.log("🛜 Fetching from", url);
|
|
||||||
// console.log("Headers", headers);
|
try {
|
||||||
// console.log("headersOptions", headersOptions);
|
|
||||||
// console.log("options", options);
|
|
||||||
const response = await fetch(url, { headers, ...options });
|
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) {
|
if (!response.ok) {
|
||||||
throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
|
throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return options.isImage ? response : response.json();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { cookies } from "next/headers";
|
|||||||
import connectDB from "@/lib/mongodb";
|
import connectDB from "@/lib/mongodb";
|
||||||
import { KomgaConfig } from "@/lib/models/config.model";
|
import { KomgaConfig } from "@/lib/models/config.model";
|
||||||
import { TTLConfig } from "@/lib/models/ttl-config.model";
|
import { TTLConfig } from "@/lib/models/ttl-config.model";
|
||||||
|
import { DebugService } from "./debug.service";
|
||||||
|
import { AuthServerService } from "./auth-server.service";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -24,25 +26,29 @@ interface TTLConfigData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ConfigDBService {
|
export class ConfigDBService {
|
||||||
private static async getCurrentUser(): Promise<User> {
|
private static getCurrentUser(): User {
|
||||||
const userCookie = cookies().get("stripUser");
|
const user = AuthServerService.getCurrentUser();
|
||||||
|
if (!user) {
|
||||||
if (!userCookie) {
|
|
||||||
throw new Error("Utilisateur non authentifié");
|
throw new Error("Utilisateur non authentifié");
|
||||||
}
|
}
|
||||||
|
return user;
|
||||||
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 getConfig() {
|
||||||
|
const user = this.getCurrentUser();
|
||||||
|
await connectDB();
|
||||||
|
|
||||||
|
return DebugService.measureMongoOperation("getConfig", async () => {
|
||||||
|
const config = await KomgaConfig.findOne({ userId: user.id });
|
||||||
|
return config;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async saveConfig(data: KomgaConfigData) {
|
static async saveConfig(data: KomgaConfigData) {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
|
return DebugService.measureMongoOperation("saveConfig", async () => {
|
||||||
const config = await KomgaConfig.findOneAndUpdate(
|
const config = await KomgaConfig.findOneAndUpdate(
|
||||||
{ userId: user.id },
|
{ userId: user.id },
|
||||||
{
|
{
|
||||||
@@ -53,27 +59,25 @@ export class ConfigDBService {
|
|||||||
},
|
},
|
||||||
{ upsert: true, new: true }
|
{ upsert: true, new: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getConfig() {
|
static async getTTLConfig() {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
const config = await KomgaConfig.findOne({ userId: user.id });
|
return DebugService.measureMongoOperation("getTTLConfig", async () => {
|
||||||
|
const config = await TTLConfig.findOne({ userId: user.id });
|
||||||
if (!config) {
|
|
||||||
throw new Error("Configuration non trouvée");
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async saveTTLConfig(data: TTLConfigData) {
|
static async saveTTLConfig(data: TTLConfigData) {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
|
return DebugService.measureMongoOperation("saveTTLConfig", async () => {
|
||||||
const config = await TTLConfig.findOneAndUpdate(
|
const config = await TTLConfig.findOneAndUpdate(
|
||||||
{ userId: user.id },
|
{ userId: user.id },
|
||||||
{
|
{
|
||||||
@@ -82,35 +86,7 @@ export class ConfigDBService {
|
|||||||
},
|
},
|
||||||
{ upsert: true, new: true }
|
{ upsert: true, new: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
});
|
||||||
|
|
||||||
static async getTTLConfig() {
|
|
||||||
const user = await this.getCurrentUser();
|
|
||||||
await connectDB();
|
|
||||||
|
|
||||||
const config = await TTLConfig.findOne({ userId: user.id });
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
defaultTTL: config.defaultTTL,
|
|
||||||
homeTTL: config.homeTTL,
|
|
||||||
librariesTTL: config.librariesTTL,
|
|
||||||
seriesTTL: config.seriesTTL,
|
|
||||||
booksTTL: config.booksTTL,
|
|
||||||
imagesTTL: config.imagesTTL,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 { cookies } from "next/headers";
|
||||||
import connectDB from "@/lib/mongodb";
|
import connectDB from "@/lib/mongodb";
|
||||||
import { FavoriteModel } from "@/lib/models/favorite.model";
|
import { FavoriteModel } from "@/lib/models/favorite.model";
|
||||||
|
import { DebugService } from "./debug.service";
|
||||||
|
import { AuthServerService } from "./auth-server.service";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -17,19 +19,12 @@ export class FavoriteService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async getCurrentUser(): Promise<User> {
|
private static getCurrentUser(): User {
|
||||||
const userCookie = cookies().get("stripUser");
|
const user = AuthServerService.getCurrentUser();
|
||||||
|
if (!user) {
|
||||||
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);
|
|
||||||
throw new Error("Utilisateur non authentifié");
|
throw new Error("Utilisateur non authentifié");
|
||||||
}
|
}
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,15 +32,16 @@ export class FavoriteService {
|
|||||||
*/
|
*/
|
||||||
static async isFavorite(seriesId: string): Promise<boolean> {
|
static async isFavorite(seriesId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
|
return DebugService.measureMongoOperation("isFavorite", async () => {
|
||||||
const favorite = await FavoriteModel.findOne({
|
const favorite = await FavoriteModel.findOne({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
seriesId: seriesId,
|
seriesId: seriesId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return !!favorite;
|
return !!favorite;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erreur lors de la vérification du favori:", error);
|
console.error("Erreur lors de la vérification du favori:", error);
|
||||||
return false;
|
return false;
|
||||||
@@ -57,14 +53,16 @@ export class FavoriteService {
|
|||||||
*/
|
*/
|
||||||
static async addToFavorites(seriesId: string): Promise<void> {
|
static async addToFavorites(seriesId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
|
await DebugService.measureMongoOperation("addToFavorites", async () => {
|
||||||
await FavoriteModel.findOneAndUpdate(
|
await FavoriteModel.findOneAndUpdate(
|
||||||
{ userId: user.id, seriesId },
|
{ userId: user.id, seriesId },
|
||||||
{ userId: user.id, seriesId },
|
{ userId: user.id, seriesId },
|
||||||
{ upsert: true }
|
{ upsert: true }
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
this.dispatchFavoritesChanged();
|
this.dispatchFavoritesChanged();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -78,13 +76,15 @@ export class FavoriteService {
|
|||||||
*/
|
*/
|
||||||
static async removeFromFavorites(seriesId: string): Promise<void> {
|
static async removeFromFavorites(seriesId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
|
await DebugService.measureMongoOperation("removeFromFavorites", async () => {
|
||||||
await FavoriteModel.findOneAndDelete({
|
await FavoriteModel.findOneAndDelete({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
seriesId,
|
seriesId,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.dispatchFavoritesChanged();
|
this.dispatchFavoritesChanged();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -97,15 +97,36 @@ export class FavoriteService {
|
|||||||
* Récupère tous les IDs des séries favorites
|
* Récupère tous les IDs des séries favorites
|
||||||
*/
|
*/
|
||||||
static async getAllFavoriteIds(): Promise<string[]> {
|
static async getAllFavoriteIds(): Promise<string[]> {
|
||||||
try {
|
const user = this.getCurrentUser();
|
||||||
const user = await this.getCurrentUser();
|
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
|
return DebugService.measureMongoOperation("getAllFavoriteIds", async () => {
|
||||||
const favorites = await FavoriteModel.find({ userId: user.id });
|
const favorites = await FavoriteModel.find({ userId: user.id });
|
||||||
return favorites.map((favorite) => favorite.seriesId);
|
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 { cookies } from "next/headers";
|
||||||
import { PreferencesModel } from "@/lib/models/preferences.model";
|
import { PreferencesModel } from "@/lib/models/preferences.model";
|
||||||
|
import { AuthServerService } from "./auth-server.service";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,24 +22,17 @@ const defaultPreferences: UserPreferences = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class PreferencesService {
|
export class PreferencesService {
|
||||||
static async getCurrentUser(): Promise<User> {
|
static getCurrentUser(): User {
|
||||||
const userCookie = cookies().get("stripUser");
|
const user = AuthServerService.getCurrentUser();
|
||||||
|
if (!user) {
|
||||||
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);
|
|
||||||
throw new Error("Utilisateur non authentifié");
|
throw new Error("Utilisateur non authentifié");
|
||||||
}
|
}
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getPreferences(): Promise<UserPreferences> {
|
static async getPreferences(): Promise<UserPreferences> {
|
||||||
try {
|
try {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
const preferences = await PreferencesModel.findOne({ userId: user.id });
|
const preferences = await PreferencesModel.findOne({ userId: user.id });
|
||||||
if (!preferences) {
|
if (!preferences) {
|
||||||
return defaultPreferences;
|
return defaultPreferences;
|
||||||
@@ -55,7 +49,7 @@ export class PreferencesService {
|
|||||||
|
|
||||||
static async updatePreferences(preferences: Partial<UserPreferences>): Promise<UserPreferences> {
|
static async updatePreferences(preferences: Partial<UserPreferences>): Promise<UserPreferences> {
|
||||||
try {
|
try {
|
||||||
const user = await this.getCurrentUser();
|
const user = this.getCurrentUser();
|
||||||
const updatedPreferences = await PreferencesModel.findOneAndUpdate(
|
const updatedPreferences = await PreferencesModel.findOneAndUpdate(
|
||||||
{ userId: user.id },
|
{ userId: user.id },
|
||||||
{ $set: preferences },
|
{ $set: preferences },
|
||||||
|
|||||||
Reference in New Issue
Block a user