diff --git a/src/app/libraries/[libraryId]/page.tsx b/src/app/libraries/[libraryId]/page.tsx index 42c805c..c7191b1 100644 --- a/src/app/libraries/[libraryId]/page.tsx +++ b/src/app/libraries/[libraryId]/page.tsx @@ -1,5 +1,6 @@ import { cookies } from "next/headers"; import { PaginatedSeriesGrid } from "@/components/library/PaginatedSeriesGrid"; +import { komgaConfigService } from "@/lib/services/komga-config.service"; interface PageProps { params: { libraryId: string }; @@ -9,38 +10,24 @@ interface PageProps { const PAGE_SIZE = 20; async function getLibrarySeries(libraryId: string, page: number = 1, unreadOnly: boolean = false) { - const configCookie = cookies().get("komgaCredentials"); - - if (!configCookie) { - throw new Error("Configuration Komga manquante"); - } - try { - const config = JSON.parse(atob(configCookie.value)); - - if (!config.serverUrl || !config.credentials?.username || !config.credentials?.password) { - throw new Error("Configuration Komga invalide ou incomplète"); - } + const cookiesStore = cookies(); + const config = komgaConfigService.validateAndGetConfig(cookiesStore); // Paramètres de pagination const pageIndex = page - 1; // L'API Komga utilise un index base 0 // Construire l'URL avec les paramètres - let url = `${config.serverUrl}/api/v1/series?library_id=${libraryId}&page=${pageIndex}&size=${PAGE_SIZE}`; - - // Ajouter le filtre pour les séries non lues et en cours si nécessaire + let path = `series?library_id=${libraryId}&page=${pageIndex}&size=${PAGE_SIZE}`; if (unreadOnly) { - url += "&read_status=UNREAD&read_status=IN_PROGRESS"; + path += "&read_status=UNREAD&read_status=IN_PROGRESS"; } - const credentials = `${config.credentials.username}:${config.credentials.password}`; - const auth = Buffer.from(credentials).toString("base64"); + const url = komgaConfigService.buildApiUrl(path, cookiesStore); + const headers = komgaConfigService.getAuthHeaders(cookiesStore); const response = await fetch(url, { - headers: { - Authorization: `Basic ${auth}`, - Accept: "application/json", - }, + headers, next: { revalidate: 300 }, // Cache de 5 minutes }); diff --git a/src/app/series/[seriesId]/page.tsx b/src/app/series/[seriesId]/page.tsx index 43b2465..ef67d31 100644 --- a/src/app/series/[seriesId]/page.tsx +++ b/src/app/series/[seriesId]/page.tsx @@ -2,6 +2,7 @@ import { cookies } from "next/headers"; import { PaginatedBookGrid } from "@/components/series/PaginatedBookGrid"; import { SeriesHeader } from "@/components/series/SeriesHeader"; import { KomgaSeries, KomgaBook } from "@/types/komga"; +import { komgaConfigService } from "@/lib/services/komga-config.service"; interface PageProps { params: { seriesId: string }; @@ -14,19 +15,9 @@ export default async function SeriesPage({ params, searchParams }: PageProps) { const currentPage = searchParams.page ? parseInt(searchParams.page) : 1; const unreadOnly = searchParams.unread === "true"; - const configCookie = cookies().get("komgaCredentials"); - if (!configCookie) { - throw new Error("Configuration Komga manquante"); - } - try { - const config = JSON.parse(atob(configCookie.value)); - if (!config.serverUrl || !config.credentials?.username || !config.credentials?.password) { - throw new Error("Configuration Komga invalide ou incomplète"); - } - - const credentials = `${config.credentials.username}:${config.credentials.password}`; - const auth = Buffer.from(credentials).toString("base64"); + const cookiesStore = cookies(); + const config = komgaConfigService.validateAndGetConfig(cookiesStore); // Paramètres de pagination const pageIndex = currentPage - 1; // L'API Komga utilise un index base 0 @@ -34,25 +25,22 @@ export default async function SeriesPage({ params, searchParams }: PageProps) { // Appels API parallèles pour les détails de la série et les tomes const [seriesResponse, booksResponse] = await Promise.all([ // Détails de la série - fetch(`${config.serverUrl}/api/v1/series/${params.seriesId}`, { - headers: { - Authorization: `Basic ${auth}`, - Accept: "application/json", - }, + fetch(komgaConfigService.buildApiUrl(`series/${params.seriesId}`, cookiesStore), { + headers: komgaConfigService.getAuthHeaders(cookiesStore), next: { revalidate: 300 }, }), // Liste des tomes avec pagination et filtre fetch( - `${config.serverUrl}/api/v1/series/${ - params.seriesId - }/books?page=${pageIndex}&size=${PAGE_SIZE}&sort=metadata.numberSort,asc${ - unreadOnly ? "&read_status=UNREAD&read_status=IN_PROGRESS" : "" - }`, + komgaConfigService.buildApiUrl( + `series/${ + params.seriesId + }/books?page=${pageIndex}&size=${PAGE_SIZE}&sort=metadata.numberSort,asc${ + unreadOnly ? "&read_status=UNREAD&read_status=IN_PROGRESS" : "" + }`, + cookiesStore + ), { - headers: { - Authorization: `Basic ${auth}`, - Accept: "application/json", - }, + headers: komgaConfigService.getAuthHeaders(cookiesStore), next: { revalidate: 300 }, } ), diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 091779c..07e6d78 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/navigation"; import { storageService } from "@/lib/services/storage.service"; import { AuthError } from "@/types/auth"; import { useToast } from "@/components/ui/use-toast"; +import { komgaConfigService } from "@/lib/services/komga-config.service"; interface ErrorMessage { message: string; @@ -136,15 +137,21 @@ export default function SettingsPage() { password, }; - storageService.setKomgaConfig( - { - serverUrl: newConfig.serverUrl, - credentials: { username: newConfig.username, password: newConfig.password }, + const komgaConfig = { + serverUrl: newConfig.serverUrl, + credentials: { + username: newConfig.username, + password: newConfig.password, }, - true - ); + }; + komgaConfigService.setConfig(komgaConfig, true); setConfig(newConfig); + + // Émettre un événement pour notifier les autres composants + const configChangeEvent = new CustomEvent("komga-config-changed", { detail: komgaConfig }); + window.dispatchEvent(configChangeEvent); + toast({ title: "Configuration sauvegardée", description: "La configuration a été sauvegardée avec succès", diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..569943a --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,6 @@ +// Clés de stockage +export const STORAGE_KEYS = { + CREDENTIALS: "komgaCredentials", + USER: "stripUser", + TTL_CONFIG: "ttlConfig", +} as const; diff --git a/src/lib/services/base-api.service.ts b/src/lib/services/base-api.service.ts index 88f533d..8716901 100644 --- a/src/lib/services/base-api.service.ts +++ b/src/lib/services/base-api.service.ts @@ -1,22 +1,15 @@ import { cookies } from "next/headers"; import { AuthConfig } from "@/types/auth"; import { serverCacheService } from "./server-cache.service"; +import { komgaConfigService } from "./komga-config.service"; // Types de cache disponibles export type CacheType = "DEFAULT" | "HOME" | "LIBRARIES" | "SERIES" | "BOOKS" | "IMAGES"; export abstract class BaseApiService { protected static async getKomgaConfig(): Promise { - const configCookie = cookies().get("komgaCredentials"); - if (!configCookie) { - throw new Error("Configuration Komga manquante"); - } - - try { - return JSON.parse(atob(configCookie.value)); - } catch (error) { - throw new Error("Configuration Komga invalide"); - } + const cookiesStore = cookies(); + return komgaConfigService.validateAndGetConfig(cookiesStore); } protected static getAuthHeaders(config: AuthConfig): Headers { diff --git a/src/lib/services/komga-config.service.ts b/src/lib/services/komga-config.service.ts new file mode 100644 index 0000000..e5a7cdc --- /dev/null +++ b/src/lib/services/komga-config.service.ts @@ -0,0 +1,134 @@ +import { AuthConfig } from "@/types/auth"; +import { storageService } from "./storage.service"; +import { STORAGE_KEYS } from "@/lib/constants"; + +const { CREDENTIALS } = STORAGE_KEYS; + +class KomgaConfigService { + private static instance: KomgaConfigService; + + private constructor() {} + + public static getInstance(): KomgaConfigService { + if (!KomgaConfigService.instance) { + KomgaConfigService.instance = new KomgaConfigService(); + } + return KomgaConfigService.instance; + } + + /** + * Récupère la configuration Komga (fonctionne côté client et serveur) + */ + getConfig(serverCookies?: any): AuthConfig | null { + // Côté serveur + if (typeof window === "undefined" && serverCookies) { + try { + const configCookie = serverCookies.get(CREDENTIALS)?.value; + if (!configCookie) return null; + return JSON.parse(atob(configCookie)); + } catch (error) { + console.error( + "KomgaConfigService - Erreur lors de la récupération de la config côté serveur:", + error + ); + return null; + } + } + + // Côté client + return storageService.getCredentials(); + } + + /** + * Définit la configuration Komga (côté client uniquement) + */ + setConfig(config: AuthConfig, remember: boolean = false): void { + if (typeof window === "undefined") { + console.warn("KomgaConfigService - setConfig ne peut être utilisé que côté client"); + return; + } + + const storage = remember ? localStorage : sessionStorage; + const encoded = btoa(JSON.stringify(config)); + + // Stocker dans le storage + storage.setItem(CREDENTIALS, encoded); + + // Définir le cookie + const cookieValue = `${CREDENTIALS}=${encoded}; path=/; samesite=strict`; + const maxAge = remember ? `; max-age=${30 * 24 * 60 * 60}` : ""; + document.cookie = cookieValue + maxAge; + } + + /** + * Vérifie si la configuration est valide + */ + isConfigValid(config: AuthConfig | null): boolean { + if (!config) return false; + return !!(config.serverUrl && config.credentials?.username && config.credentials?.password); + } + + /** + * Efface la configuration + */ + clearConfig(): void { + if (typeof window === "undefined") return; + + localStorage.removeItem(CREDENTIALS); + sessionStorage.removeItem(CREDENTIALS); + document.cookie = `${CREDENTIALS}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`; + } + + /** + * Récupère l'URL du serveur à partir de la configuration + */ + getServerUrl(serverCookies?: any): string | null { + const config = this.getConfig(serverCookies); + return config?.serverUrl || null; + } + + /** + * Récupère les credentials à partir de la configuration + */ + getCredentials(serverCookies?: any): { username: string; password: string } | null { + const config = this.getConfig(serverCookies); + return config?.credentials || null; + } + + /** + * Construit une URL complète pour l'API Komga + */ + buildApiUrl(path: string, serverCookies?: any): string { + const serverUrl = this.getServerUrl(serverCookies); + if (!serverUrl) throw new Error("URL du serveur non disponible"); + return `${serverUrl}/api/v1/${path}`; + } + + /** + * Génère les en-têtes d'authentification pour les requêtes + */ + getAuthHeaders(serverCookies?: any): Headers { + const credentials = this.getCredentials(serverCookies); + if (!credentials) throw new Error("Credentials non disponibles"); + + const auth = Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64"); + const headers = new Headers(); + headers.set("Authorization", `Basic ${auth}`); + headers.set("Accept", "application/json"); + + return headers; + } + + /** + * Vérifie et récupère la configuration complète, lance une erreur si invalide + */ + validateAndGetConfig(serverCookies?: any): AuthConfig { + const config = this.getConfig(serverCookies); + if (!this.isConfigValid(config)) { + throw new Error("Configuration Komga manquante ou invalide"); + } + return config as AuthConfig; + } +} + +export const komgaConfigService = KomgaConfigService.getInstance(); diff --git a/src/lib/services/storage.service.ts b/src/lib/services/storage.service.ts index 15111d7..538a845 100644 --- a/src/lib/services/storage.service.ts +++ b/src/lib/services/storage.service.ts @@ -1,8 +1,11 @@ import { AuthConfig } from "@/types/auth"; +import { STORAGE_KEYS } from "@/lib/constants"; -const KOMGACREDENTIALS_KEY = "komgaCredentials"; -const USER_KEY = "stripUser"; -const TTL_CONFIG_KEY = "ttlConfig"; +const { + CREDENTIALS: KOMGACREDENTIALS_KEY, + USER: USER_KEY, + TTL_CONFIG: TTL_CONFIG_KEY, +} = STORAGE_KEYS; interface TTLConfig { defaultTTL: number; diff --git a/src/middleware.ts b/src/middleware.ts index bcac563..1c651e8 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; +import { komgaConfigService } from "@/lib/services/komga-config.service"; // Routes qui ne nécessitent pas d'authentification const publicRoutes = ["/login", "/register", "/images"]; @@ -21,17 +22,14 @@ export function middleware(request: NextRequest) { // Vérifier si c'est une route d'API if (pathname.startsWith("/api/")) { - // Vérifier les credentials Komga - const configCookie = request.cookies.get("komgaCredentials"); + // Vérifier la configuration Komga + const config = komgaConfigService.getConfig(request.cookies); - if (!configCookie) { - return NextResponse.json({ error: "Configuration Komga manquante" }, { status: 401 }); - } - - try { - JSON.parse(atob(configCookie.value)); - } catch (error) { - return NextResponse.json({ error: "Configuration Komga invalide" }, { status: 401 }); + if (!komgaConfigService.isConfigValid(config)) { + return NextResponse.json( + { error: "Configuration Komga manquante ou invalide" }, + { status: 401 } + ); } return NextResponse.next();