Merge branch 'main' into feat/debugmode

This commit is contained in:
Julien Froidefond
2025-02-23 21:30:34 +01:00
20 changed files with 548 additions and 223 deletions

View File

@@ -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" });
}

View File

@@ -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 });

View File

@@ -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 };

View File

@@ -8,7 +8,7 @@ async function refreshHome() {
"use server";
try {
await HomeService.clearHomeCache();
await HomeService.invalidateHomeCache();
revalidatePath("/");
return { success: true };
} catch (error) {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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 */}
<div className="space-y-12">
{data.ongoing && data.ongoing.length > 0 && (
<MediaRow title="Continuer la lecture" items={optimizeSeriesData(data.ongoing)} />
<MediaRow
title="Continuer la lecture"
items={optimizeSeriesData(data.ongoing)}
icon={<BookOpenCheck className="w-6 h-6" />}
/>
)}
{data.onDeck && data.onDeck.length > 0 && (
<MediaRow title="À suivre" items={optimizeBookData(data.onDeck)} />
<MediaRow
title="À suivre"
items={optimizeBookData(data.onDeck)}
icon={<Clock className="w-6 h-6" />}
/>
)}
{data.latestSeries && data.latestSeries.length > 0 && (
<MediaRow
title="Dernières séries"
items={optimizeSeriesData(data.latestSeries)}
icon={<Sparkles className="w-6 h-6" />}
/>
)}
{data.recentlyRead && data.recentlyRead.length > 0 && (
<MediaRow title="Ajouts récents" items={optimizeBookData(data.recentlyRead)} />
<MediaRow
title="Ajouts récents"
items={optimizeBookData(data.recentlyRead)}
icon={<History className="w-6 h-6" />}
/>
)}
</div>
</main>

View File

@@ -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<HTMLDivElement>(null);
const [showLeftArrow, setShowLeftArrow] = useState(false);
const [showRightArrow, setShowRightArrow] = useState(true);
@@ -58,7 +59,10 @@ export function MediaRow({ title, items }: MediaRowProps) {
return (
<div className="space-y-4">
<h2 className="text-2xl font-bold tracking-tight">{title}</h2>
<div className="flex items-center gap-2">
{icon}
<h2 className="text-2xl font-bold tracking-tight">{title}</h2>
</div>
<div className="relative">
{/* Bouton de défilement gauche */}
{showLeftArrow && (

View File

@@ -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)) {

View File

@@ -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<UserData> {
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");
}

View File

@@ -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<T>,
type: CacheType = "DEFAULT"
): Promise<T> {
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

View File

@@ -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();

View File

@@ -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";

View File

@@ -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<HomeData> {
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<LibraryResponse<KomgaSeries>>(
"home-ongoing",
async () =>
@@ -55,21 +56,36 @@ export class HomeService extends BaseApiService {
}),
"HOME"
),
this.fetchWithCache<LibraryResponse<KomgaSeries>>(
"home-latest-series",
async () =>
this.fetchFromApi<LibraryResponse<KomgaSeries>>({
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<void> {
const cacheService = await getServerCacheService();
cacheService.delete("home-ongoing");
cacheService.delete("home-recently-read");
cacheService.delete("home-on-deck");
}
}

View File

@@ -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<Library[]> {
@@ -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<void> {
const cacheService = await getServerCacheService();
cacheService.delete(`library-${libraryId}-all-series`);
}
}

View File

@@ -1,4 +1,3 @@
import { cookies } from "next/headers";
import { PreferencesModel } from "@/lib/models/preferences.model";
import { AuthServerService } from "./auth-server.service";

View File

@@ -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<KomgaSeries> {
@@ -19,8 +19,9 @@ export class SeriesService extends BaseApiService {
}
}
static async clearSeriesCache(seriesId: string) {
serverCacheService.delete(`series-${seriesId}`);
static async invalidateSeriesCache(seriesId: string): Promise<void> {
const cacheService = await getServerCacheService();
cacheService.delete(`series-${seriesId}`);
}
static async getAllSeriesBooks(seriesId: string): Promise<KomgaBook[]> {
@@ -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<void> {
const cacheService = await getServerCacheService();
cacheService.delete(`series-${seriesId}-all-books`);
}
static async getFirstBook(seriesId: string): Promise<string> {

View File

@@ -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<void> {
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<ServerCacheService> {
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<ServerCacheService>;
export const getServerCacheService = async (): Promise<ServerCacheService> => {
if (!initializedInstance) {
initializedInstance = ServerCacheService.getInstance();
}
return initializedInstance;
};
// Exporter aussi la classe pour les tests
export { ServerCacheService };