refactor: Amélioration de la page d'accueil et du cache - Suppression du cache.service.ts redondant - Mise à jour de l'ordre des sections (ongoing, onDeck, recentlyRead) - Correction des types et interfaces

This commit is contained in:
Julien Froidefond
2025-02-12 13:09:31 +01:00
parent 28e2248296
commit 0483fee8cd
11 changed files with 306 additions and 336 deletions

View File

@@ -1,151 +0,0 @@
class CacheService {
private static instance: CacheService;
private cacheName = "komga-cache-v1";
private static readonly fiveMinutes = 5 * 60;
private static readonly tenMinutes = 10 * 60;
private static readonly twentyFourHours = 24 * 60 * 60;
private static readonly oneMinute = 1 * 60;
private static readonly noCache = 0;
// Configuration des temps de cache en secondes
private static readonly TTL = {
DEFAULT: CacheService.fiveMinutes, // 5 minutes
HOME: CacheService.fiveMinutes, // 5 minutes
LIBRARIES: CacheService.tenMinutes, // 10 minutes
SERIES: CacheService.fiveMinutes, // 5 minutes
BOOKS: CacheService.fiveMinutes, // 5 minutes
IMAGES: CacheService.twentyFourHours, // 24 heures
READ_PROGRESS: CacheService.oneMinute, // 1 minute
};
// private static readonly TTL = {
// DEFAULT: CacheService.noCache, // 5 minutes
// HOME: CacheService.noCache, // 5 minutes
// LIBRARIES: CacheService.noCache, // 10 minutes
// SERIES: CacheService.noCache, // 5 minutes
// BOOKS: CacheService.noCache, // 5 minutes
// IMAGES: CacheService.noCache, // 24 heures
// READ_PROGRESS: CacheService.noCache, // 1 minute
// };
private constructor() {}
public static getInstance(): CacheService {
if (!CacheService.instance) {
CacheService.instance = new CacheService();
}
return CacheService.instance;
}
/**
* Retourne le TTL pour un type de données spécifique
*/
public getTTL(type: keyof typeof CacheService.TTL): number {
return CacheService.TTL[type];
}
/**
* Met en cache une réponse avec une durée de vie
*/
async set(
key: string,
response: Response,
ttl: number = CacheService.TTL.DEFAULT
): Promise<void> {
if (typeof window === "undefined") return;
try {
const cache = await caches.open(this.cacheName);
const headers = new Headers(response.headers);
headers.append("x-cache-timestamp", Date.now().toString());
headers.append("x-cache-ttl", ttl.toString());
const cachedResponse = new Response(await response.clone().blob(), {
status: response.status,
statusText: response.statusText,
headers,
});
await cache.put(key, cachedResponse);
} catch (error) {
console.error("Erreur lors de la mise en cache:", error);
}
}
/**
* Récupère une réponse du cache si elle est valide
*/
async get(key: string): Promise<Response | null> {
if (typeof window === "undefined") return null;
try {
const cache = await caches.open(this.cacheName);
const response = await cache.match(key);
if (!response) return null;
// Vérifier si la réponse est expirée
const timestamp = parseInt(response.headers.get("x-cache-timestamp") || "0");
const ttl = parseInt(response.headers.get("x-cache-ttl") || "0");
const now = Date.now();
if (now - timestamp > ttl * 1000) {
await cache.delete(key);
return null;
}
return response;
} catch (error) {
console.error("Erreur lors de la lecture du cache:", error);
return null;
}
}
/**
* Supprime une entrée du cache
*/
async delete(key: string): Promise<void> {
if (typeof window === "undefined") return;
try {
const cache = await caches.open(this.cacheName);
await cache.delete(key);
} catch (error) {
console.error("Erreur lors de la suppression du cache:", error);
}
}
/**
* Vide le cache
*/
async clear(): Promise<void> {
if (typeof window === "undefined") return;
try {
await caches.delete(this.cacheName);
} catch (error) {
console.error("Erreur lors du nettoyage du cache:", error);
}
}
/**
* Récupère une réponse du cache ou fait l'appel API si nécessaire
*/
async getOrFetch(
key: string,
fetcher: () => Promise<Response>,
type: keyof typeof CacheService.TTL = "DEFAULT"
): Promise<Response> {
const cachedResponse = await this.get(key);
if (cachedResponse) {
return cachedResponse;
}
const response = await fetcher();
const clonedResponse = response.clone();
await this.set(key, clonedResponse, CacheService.TTL[type]);
return response;
}
}
export const cacheService = CacheService.getInstance();

