diff --git a/package.json b/package.json index 56dc75a..17b14a2 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-slot": "1.0.2", "@radix-ui/react-toast": "1.1.5", + "@types/bcrypt": "^5.0.2", "@types/mongoose": "5.11.97", + "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "2.1.0", "lucide-react": "0.323.0", diff --git a/src/app/api/komga/cache/clear/route.ts b/src/app/api/komga/cache/clear/route.ts index ebf4239..5bdbcec 100644 --- a/src/app/api/komga/cache/clear/route.ts +++ b/src/app/api/komga/cache/clear/route.ts @@ -1,15 +1,8 @@ import { NextResponse } from "next/server"; -import { serverCacheService } from "@/lib/services/server-cache.service"; +import { getServerCacheService } from "@/lib/services/server-cache.service"; export async function POST() { - try { - serverCacheService.clear(); - return NextResponse.json({ message: "Cache serveur supprimé avec succès" }); - } catch (error) { - console.error("Erreur lors de la suppression du cache serveur:", error); - return NextResponse.json( - { error: "Erreur lors de la suppression du cache serveur" }, - { status: 500 } - ); - } + const cacheService = await getServerCacheService(); + cacheService.clear(); + return NextResponse.json({ message: "Cache cleared" }); } diff --git a/src/app/api/komga/cache/mode/route.ts b/src/app/api/komga/cache/mode/route.ts index 28292c7..affb6d7 100644 --- a/src/app/api/komga/cache/mode/route.ts +++ b/src/app/api/komga/cache/mode/route.ts @@ -1,8 +1,9 @@ import { NextResponse } from "next/server"; -import { serverCacheService } from "@/lib/services/server-cache.service"; +import { getServerCacheService } from "@/lib/services/server-cache.service"; export async function GET() { - return NextResponse.json({ mode: serverCacheService.getCacheMode() }); + const cacheService = await getServerCacheService(); + return NextResponse.json({ mode: cacheService.getCacheMode() }); } export async function POST(request: Request) { @@ -15,8 +16,9 @@ export async function POST(request: Request) { ); } - serverCacheService.setCacheMode(mode); - return NextResponse.json({ mode: serverCacheService.getCacheMode() }); + const cacheService = await getServerCacheService(); + cacheService.setCacheMode(mode); + return NextResponse.json({ mode: cacheService.getCacheMode() }); } catch (error) { console.error("Erreur lors de la mise à jour du mode de cache:", error); return NextResponse.json({ error: "Invalid request" }, { status: 400 }); diff --git a/src/app/libraries/[libraryId]/page.tsx b/src/app/libraries/[libraryId]/page.tsx index 84ac29a..023b53d 100644 --- a/src/app/libraries/[libraryId]/page.tsx +++ b/src/app/libraries/[libraryId]/page.tsx @@ -16,7 +16,7 @@ async function refreshLibrary(libraryId: string) { "use server"; try { - await LibraryService.clearLibrarySeriesCache(libraryId); + await LibraryService.invalidateLibrarySeriesCache(libraryId); revalidatePath(`/libraries/${libraryId}`); return { success: true }; diff --git a/src/app/page.tsx b/src/app/page.tsx index 3a56703..a503974 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,7 +8,7 @@ async function refreshHome() { "use server"; try { - await HomeService.clearHomeCache(); + await HomeService.invalidateHomeCache(); revalidatePath("/"); return { success: true }; } catch (error) { diff --git a/src/app/series/[seriesId]/page.tsx b/src/app/series/[seriesId]/page.tsx index 72d4800..c079dcb 100644 --- a/src/app/series/[seriesId]/page.tsx +++ b/src/app/series/[seriesId]/page.tsx @@ -29,8 +29,8 @@ async function refreshSeries(seriesId: string) { "use server"; try { - await SeriesService.clearSeriesBooksCache(seriesId); - await SeriesService.clearSeriesCache(seriesId); + await SeriesService.invalidateSeriesBooksCache(seriesId); + await SeriesService.invalidateSeriesCache(seriesId); revalidatePath(`/series/${seriesId}`); return { success: true }; } catch (error) { diff --git a/src/components/downloads/DownloadManager.tsx b/src/components/downloads/DownloadManager.tsx index 0198c60..7f487f2 100644 --- a/src/components/downloads/DownloadManager.tsx +++ b/src/components/downloads/DownloadManager.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { KomgaBook } from "@/types/komga"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card } from "@/components/ui/card"; @@ -31,12 +31,11 @@ export function DownloadManager() { const [isLoading, setIsLoading] = useState(true); const { toast } = useToast(); - const getStorageKey = (bookId: string) => `book-status-${bookId}`; + const getStorageKey = useCallback((bookId: string) => `book-status-${bookId}`, []); - const loadDownloadedBooks = async () => { + const loadDownloadedBooks = useCallback(async () => { setIsLoading(true); try { - // Récupère tous les livres du localStorage const books: DownloadedBook[] = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); @@ -70,9 +69,9 @@ export function DownloadManager() { } finally { setIsLoading(false); } - }; + }, [toast]); - const updateBookStatuses = () => { + const updateBookStatuses = useCallback(() => { setDownloadedBooks((prevBooks) => { return prevBooks.map((downloadedBook) => { const status = JSON.parse( @@ -87,29 +86,25 @@ export function DownloadManager() { }; }); }); - }; + }, [getStorageKey]); useEffect(() => { loadDownloadedBooks(); - // Écoute les changements de statut des livres const handleStorageChange = (e: StorageEvent) => { if (e.key?.startsWith("book-status-")) { updateBookStatuses(); } }; - // Écoute les changements dans d'autres onglets window.addEventListener("storage", handleStorageChange); - - // Écoute les changements dans l'onglet courant const interval = setInterval(updateBookStatuses, 1000); return () => { window.removeEventListener("storage", handleStorageChange); clearInterval(interval); }; - }, []); + }, [loadDownloadedBooks, updateBookStatuses]); const handleDeleteBook = async (book: KomgaBook) => { try { diff --git a/src/components/home/HomeContent.tsx b/src/components/home/HomeContent.tsx index c16fa89..58045ae 100644 --- a/src/components/home/HomeContent.tsx +++ b/src/components/home/HomeContent.tsx @@ -2,11 +2,13 @@ import { HeroSection } from "./HeroSection"; import { MediaRow } from "./MediaRow"; import { KomgaBook, KomgaSeries } from "@/types/komga"; import { RefreshButton } from "@/components/library/RefreshButton"; +import { BookOpenCheck, History, Sparkles, Clock } from "lucide-react"; interface HomeData { ongoing: KomgaSeries[]; recentlyRead: KomgaBook[]; onDeck: KomgaBook[]; + latestSeries: KomgaSeries[]; } interface HomeContentProps { @@ -61,15 +63,35 @@ export function HomeContent({ data, refreshHome }: HomeContentProps) { {/* Sections de contenu */}
{data.ongoing && data.ongoing.length > 0 && ( - + } + /> )} {data.onDeck && data.onDeck.length > 0 && ( - + } + /> + )} + + {data.latestSeries && data.latestSeries.length > 0 && ( + } + /> )} {data.recentlyRead && data.recentlyRead.length > 0 && ( - + } + /> )}
diff --git a/src/components/home/MediaRow.tsx b/src/components/home/MediaRow.tsx index 26c4708..4b8c34e 100644 --- a/src/components/home/MediaRow.tsx +++ b/src/components/home/MediaRow.tsx @@ -26,9 +26,10 @@ interface OptimizedBook extends BaseItem { interface MediaRowProps { title: string; items: (OptimizedSeries | OptimizedBook)[]; + icon?: React.ReactNode; } -export function MediaRow({ title, items }: MediaRowProps) { +export function MediaRow({ title, items, icon }: MediaRowProps) { const scrollContainerRef = useRef(null); const [showLeftArrow, setShowLeftArrow] = useState(false); const [showRightArrow, setShowRightArrow] = useState(true); @@ -58,7 +59,10 @@ export function MediaRow({ title, items }: MediaRowProps) { return (
-

{title}

+
+ {icon} +

{title}

+
{/* Bouton de défilement gauche */} {showLeftArrow && ( diff --git a/src/components/ui/book-offline-button.tsx b/src/components/ui/book-offline-button.tsx index 3b1f815..3037345 100644 --- a/src/components/ui/book-offline-button.tsx +++ b/src/components/ui/book-offline-button.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { Download, Check, Loader2 } from "lucide-react"; import { Button } from "./button"; import { useToast } from "./use-toast"; @@ -27,156 +27,141 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { const [downloadProgress, setDownloadProgress] = useState(0); const { toast } = useToast(); - const getStorageKey = (bookId: string) => `book-status-${bookId}`; + const getStorageKey = useCallback((bookId: string) => `book-status-${bookId}`, []); - const getBookStatus = (bookId: string): BookDownloadStatus => { - try { - const status = localStorage.getItem(getStorageKey(bookId)); - return status ? JSON.parse(status) : { status: "idle", progress: 0, timestamp: 0 }; - } catch { - return { status: "idle", progress: 0, timestamp: 0 }; - } - }; - - const setBookStatus = (bookId: string, status: BookDownloadStatus) => { - localStorage.setItem(getStorageKey(bookId), JSON.stringify(status)); - }; - - const downloadBook = async (startFromPage: number = 1) => { - try { - const cache = await caches.open("stripstream-books"); - - // Marque le début du téléchargement - setBookStatus(book.id, { - status: "downloading", - progress: ((startFromPage - 1) / book.media.pagesCount) * 100, - timestamp: Date.now(), - lastDownloadedPage: startFromPage - 1, - }); - - // Ajoute le livre au cache si on commence depuis le début - if (startFromPage === 1) { - const pagesResponse = await fetch(`/api/komga/images/books/${book.id}/pages/1`); - if (!pagesResponse.ok) throw new Error("Erreur lors de la récupération des pages"); - await cache.put(`/api/komga/images/books/${book.id}/pages`, pagesResponse.clone()); + const getBookStatus = useCallback( + (bookId: string): BookDownloadStatus => { + try { + const status = localStorage.getItem(getStorageKey(bookId)); + return status ? JSON.parse(status) : { status: "idle", progress: 0, timestamp: 0 }; + } catch { + return { status: "idle", progress: 0, timestamp: 0 }; } + }, + [getStorageKey] + ); - // Cache chaque page avec retry - let failedPages = 0; - for (let i = startFromPage; i <= book.media.pagesCount; i++) { - let retryCount = 0; - const maxRetries = 3; + const setBookStatus = useCallback( + (bookId: string, status: BookDownloadStatus) => { + localStorage.setItem(getStorageKey(bookId), JSON.stringify(status)); + }, + [getStorageKey] + ); - while (retryCount < maxRetries) { - try { - const pageResponse = await fetch(`/api/komga/images/books/${book.id}/pages/${i}`); - if (!pageResponse.ok) { + const downloadBook = useCallback( + async (startFromPage: number = 1) => { + try { + const cache = await caches.open("stripstream-books"); + + // Marque le début du téléchargement + setBookStatus(book.id, { + status: "downloading", + progress: ((startFromPage - 1) / book.media.pagesCount) * 100, + timestamp: Date.now(), + lastDownloadedPage: startFromPage - 1, + }); + + // Ajoute le livre au cache si on commence depuis le début + if (startFromPage === 1) { + const pagesResponse = await fetch(`/api/komga/images/books/${book.id}/pages/1`); + if (!pagesResponse.ok) throw new Error("Erreur lors de la récupération des pages"); + await cache.put(`/api/komga/images/books/${book.id}/pages`, pagesResponse.clone()); + } + + // Cache chaque page avec retry + let failedPages = 0; + for (let i = startFromPage; i <= book.media.pagesCount; i++) { + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + try { + const pageResponse = await fetch(`/api/komga/images/books/${book.id}/pages/${i}`); + if (!pageResponse.ok) { + retryCount++; + if (retryCount === maxRetries) { + failedPages++; + console.error( + `Échec du téléchargement de la page ${i} après ${maxRetries} tentatives` + ); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); // Attendre 1s avant de réessayer + continue; + } + await cache.put( + `/api/komga/images/books/${book.id}/pages/${i}`, + pageResponse.clone() + ); + break; // Sortir de la boucle si réussi + } catch (error) { retryCount++; if (retryCount === maxRetries) { failedPages++; - console.error( - `Échec du téléchargement de la page ${i} après ${maxRetries} tentatives` - ); + console.error(`Erreur lors du téléchargement de la page ${i}:`, error); } - await new Promise((resolve) => setTimeout(resolve, 1000)); // Attendre 1s avant de réessayer - continue; + await new Promise((resolve) => setTimeout(resolve, 1000)); } - await cache.put(`/api/komga/images/books/${book.id}/pages/${i}`, pageResponse.clone()); - break; // Sortir de la boucle si réussi - } catch (error) { - retryCount++; - if (retryCount === maxRetries) { - failedPages++; - console.error(`Erreur lors du téléchargement de la page ${i}:`, error); - } - await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + // Mise à jour du statut + const progress = (i / book.media.pagesCount) * 100; + setDownloadProgress(progress); + setBookStatus(book.id, { + status: "downloading", + progress, + timestamp: Date.now(), + lastDownloadedPage: i, + }); + + // Vérifier si le statut a changé pendant le téléchargement + const currentStatus = getBookStatus(book.id); + if (currentStatus.status === "idle") { + // Le téléchargement a été annulé + throw new Error("Téléchargement annulé"); } } - // Mise à jour du statut - const progress = (i / book.media.pagesCount) * 100; - setDownloadProgress(progress); - setBookStatus(book.id, { - status: "downloading", - progress, - timestamp: Date.now(), - lastDownloadedPage: i, - }); - - // Vérifier si le statut a changé pendant le téléchargement - const currentStatus = getBookStatus(book.id); - if (currentStatus.status === "idle") { - // Le téléchargement a été annulé - throw new Error("Téléchargement annulé"); - } - } - - if (failedPages > 0) { - // Si des pages ont échoué, on supprime tout le cache pour ce livre - await cache.delete(`/api/komga/images/books/${book.id}/pages`); - for (let i = 1; i <= book.media.pagesCount; i++) { - await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`); - } - setIsAvailableOffline(false); - setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); - toast({ - title: "Erreur", - description: `${failedPages} page(s) n'ont pas pu être téléchargées. Le livre ne sera pas disponible hors ligne.`, - variant: "destructive", - }); - } else { - setIsAvailableOffline(true); - setBookStatus(book.id, { status: "available", progress: 100, timestamp: Date.now() }); - toast({ - title: "Livre téléchargé", - description: "Le livre est maintenant disponible hors ligne", - }); - } - } catch (error) { - console.error("Erreur lors du téléchargement:", error); - // Ne pas changer le statut si le téléchargement a été volontairement annulé - if ((error as Error)?.message !== "Téléchargement annulé") { - setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); - toast({ - title: "Erreur", - description: "Une erreur est survenue lors du téléchargement", - variant: "destructive", - }); - } - } finally { - setIsLoading(false); - setDownloadProgress(0); - } - }; - - // Vérifie si le livre est déjà disponible hors ligne - useEffect(() => { - const checkStatus = async () => { - const storedStatus = getBookStatus(book.id); - - // Si le livre est marqué comme en cours de téléchargement - if (storedStatus.status === "downloading") { - // Si le téléchargement a commencé il y a plus de 5 minutes, on considère qu'il a échoué - if (Date.now() - storedStatus.timestamp > 5 * 60 * 1000) { + if (failedPages > 0) { + // Si des pages ont échoué, on supprime tout le cache pour ce livre + await cache.delete(`/api/komga/images/books/${book.id}/pages`); + for (let i = 1; i <= book.media.pagesCount; i++) { + await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`); + } + setIsAvailableOffline(false); setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); - setIsLoading(false); - setDownloadProgress(0); + toast({ + title: "Erreur", + description: `${failedPages} page(s) n'ont pas pu être téléchargées. Le livre ne sera pas disponible hors ligne.`, + variant: "destructive", + }); } else { - // On reprend le téléchargement là où il s'était arrêté - setIsLoading(true); - setDownloadProgress(storedStatus.progress); - const startFromPage = (storedStatus.lastDownloadedPage || 0) + 1; - downloadBook(startFromPage); + setIsAvailableOffline(true); + setBookStatus(book.id, { status: "available", progress: 100, timestamp: Date.now() }); + toast({ + title: "Livre téléchargé", + description: "Le livre est maintenant disponible hors ligne", + }); } + } catch (error) { + console.error("Erreur lors du téléchargement:", error); + // Ne pas changer le statut si le téléchargement a été volontairement annulé + if ((error as Error)?.message !== "Téléchargement annulé") { + setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); + toast({ + title: "Erreur", + description: "Une erreur est survenue lors du téléchargement", + variant: "destructive", + }); + } + } finally { + setIsLoading(false); + setDownloadProgress(0); } + }, + [book.id, book.media.pagesCount, getBookStatus, setBookStatus, toast] + ); - await checkOfflineAvailability(); - }; - - checkStatus(); - }, [book.id]); - - const checkOfflineAvailability = async () => { + const checkOfflineAvailability = useCallback(async () => { if (!("caches" in window)) return; try { @@ -209,7 +194,30 @@ export function BookOfflineButton({ book, className }: BookOfflineButtonProps) { console.error("Erreur lors de la vérification du cache:", error); setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); } - }; + }, [book.id, book.media.pagesCount, setBookStatus]); + + useEffect(() => { + const checkStatus = async () => { + const storedStatus = getBookStatus(book.id); + + if (storedStatus.status === "downloading") { + if (Date.now() - storedStatus.timestamp > 5 * 60 * 1000) { + setBookStatus(book.id, { status: "error", progress: 0, timestamp: Date.now() }); + setIsLoading(false); + setDownloadProgress(0); + } else { + setIsLoading(true); + setDownloadProgress(storedStatus.progress); + const startFromPage = (storedStatus.lastDownloadedPage || 0) + 1; + downloadBook(startFromPage); + } + } + + await checkOfflineAvailability(); + }; + + checkStatus(); + }, [book.id, checkOfflineAvailability, downloadBook, getBookStatus, setBookStatus]); const handleToggleOffline = async () => { if (!("caches" in window)) { diff --git a/src/lib/services/auth-server.service.ts b/src/lib/services/auth-server.service.ts index 34d8a03..4ea7369 100644 --- a/src/lib/services/auth-server.service.ts +++ b/src/lib/services/auth-server.service.ts @@ -1,6 +1,7 @@ import { cookies } from "next/headers"; import connectDB from "@/lib/mongodb"; import { UserModel } from "@/lib/models/user.model"; +import bcrypt from "bcrypt"; interface UserData { id: string; @@ -10,6 +11,8 @@ interface UserData { } export class AuthServerService { + private static readonly SALT_ROUNDS = 10; + static async createUser(email: string, password: string): Promise { await connectDB(); @@ -24,10 +27,13 @@ export class AuthServerService { throw new Error("EMAIL_EXISTS"); } + // Hash password + const hashedPassword = await bcrypt.hash(password, this.SALT_ROUNDS); + // Create new user const user = await UserModel.create({ email: email.toLowerCase(), - password, + password: hashedPassword, roles: ["ROLE_USER"], authenticated: true, }); @@ -41,6 +47,7 @@ export class AuthServerService { return userData; } + static isPasswordStrong(password: string): boolean { //check if password is strong if (password.length < 8) { @@ -52,9 +59,9 @@ export class AuthServerService { if (!/[0-9]/.test(password)) { return false; } - if (!/[!@#$%^&*]/.test(password)) { - return false; - } + // if (!/[!@#$%^&*]/.test(password)) { + // return false; + // } return true; } @@ -95,7 +102,8 @@ export class AuthServerService { throw new Error("INVALID_CREDENTIALS"); } - if (user.password !== password) { + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { throw new Error("INVALID_CREDENTIALS"); } diff --git a/src/lib/services/base-api.service.ts b/src/lib/services/base-api.service.ts index fddb58e..4be421b 100644 --- a/src/lib/services/base-api.service.ts +++ b/src/lib/services/base-api.service.ts @@ -1,5 +1,5 @@ import { AuthConfig } from "@/types/auth"; -import { serverCacheService } from "./server-cache.service"; +import { getServerCacheService } from "./server-cache.service"; import { ConfigDBService } from "./config-db.service"; import { DebugService } from "./debug.service"; @@ -52,10 +52,11 @@ export abstract class BaseApiService { fetcher: () => Promise, type: CacheType = "DEFAULT" ): Promise { + const cacheService = await getServerCacheService(); const startTime = performance.now(); try { - const result = await serverCacheService.getOrSet(key, fetcher, type); + const result = await cacheService.getOrSet(key, fetcher, type); const endTime = performance.now(); // Log la requête avec l'indication du cache diff --git a/src/lib/services/config-db.service.ts b/src/lib/services/config-db.service.ts index d3b1cb7..ec4a0d6 100644 --- a/src/lib/services/config-db.service.ts +++ b/src/lib/services/config-db.service.ts @@ -1,4 +1,3 @@ -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"; @@ -34,6 +33,24 @@ export class ConfigDBService { return user; } + static async saveConfig(data: KomgaConfigData) { + const user = 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; + } + static async getConfig() { const user = this.getCurrentUser(); await connectDB(); @@ -44,25 +61,6 @@ export class ConfigDBService { }); } - static async saveConfig(data: KomgaConfigData) { - const user = this.getCurrentUser(); - await connectDB(); - - 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 = this.getCurrentUser(); await connectDB(); diff --git a/src/lib/services/favorite.service.ts b/src/lib/services/favorite.service.ts index 148a760..7bd682c 100644 --- a/src/lib/services/favorite.service.ts +++ b/src/lib/services/favorite.service.ts @@ -1,4 +1,3 @@ -import { cookies } from "next/headers"; import connectDB from "@/lib/mongodb"; import { FavoriteModel } from "@/lib/models/favorite.model"; import { DebugService } from "./debug.service"; diff --git a/src/lib/services/home.service.ts b/src/lib/services/home.service.ts index 2da6fc2..066a75e 100644 --- a/src/lib/services/home.service.ts +++ b/src/lib/services/home.service.ts @@ -1,19 +1,20 @@ import { BaseApiService } from "./base-api.service"; import { KomgaBook, KomgaSeries } from "@/types/komga"; import { LibraryResponse } from "@/types/library"; -import { serverCacheService } from "./server-cache.service"; +import { getServerCacheService } from "./server-cache.service"; interface HomeData { ongoing: KomgaSeries[]; recentlyRead: KomgaBook[]; onDeck: KomgaBook[]; + latestSeries: KomgaSeries[]; } export class HomeService extends BaseApiService { static async getHomeData(): Promise { try { // Appels API parallèles avec cache individuel - const [ongoing, recentlyRead, onDeck] = await Promise.all([ + const [ongoing, recentlyRead, onDeck, latestSeries] = await Promise.all([ this.fetchWithCache>( "home-ongoing", async () => @@ -55,21 +56,36 @@ export class HomeService extends BaseApiService { }), "HOME" ), + this.fetchWithCache>( + "home-latest-series", + async () => + this.fetchFromApi>({ + path: "series/latest", + params: { + page: "0", + size: "10", + media_status: "READY", + }, + }), + "HOME" + ), ]); return { ongoing: ongoing.content || [], recentlyRead: recentlyRead.content || [], onDeck: onDeck.content || [], + latestSeries: latestSeries.content || [], }; } catch (error) { return this.handleError(error, "Impossible de récupérer les données de la page d'accueil"); } } - static async clearHomeCache() { - serverCacheService.delete("home-ongoing"); - serverCacheService.delete("home-recently-read"); - serverCacheService.delete("home-on-deck"); + static async invalidateHomeCache(): Promise { + const cacheService = await getServerCacheService(); + cacheService.delete("home-ongoing"); + cacheService.delete("home-recently-read"); + cacheService.delete("home-on-deck"); } } diff --git a/src/lib/services/library.service.ts b/src/lib/services/library.service.ts index dc8be01..a2ffdf2 100644 --- a/src/lib/services/library.service.ts +++ b/src/lib/services/library.service.ts @@ -1,7 +1,7 @@ import { BaseApiService } from "./base-api.service"; import { Library, LibraryResponse } from "@/types/library"; import { Series } from "@/types/series"; -import { serverCacheService } from "./server-cache.service"; +import { getServerCacheService } from "./server-cache.service"; export class LibraryService extends BaseApiService { static async getLibraries(): Promise { @@ -134,7 +134,8 @@ export class LibraryService extends BaseApiService { } } - static async clearLibrarySeriesCache(libraryId: string) { - serverCacheService.delete(`library-${libraryId}-all-series`); + static async invalidateLibrarySeriesCache(libraryId: string): Promise { + const cacheService = await getServerCacheService(); + cacheService.delete(`library-${libraryId}-all-series`); } } diff --git a/src/lib/services/preferences.service.ts b/src/lib/services/preferences.service.ts index f3a6329..6658e48 100644 --- a/src/lib/services/preferences.service.ts +++ b/src/lib/services/preferences.service.ts @@ -1,4 +1,3 @@ -import { cookies } from "next/headers"; import { PreferencesModel } from "@/lib/models/preferences.model"; import { AuthServerService } from "./auth-server.service"; diff --git a/src/lib/services/series.service.ts b/src/lib/services/series.service.ts index 78b4aa5..3f774f1 100644 --- a/src/lib/services/series.service.ts +++ b/src/lib/services/series.service.ts @@ -4,7 +4,7 @@ import { KomgaBook, KomgaSeries } from "@/types/komga"; import { BookService } from "./book.service"; import { ImageService } from "./image.service"; import { PreferencesService } from "./preferences.service"; -import { serverCacheService } from "./server-cache.service"; +import { getServerCacheService } from "./server-cache.service"; export class SeriesService extends BaseApiService { static async getSeries(seriesId: string): Promise { @@ -19,8 +19,9 @@ export class SeriesService extends BaseApiService { } } - static async clearSeriesCache(seriesId: string) { - serverCacheService.delete(`series-${seriesId}`); + static async invalidateSeriesCache(seriesId: string): Promise { + const cacheService = await getServerCacheService(); + cacheService.delete(`series-${seriesId}`); } static async getAllSeriesBooks(seriesId: string): Promise { @@ -125,8 +126,9 @@ export class SeriesService extends BaseApiService { } } - static async clearSeriesBooksCache(seriesId: string) { - serverCacheService.delete(`series-${seriesId}-all-books`); + static async invalidateSeriesBooksCache(seriesId: string): Promise { + const cacheService = await getServerCacheService(); + cacheService.delete(`series-${seriesId}-all-books`); } static async getFirstBook(seriesId: string): Promise { diff --git a/src/lib/services/server-cache.service.ts b/src/lib/services/server-cache.service.ts index fc98fb3..de71eed 100644 --- a/src/lib/services/server-cache.service.ts +++ b/src/lib/services/server-cache.service.ts @@ -1,5 +1,6 @@ import fs from "fs"; import path from "path"; +import { PreferencesService } from "./preferences.service"; type CacheMode = "file" | "memory"; @@ -37,6 +38,17 @@ class ServerCacheService { this.cacheDir = path.join(process.cwd(), ".cache"); this.ensureCacheDirectory(); this.cleanExpiredCache(); + this.initializeCacheMode(); + } + + private async initializeCacheMode(): Promise { + try { + const preferences = await PreferencesService.getPreferences(); + this.setCacheMode(preferences.cacheMode); + } catch (error) { + console.error("Error initializing cache mode from preferences:", error); + // Keep default memory mode if preferences can't be loaded + } } private ensureCacheDirectory(): void { @@ -117,9 +129,10 @@ class ServerCacheService { cleanDirectory(this.cacheDir); } - public static getInstance(): ServerCacheService { + public static async getInstance(): Promise { if (!ServerCacheService.instance) { ServerCacheService.instance = new ServerCacheService(); + await ServerCacheService.instance.initializeCacheMode(); } return ServerCacheService.instance; } @@ -376,4 +389,15 @@ class ServerCacheService { } } -export const serverCacheService = ServerCacheService.getInstance(); +// Créer une instance initialisée du service +let initializedInstance: Promise; + +export const getServerCacheService = async (): Promise => { + if (!initializedInstance) { + initializedInstance = ServerCacheService.getInstance(); + } + return initializedInstance; +}; + +// Exporter aussi la classe pour les tests +export { ServerCacheService }; diff --git a/yarn.lock b/yarn.lock index 9cd21a4..f9cc258 100644 --- a/yarn.lock +++ b/yarn.lock @@ -256,6 +256,21 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@mongodb-js/saslprep@^1.1.0", "@mongodb-js/saslprep@^1.1.9": version "1.2.0" resolved "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz" @@ -695,6 +710,13 @@ dependencies: tslib "^2.4.0" +"@types/bcrypt@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.2.tgz#22fddc11945ea4fbc3655b3e8b8847cc9f811477" + integrity sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ== + dependencies: + "@types/node" "*" + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" @@ -707,6 +729,13 @@ dependencies: mongoose "*" +"@types/node@*": + version "22.13.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.5.tgz#23add1d71acddab2c6a4d31db89c0f98d330b511" + integrity sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg== + dependencies: + undici-types "~6.20.0" + "@types/node@20.11.16": version "20.11.16" resolved "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz" @@ -880,6 +909,11 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" @@ -890,6 +924,13 @@ acorn@^8.9.0: resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" @@ -935,6 +976,19 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" @@ -1094,6 +1148,14 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bcrypt@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" @@ -1207,6 +1269,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + class-variance-authority@^0.7.0: version "0.7.1" resolved "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz" @@ -1249,6 +1316,11 @@ color-string@^1.9.0: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/color/-/color-4.2.3.tgz" @@ -1267,6 +1339,11 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + cookie@^0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" @@ -1323,7 +1400,7 @@ data-view-byte-offset@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" -debug@4.x, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: +debug@4, debug@4.x, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: version "4.4.0" resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -1360,7 +1437,12 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -detect-libc@^2.0.2: +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0, detect-libc@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== @@ -1888,6 +1970,13 @@ fraction.js@^4.3.7: resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -1920,6 +2009,21 @@ functions-have-names@^1.2.3: resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz" @@ -2092,6 +2196,11 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + hasown@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" @@ -2099,6 +2208,14 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" @@ -2125,7 +2242,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2527,6 +2644,13 @@ lucide-react@0.323.0: resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.323.0.tgz" integrity sha512-rTXZFILl2Y4d1SG9p1Mdcf17AcPvPvpc/egFIzUrp7IUy60MUQo3Oi1mu8LGYXUVwuRZYsSMt3csHRW5mAovJg== +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" @@ -2576,11 +2700,36 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mongodb-connection-string-url@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz" @@ -2712,11 +2861,30 @@ next@14.1.0: "@next/swc-win32-ia32-msvc" "14.1.0" "@next/swc-win32-x64-msvc" "14.1.0" +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-releases@^2.0.19: version "2.0.19" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -2727,6 +2895,16 @@ normalize-range@^0.1.2: resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + oauth@^0.9.15: version "0.9.15" resolved "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz" @@ -3105,6 +3283,15 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" @@ -3201,6 +3388,11 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-push-apply@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz" @@ -3225,16 +3417,21 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" -semver@^6.3.1: +semver@^6.0.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: +semver@^7.3.5, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: version "7.7.1" resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz" integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" @@ -3357,6 +3554,11 @@ sift@17.1.3: resolved "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz" integrity sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ== +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" @@ -3405,7 +3607,7 @@ streamsearch@^1.1.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3491,6 +3693,13 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -3599,6 +3808,18 @@ tapable@^2.2.0: resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar@^6.1.11: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -3640,6 +3861,11 @@ tr46@^5.0.0: dependencies: punycode "^2.3.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + ts-api-utils@^1.0.1: version "1.4.3" resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz" @@ -3747,6 +3973,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + update-browserslist-db@^1.1.1: version "1.1.2" resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz" @@ -3777,7 +4008,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -util-deprecate@^1.0.2: +util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -3787,6 +4018,11 @@ uuid@^8.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" @@ -3800,6 +4036,14 @@ webidl-conversions@^7.0.0: tr46 "^5.0.0" webidl-conversions "^7.0.0" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz" @@ -3859,6 +4103,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"