View File

@@ -5,7 +5,7 @@ import { LibraryResponse } from "@/types/library";
interface HomeData {
ongoing: KomgaSeries[];
recentlyRead: KomgaBook[];
popular: KomgaSeries[];
onDeck: KomgaBook[];
}
export class HomeService extends BaseApiService {
@@ -34,27 +34,25 @@ export class HomeService extends BaseApiService {
media_status: "READY",
});
const popularUrl = this.buildUrl(config, "series", {
const onDeckUrl = this.buildUrl(config, "books/ondeck", {
page: "0",
size: "20",
sort: "metadata.titleSort,asc",
media_status: "READY",
});
// Appels API parallèles avec fetchFromApi
const [ongoing, recentlyRead, popular] = await Promise.all([
const [ongoing, recentlyRead, onDeck] = await Promise.all([
this.fetchFromApi<LibraryResponse<KomgaSeries>>(ongoingUrl, headers),
this.fetchFromApi<LibraryResponse<KomgaBook>>(recentlyReadUrl, headers),
this.fetchFromApi<LibraryResponse<KomgaSeries>>(popularUrl, headers),
this.fetchFromApi<LibraryResponse<KomgaBook>>(onDeckUrl, headers),
]);
return {
ongoing: ongoing.content || [],
recentlyRead: recentlyRead.content || [],
popular: popular.content || [],
onDeck: onDeck.content || [],
};
},
"HOME" // Type de cache
"HOME"
);
} catch (error) {
return this.handleError(error, "Impossible de récupérer les données de la page d'accueil");

View File

@@ -8,15 +8,21 @@ class ServerCacheService {
private static instance: ServerCacheService;
private cache: Map<string, { data: unknown; expiry: number }> = new Map();
// Configuration des temps de cache en secondes (identique à CacheService)
private static readonly fiveMinutes = 5 * 60;
private static readonly tenMinutes = 10 * 60;
private static readonly twentyFourHours = 24 * 60 * 60;
private static readonly oneMinute = 1 * 60;
private static readonly noCache = 0;
// Configuration des temps de cache en secondes
private static readonly TTL = {
DEFAULT: 5 * 60, // 5 minutes
HOME: 5 * 60, // 5 minutes
LIBRARIES: 10 * 60, // 10 minutes
SERIES: 5 * 60, // 5 minutes
BOOKS: 5 * 60, // 5 minutes
IMAGES: 24 * 60 * 60, // 24 heures
READ_PROGRESS: 1 * 60, // 1 minute
DEFAULT: ServerCacheService.fiveMinutes, // 5 minutes
HOME: ServerCacheService.oneMinute, // 1 minute
LIBRARIES: ServerCacheService.tenMinutes, // 10 minutes
SERIES: ServerCacheService.fiveMinutes, // 5 minutes
BOOKS: ServerCacheService.fiveMinutes, // 5 minutes
IMAGES: ServerCacheService.twentyFourHours, // 24 heures
READ_PROGRESS: ServerCacheService.oneMinute, // 1 minute
};
private constructor() {
@@ -89,6 +95,7 @@ class ServerCacheService {
const cached = this.cache.get(key);
if (cached && cached.expiry > now) {
console.log("Cache hit for key:", key);
return cached.data as T;
}

View File

@@ -0,0 +1,30 @@
import { BaseApiService } from "./base-api.service";
import { AuthConfig } from "@/types/auth";
import { KomgaLibrary } from "@/types/komga";
export class TestService extends BaseApiService {
static async testConnection(config: AuthConfig): Promise<{ libraries: KomgaLibrary[] }> {
try {
const url = this.buildUrl(config, "libraries");
const headers = this.getAuthHeaders(config);
const response = await fetch(url, { headers });
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || "Erreur lors du test de connexion");
}
const libraries = await response.json();
return { libraries };
} catch (error) {
console.error("Erreur lors du test de connexion:", error);
if (error instanceof Error && error.message.includes("fetch")) {
throw new Error(
"Impossible de se connecter au serveur. Vérifiez l'URL et que le serveur est accessible."
);
}
throw error instanceof Error ? error : new Error("Erreur lors du test de connexion");
}
}
}