perf: optimize Komga caching with unstable_cache for POST requests and reduce API calls
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
- Fix POST requests (series/list, books/list) not being cached by Next.js fetch cache by wrapping them with unstable_cache in the private fetch method - Wrap getHomeData() entirely with unstable_cache so all 5 home requests are cached as a single unit, reducing cold-start cost from 5 parallel calls to 0 on cache hit - Remove N+1 book count enrichment from getLibraries() (8 extra calls per cold start) as LibraryDto does not return booksCount and the value was only used in BackgroundSettings - Simplify getLibraryById() to reuse cached getLibraries() data instead of making separate HTTP calls (saves 2 calls per library page load) - Fix cache debug logs: replace misleading x-nextjs-cache header check (always UNKNOWN on external APIs) with pre-request logs showing the configured cache strategy - Remove book count display from BackgroundSettings as it is no longer fetched Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -278,7 +278,7 @@ export function BackgroundSettings({ initialLibraries }: BackgroundSettingsProps
|
|||||||
htmlFor={`lib-${library.id}`}
|
htmlFor={`lib-${library.id}`}
|
||||||
className="cursor-pointer font-normal text-sm"
|
className="cursor-pointer font-normal text-sm"
|
||||||
>
|
>
|
||||||
{library.name} ({library.bookCount} livres)
|
{library.name}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -34,14 +34,8 @@ export function KomgaSettings({ initialConfig }: KomgaSettingsProps) {
|
|||||||
const handleTest = async () => {
|
const handleTest = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const form = document.querySelector("form") as HTMLFormElement;
|
|
||||||
const formData = new FormData(form);
|
|
||||||
const serverUrl = formData.get("serverUrl") as string;
|
|
||||||
const username = formData.get("username") as string;
|
|
||||||
const password = formData.get("password") as string;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await testKomgaConnection(serverUrl.trim(), username, password || config.password);
|
const result = await testKomgaConnection(config.serverUrl.trim(), config.username, config.password);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.message);
|
throw new Error(result.message);
|
||||||
@@ -55,8 +49,8 @@ export function KomgaSettings({ initialConfig }: KomgaSettingsProps) {
|
|||||||
logger.error({ err: error }, "Erreur:");
|
logger.error({ err: error }, "Erreur:");
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t("settings.komga.error.title"),
|
title: t("settings.komga.error.connectionTitle"),
|
||||||
description: t("settings.komga.error.message"),
|
description: t("settings.komga.error.connectionMessage"),
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -134,7 +134,9 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Error saving configuration",
|
"title": "Error saving configuration",
|
||||||
"message": "An error occurred while saving the configuration"
|
"message": "An error occurred while saving the configuration",
|
||||||
|
"connectionTitle": "Connection error",
|
||||||
|
"connectionMessage": "Unable to connect to the Komga server. Check the URL and credentials."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cache": {
|
"cache": {
|
||||||
@@ -361,7 +363,6 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"MONGODB_MISSING_URI": "MongoDB URI missing",
|
"MONGODB_MISSING_URI": "MongoDB URI missing",
|
||||||
"MONGODB_CONNECTION_FAILED": "MongoDB connection failed",
|
"MONGODB_CONNECTION_FAILED": "MongoDB connection failed",
|
||||||
|
|
||||||
"AUTH_UNAUTHENTICATED": "Unauthenticated",
|
"AUTH_UNAUTHENTICATED": "Unauthenticated",
|
||||||
"AUTH_INVALID_CREDENTIALS": "Invalid credentials",
|
"AUTH_INVALID_CREDENTIALS": "Invalid credentials",
|
||||||
"AUTH_PASSWORD_NOT_STRONG": "Password is not strong enough",
|
"AUTH_PASSWORD_NOT_STRONG": "Password is not strong enough",
|
||||||
@@ -371,7 +372,6 @@
|
|||||||
"AUTH_LOGOUT_ERROR": "Error during logout",
|
"AUTH_LOGOUT_ERROR": "Error during logout",
|
||||||
"AUTH_LOGIN_ERROR": "Error during login",
|
"AUTH_LOGIN_ERROR": "Error during login",
|
||||||
"AUTH_REGISTER_ERROR": "Error during registration",
|
"AUTH_REGISTER_ERROR": "Error during registration",
|
||||||
|
|
||||||
"KOMGA_MISSING_CONFIG": "Komga configuration missing",
|
"KOMGA_MISSING_CONFIG": "Komga configuration missing",
|
||||||
"KOMGA_MISSING_CREDENTIALS": "Komga credentials missing",
|
"KOMGA_MISSING_CREDENTIALS": "Komga credentials missing",
|
||||||
"KOMGA_CONNECTION_ERROR": "Error connecting to Komga server",
|
"KOMGA_CONNECTION_ERROR": "Error connecting to Komga server",
|
||||||
@@ -380,25 +380,20 @@
|
|||||||
"STRIPSTREAM_MISSING_CONFIG": "Stripstream Librarian configuration missing",
|
"STRIPSTREAM_MISSING_CONFIG": "Stripstream Librarian configuration missing",
|
||||||
"STRIPSTREAM_CONNECTION_ERROR": "Error connecting to Stripstream Librarian",
|
"STRIPSTREAM_CONNECTION_ERROR": "Error connecting to Stripstream Librarian",
|
||||||
"STRIPSTREAM_HTTP_ERROR": "HTTP error while communicating with Stripstream Librarian",
|
"STRIPSTREAM_HTTP_ERROR": "HTTP error while communicating with Stripstream Librarian",
|
||||||
|
|
||||||
"CONFIG_SAVE_ERROR": "Error saving configuration",
|
"CONFIG_SAVE_ERROR": "Error saving configuration",
|
||||||
"CONFIG_FETCH_ERROR": "Error fetching configuration",
|
"CONFIG_FETCH_ERROR": "Error fetching configuration",
|
||||||
"CONFIG_TTL_SAVE_ERROR": "Error saving TTL configuration",
|
"CONFIG_TTL_SAVE_ERROR": "Error saving TTL configuration",
|
||||||
"CONFIG_TTL_FETCH_ERROR": "Error fetching TTL configuration",
|
"CONFIG_TTL_FETCH_ERROR": "Error fetching TTL configuration",
|
||||||
|
|
||||||
"LIBRARY_NOT_FOUND": "Library not found",
|
"LIBRARY_NOT_FOUND": "Library not found",
|
||||||
"LIBRARY_FETCH_ERROR": "Error fetching library",
|
"LIBRARY_FETCH_ERROR": "Error fetching library",
|
||||||
"LIBRARY_SCAN_ERROR": "Error scanning library",
|
"LIBRARY_SCAN_ERROR": "Error scanning library",
|
||||||
|
|
||||||
"SERIES_FETCH_ERROR": "Error fetching series",
|
"SERIES_FETCH_ERROR": "Error fetching series",
|
||||||
"SERIES_NO_BOOKS_FOUND": "No books found in series",
|
"SERIES_NO_BOOKS_FOUND": "No books found in series",
|
||||||
|
|
||||||
"BOOK_NOT_FOUND": "Book not found",
|
"BOOK_NOT_FOUND": "Book not found",
|
||||||
"BOOK_PROGRESS_UPDATE_ERROR": "Error updating reading progress",
|
"BOOK_PROGRESS_UPDATE_ERROR": "Error updating reading progress",
|
||||||
"BOOK_PROGRESS_DELETE_ERROR": "Error deleting reading progress",
|
"BOOK_PROGRESS_DELETE_ERROR": "Error deleting reading progress",
|
||||||
"BOOK_PAGES_FETCH_ERROR": "Error fetching book pages",
|
"BOOK_PAGES_FETCH_ERROR": "Error fetching book pages",
|
||||||
"BOOK_DOWNLOAD_CANCELLED": "Book download cancelled",
|
"BOOK_DOWNLOAD_CANCELLED": "Book download cancelled",
|
||||||
|
|
||||||
"FAVORITE_ADD_ERROR": "Error adding to favorites",
|
"FAVORITE_ADD_ERROR": "Error adding to favorites",
|
||||||
"FAVORITE_DELETE_ERROR": "Error removing from favorites",
|
"FAVORITE_DELETE_ERROR": "Error removing from favorites",
|
||||||
"FAVORITE_FETCH_ERROR": "Error fetching favorites",
|
"FAVORITE_FETCH_ERROR": "Error fetching favorites",
|
||||||
@@ -406,26 +401,19 @@
|
|||||||
"FAVORITE_NETWORK_ERROR": "Network error while accessing favorites",
|
"FAVORITE_NETWORK_ERROR": "Network error while accessing favorites",
|
||||||
"FAVORITE_SERVER_ERROR": "Server error while accessing favorites",
|
"FAVORITE_SERVER_ERROR": "Server error while accessing favorites",
|
||||||
"FAVORITE_STATUS_CHECK_ERROR": "Error checking favorites status",
|
"FAVORITE_STATUS_CHECK_ERROR": "Error checking favorites status",
|
||||||
|
|
||||||
"PREFERENCES_FETCH_ERROR": "Error fetching preferences",
|
"PREFERENCES_FETCH_ERROR": "Error fetching preferences",
|
||||||
"PREFERENCES_UPDATE_ERROR": "Error updating preferences",
|
"PREFERENCES_UPDATE_ERROR": "Error updating preferences",
|
||||||
"PREFERENCES_CONTEXT_ERROR": "Preferences context error",
|
"PREFERENCES_CONTEXT_ERROR": "Preferences context error",
|
||||||
|
|
||||||
"UI_TABS_TRIGGER_ERROR": "Error triggering tabs",
|
"UI_TABS_TRIGGER_ERROR": "Error triggering tabs",
|
||||||
"UI_TABS_CONTENT_ERROR": "Error loading tabs content",
|
"UI_TABS_CONTENT_ERROR": "Error loading tabs content",
|
||||||
|
|
||||||
"IMAGE_FETCH_ERROR": "Error fetching image",
|
"IMAGE_FETCH_ERROR": "Error fetching image",
|
||||||
|
|
||||||
"HOME_FETCH_ERROR": "Error fetching home page",
|
"HOME_FETCH_ERROR": "Error fetching home page",
|
||||||
|
|
||||||
"MIDDLEWARE_UNAUTHORIZED": "Unauthorized",
|
"MIDDLEWARE_UNAUTHORIZED": "Unauthorized",
|
||||||
"MIDDLEWARE_INVALID_TOKEN": "Invalid authentication token",
|
"MIDDLEWARE_INVALID_TOKEN": "Invalid authentication token",
|
||||||
"MIDDLEWARE_INVALID_SESSION": "Invalid session",
|
"MIDDLEWARE_INVALID_SESSION": "Invalid session",
|
||||||
|
|
||||||
"CLIENT_FETCH_ERROR": "Error fetching data",
|
"CLIENT_FETCH_ERROR": "Error fetching data",
|
||||||
"CLIENT_NETWORK_ERROR": "Network error",
|
"CLIENT_NETWORK_ERROR": "Network error",
|
||||||
"CLIENT_REQUEST_FAILED": "Request failed",
|
"CLIENT_REQUEST_FAILED": "Request failed",
|
||||||
|
|
||||||
"GENERIC_ERROR": "An error occurred"
|
"GENERIC_ERROR": "An error occurred"
|
||||||
},
|
},
|
||||||
"reader": {
|
"reader": {
|
||||||
|
|||||||
@@ -134,7 +134,9 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Erreur lors de la sauvegarde de la configuration",
|
"title": "Erreur lors de la sauvegarde de la configuration",
|
||||||
"message": "Une erreur est survenue lors de la sauvegarde de la configuration"
|
"message": "Une erreur est survenue lors de la sauvegarde de la configuration",
|
||||||
|
"connectionTitle": "Erreur de connexion",
|
||||||
|
"connectionMessage": "Impossible de se connecter au serveur Komga. Vérifiez l'URL et les identifiants."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cache": {
|
"cache": {
|
||||||
@@ -359,7 +361,6 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"MONGODB_MISSING_URI": "URI MongoDB manquante",
|
"MONGODB_MISSING_URI": "URI MongoDB manquante",
|
||||||
"MONGODB_CONNECTION_FAILED": "Erreur lors de la connexion à MongoDB",
|
"MONGODB_CONNECTION_FAILED": "Erreur lors de la connexion à MongoDB",
|
||||||
|
|
||||||
"AUTH_UNAUTHENTICATED": "Non authentifié",
|
"AUTH_UNAUTHENTICATED": "Non authentifié",
|
||||||
"AUTH_INVALID_CREDENTIALS": "Identifiants invalides",
|
"AUTH_INVALID_CREDENTIALS": "Identifiants invalides",
|
||||||
"AUTH_PASSWORD_NOT_STRONG": "Le mot de passe n'est pas assez fort",
|
"AUTH_PASSWORD_NOT_STRONG": "Le mot de passe n'est pas assez fort",
|
||||||
@@ -369,7 +370,6 @@
|
|||||||
"AUTH_LOGOUT_ERROR": "Erreur lors de la déconnexion",
|
"AUTH_LOGOUT_ERROR": "Erreur lors de la déconnexion",
|
||||||
"AUTH_LOGIN_ERROR": "Erreur lors de la connexion",
|
"AUTH_LOGIN_ERROR": "Erreur lors de la connexion",
|
||||||
"AUTH_REGISTER_ERROR": "Erreur lors de l'inscription",
|
"AUTH_REGISTER_ERROR": "Erreur lors de l'inscription",
|
||||||
|
|
||||||
"KOMGA_MISSING_CONFIG": "Configuration Komga manquante",
|
"KOMGA_MISSING_CONFIG": "Configuration Komga manquante",
|
||||||
"KOMGA_MISSING_CREDENTIALS": "Identifiants Komga manquants",
|
"KOMGA_MISSING_CREDENTIALS": "Identifiants Komga manquants",
|
||||||
"KOMGA_CONNECTION_ERROR": "Erreur de connexion au serveur Komga",
|
"KOMGA_CONNECTION_ERROR": "Erreur de connexion au serveur Komga",
|
||||||
@@ -378,25 +378,20 @@
|
|||||||
"STRIPSTREAM_MISSING_CONFIG": "Configuration Stripstream Librarian manquante",
|
"STRIPSTREAM_MISSING_CONFIG": "Configuration Stripstream Librarian manquante",
|
||||||
"STRIPSTREAM_CONNECTION_ERROR": "Erreur de connexion à Stripstream Librarian",
|
"STRIPSTREAM_CONNECTION_ERROR": "Erreur de connexion à Stripstream Librarian",
|
||||||
"STRIPSTREAM_HTTP_ERROR": "Erreur HTTP lors de la communication avec Stripstream Librarian",
|
"STRIPSTREAM_HTTP_ERROR": "Erreur HTTP lors de la communication avec Stripstream Librarian",
|
||||||
|
|
||||||
"CONFIG_SAVE_ERROR": "Erreur lors de la sauvegarde de la configuration",
|
"CONFIG_SAVE_ERROR": "Erreur lors de la sauvegarde de la configuration",
|
||||||
"CONFIG_FETCH_ERROR": "Erreur lors de la récupération de la configuration",
|
"CONFIG_FETCH_ERROR": "Erreur lors de la récupération de la configuration",
|
||||||
"CONFIG_TTL_SAVE_ERROR": "Erreur lors de la sauvegarde des TTL",
|
"CONFIG_TTL_SAVE_ERROR": "Erreur lors de la sauvegarde des TTL",
|
||||||
"CONFIG_TTL_FETCH_ERROR": "Erreur lors de la récupération des TTL",
|
"CONFIG_TTL_FETCH_ERROR": "Erreur lors de la récupération des TTL",
|
||||||
|
|
||||||
"LIBRARY_NOT_FOUND": "Bibliothèque introuvable",
|
"LIBRARY_NOT_FOUND": "Bibliothèque introuvable",
|
||||||
"LIBRARY_FETCH_ERROR": "Erreur lors de la récupération de la bibliothèque",
|
"LIBRARY_FETCH_ERROR": "Erreur lors de la récupération de la bibliothèque",
|
||||||
"LIBRARY_SCAN_ERROR": "Erreur lors de l'analyse de la bibliothèque",
|
"LIBRARY_SCAN_ERROR": "Erreur lors de l'analyse de la bibliothèque",
|
||||||
|
|
||||||
"SERIES_FETCH_ERROR": "Erreur lors de la récupération des séries",
|
"SERIES_FETCH_ERROR": "Erreur lors de la récupération des séries",
|
||||||
"SERIES_NO_BOOKS_FOUND": "Aucun livre trouvé dans la série",
|
"SERIES_NO_BOOKS_FOUND": "Aucun livre trouvé dans la série",
|
||||||
|
|
||||||
"BOOK_NOT_FOUND": "Livre introuvable",
|
"BOOK_NOT_FOUND": "Livre introuvable",
|
||||||
"BOOK_PROGRESS_UPDATE_ERROR": "Erreur lors de la mise à jour de la progression",
|
"BOOK_PROGRESS_UPDATE_ERROR": "Erreur lors de la mise à jour de la progression",
|
||||||
"BOOK_PROGRESS_DELETE_ERROR": "Erreur lors de la suppression de la progression",
|
"BOOK_PROGRESS_DELETE_ERROR": "Erreur lors de la suppression de la progression",
|
||||||
"BOOK_PAGES_FETCH_ERROR": "Erreur lors de la récupération des pages du livre",
|
"BOOK_PAGES_FETCH_ERROR": "Erreur lors de la récupération des pages du livre",
|
||||||
"BOOK_DOWNLOAD_CANCELLED": "Téléchargement du livre annulé",
|
"BOOK_DOWNLOAD_CANCELLED": "Téléchargement du livre annulé",
|
||||||
|
|
||||||
"FAVORITE_ADD_ERROR": "Erreur lors de l'ajout aux favoris",
|
"FAVORITE_ADD_ERROR": "Erreur lors de l'ajout aux favoris",
|
||||||
"FAVORITE_DELETE_ERROR": "Erreur lors de la suppression des favoris",
|
"FAVORITE_DELETE_ERROR": "Erreur lors de la suppression des favoris",
|
||||||
"FAVORITE_FETCH_ERROR": "Erreur lors de la récupération des favoris",
|
"FAVORITE_FETCH_ERROR": "Erreur lors de la récupération des favoris",
|
||||||
@@ -404,26 +399,19 @@
|
|||||||
"FAVORITE_NETWORK_ERROR": "Erreur réseau lors de l'accès aux favoris",
|
"FAVORITE_NETWORK_ERROR": "Erreur réseau lors de l'accès aux favoris",
|
||||||
"FAVORITE_SERVER_ERROR": "Erreur serveur lors de l'accès aux favoris",
|
"FAVORITE_SERVER_ERROR": "Erreur serveur lors de l'accès aux favoris",
|
||||||
"FAVORITE_STATUS_CHECK_ERROR": "Erreur lors de la vérification du statut des favoris",
|
"FAVORITE_STATUS_CHECK_ERROR": "Erreur lors de la vérification du statut des favoris",
|
||||||
|
|
||||||
"PREFERENCES_FETCH_ERROR": "Erreur lors de la récupération des préférences",
|
"PREFERENCES_FETCH_ERROR": "Erreur lors de la récupération des préférences",
|
||||||
"PREFERENCES_UPDATE_ERROR": "Erreur lors de la mise à jour des préférences",
|
"PREFERENCES_UPDATE_ERROR": "Erreur lors de la mise à jour des préférences",
|
||||||
"PREFERENCES_CONTEXT_ERROR": "Erreur de contexte des préférences",
|
"PREFERENCES_CONTEXT_ERROR": "Erreur de contexte des préférences",
|
||||||
|
|
||||||
"UI_TABS_TRIGGER_ERROR": "Erreur lors du déclenchement des onglets",
|
"UI_TABS_TRIGGER_ERROR": "Erreur lors du déclenchement des onglets",
|
||||||
"UI_TABS_CONTENT_ERROR": "Erreur lors du chargement du contenu des onglets",
|
"UI_TABS_CONTENT_ERROR": "Erreur lors du chargement du contenu des onglets",
|
||||||
|
|
||||||
"IMAGE_FETCH_ERROR": "Erreur lors de la récupération de l'image",
|
"IMAGE_FETCH_ERROR": "Erreur lors de la récupération de l'image",
|
||||||
|
|
||||||
"HOME_FETCH_ERROR": "Erreur lors de la récupération de l'accueil",
|
"HOME_FETCH_ERROR": "Erreur lors de la récupération de l'accueil",
|
||||||
|
|
||||||
"MIDDLEWARE_UNAUTHORIZED": "Non autorisé",
|
"MIDDLEWARE_UNAUTHORIZED": "Non autorisé",
|
||||||
"MIDDLEWARE_INVALID_TOKEN": "Jeton d'authentification invalide",
|
"MIDDLEWARE_INVALID_TOKEN": "Jeton d'authentification invalide",
|
||||||
"MIDDLEWARE_INVALID_SESSION": "Session invalide",
|
"MIDDLEWARE_INVALID_SESSION": "Session invalide",
|
||||||
|
|
||||||
"CLIENT_FETCH_ERROR": "Erreur lors de la récupération des données",
|
"CLIENT_FETCH_ERROR": "Erreur lors de la récupération des données",
|
||||||
"CLIENT_NETWORK_ERROR": "Erreur réseau",
|
"CLIENT_NETWORK_ERROR": "Erreur réseau",
|
||||||
"CLIENT_REQUEST_FAILED": "La requête a échoué",
|
"CLIENT_REQUEST_FAILED": "La requête a échoué",
|
||||||
|
|
||||||
"GENERIC_ERROR": "Une erreur est survenue"
|
"GENERIC_ERROR": "Une erreur est survenue"
|
||||||
},
|
},
|
||||||
"reader": {
|
"reader": {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type { LibraryResponse } from "@/types/library";
|
|||||||
import type { AuthConfig } from "@/types/auth";
|
import type { AuthConfig } from "@/types/auth";
|
||||||
import logger from "@/lib/logger";
|
import logger from "@/lib/logger";
|
||||||
import { HOME_CACHE_TAG, LIBRARY_SERIES_CACHE_TAG, SERIES_BOOKS_CACHE_TAG } from "@/constants/cacheConstants";
|
import { HOME_CACHE_TAG, LIBRARY_SERIES_CACHE_TAG, SERIES_BOOKS_CACHE_TAG } from "@/constants/cacheConstants";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
|
||||||
type KomgaCondition = Record<string, unknown>;
|
type KomgaCondition = Record<string, unknown>;
|
||||||
|
|
||||||
@@ -64,7 +65,6 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
|
|
||||||
const isDebug = process.env.KOMGA_DEBUG === "true";
|
const isDebug = process.env.KOMGA_DEBUG === "true";
|
||||||
const isCacheDebug = process.env.CACHE_DEBUG === "true";
|
const isCacheDebug = process.env.CACHE_DEBUG === "true";
|
||||||
const startTime = isDebug ? Date.now() : 0;
|
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -72,8 +72,14 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
"🔵 Komga Request"
|
"🔵 Komga Request"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isCacheDebug && options.revalidate) {
|
if (isCacheDebug) {
|
||||||
logger.info({ url, cache: "enabled", ttl: options.revalidate }, "💾 Cache enabled");
|
if (options.tags) {
|
||||||
|
logger.info({ url, cache: "tags", tags: options.tags }, "💾 Cache tags");
|
||||||
|
} else if (options.revalidate !== undefined) {
|
||||||
|
logger.info({ url, cache: "revalidate", ttl: options.revalidate }, "💾 Cache revalidate");
|
||||||
|
} else {
|
||||||
|
logger.info({ url, cache: "none" }, "💾 Cache none");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextOptions = options.tags
|
const nextOptions = options.tags
|
||||||
@@ -88,6 +94,19 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
next: nextOptions,
|
next: nextOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Next.js does not cache POST fetch requests — use unstable_cache to cache results instead
|
||||||
|
if (options.method === "POST" && nextOptions) {
|
||||||
|
const cacheKey = ["komga", this.config.authHeader, url, String(options.body ?? "")];
|
||||||
|
return unstable_cache(() => this.executeRequest<T>(url, fetchOptions), cacheKey, nextOptions)();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.executeRequest<T>(url, fetchOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeRequest<T>(url: string, fetchOptions: RequestInit): Promise<T> {
|
||||||
|
const isDebug = process.env.KOMGA_DEBUG === "true";
|
||||||
|
const startTime = isDebug ? Date.now() : 0;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
||||||
|
|
||||||
@@ -124,10 +143,6 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
"🟢 Komga Response"
|
"🟢 Komga Response"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isCacheDebug && options.revalidate) {
|
|
||||||
const cacheStatus = response.headers.get("x-nextjs-cache") ?? "UNKNOWN";
|
|
||||||
logger.info({ url, cacheStatus }, `💾 Cache ${cacheStatus}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
@@ -163,25 +178,7 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
const raw = await this.fetch<KomgaLibrary[]>("libraries", undefined, {
|
const raw = await this.fetch<KomgaLibrary[]>("libraries", undefined, {
|
||||||
revalidate: CACHE_TTL_LONG,
|
revalidate: CACHE_TTL_LONG,
|
||||||
});
|
});
|
||||||
// Enrich with book counts
|
return raw.map(KomgaAdapter.toNormalizedLibrary);
|
||||||
const enriched = await Promise.all(
|
|
||||||
raw.map(async (lib) => {
|
|
||||||
try {
|
|
||||||
const resp = await this.fetch<{ totalElements: number }>(
|
|
||||||
"books",
|
|
||||||
{
|
|
||||||
library_id: lib.id,
|
|
||||||
size: "0",
|
|
||||||
},
|
|
||||||
{ revalidate: CACHE_TTL_LONG }
|
|
||||||
);
|
|
||||||
return { ...lib, booksCount: resp.totalElements, booksReadCount: 0 } as KomgaLibrary;
|
|
||||||
} catch {
|
|
||||||
return { ...lib, booksCount: 0, booksReadCount: 0 } as KomgaLibrary;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return enriched.map(KomgaAdapter.toNormalizedLibrary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSeries(libraryId: string, cursor?: string, limit = 20, unreadOnly = false, search?: string): Promise<NormalizedSeriesPage> {
|
async getSeries(libraryId: string, cursor?: string, limit = 20, unreadOnly = false, search?: string): Promise<NormalizedSeriesPage> {
|
||||||
@@ -356,30 +353,8 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getLibraryById(libraryId: string): Promise<NormalizedLibrary | null> {
|
async getLibraryById(libraryId: string): Promise<NormalizedLibrary | null> {
|
||||||
try {
|
const libraries = await this.getLibraries();
|
||||||
const lib = await this.fetch<KomgaLibrary>(`libraries/${libraryId}`, undefined, {
|
return libraries.find((lib) => lib.id === libraryId) ?? null;
|
||||||
revalidate: CACHE_TTL_LONG,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const resp = await this.fetch<{ totalElements: number }>(
|
|
||||||
"books",
|
|
||||||
{
|
|
||||||
library_id: lib.id,
|
|
||||||
size: "0",
|
|
||||||
},
|
|
||||||
{ revalidate: CACHE_TTL_LONG }
|
|
||||||
);
|
|
||||||
return KomgaAdapter.toNormalizedLibrary({
|
|
||||||
...lib,
|
|
||||||
booksCount: resp.totalElements,
|
|
||||||
booksReadCount: 0,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return KomgaAdapter.toNormalizedLibrary({ ...lib, booksCount: 0, booksReadCount: 0 });
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNextBook(bookId: string): Promise<NormalizedBook | null> {
|
async getNextBook(bookId: string): Promise<NormalizedBook | null> {
|
||||||
@@ -398,7 +373,14 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getHomeData(): Promise<HomeData> {
|
async getHomeData(): Promise<HomeData> {
|
||||||
const homeOpts = { revalidate: CACHE_TTL_MED, tags: [HOME_CACHE_TAG] };
|
return unstable_cache(
|
||||||
|
() => this.fetchHomeData(),
|
||||||
|
["komga-home", this.config.authHeader],
|
||||||
|
{ revalidate: CACHE_TTL_MED, tags: [HOME_CACHE_TAG] }
|
||||||
|
)();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchHomeData(): Promise<HomeData> {
|
||||||
const [ongoing, ongoingBooks, recentlyRead, onDeck, latestSeries] = await Promise.all([
|
const [ongoing, ongoingBooks, recentlyRead, onDeck, latestSeries] = await Promise.all([
|
||||||
this.fetch<LibraryResponse<KomgaSeries>>(
|
this.fetch<LibraryResponse<KomgaSeries>>(
|
||||||
"series/list",
|
"series/list",
|
||||||
@@ -408,7 +390,6 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
condition: { readStatus: { operator: "is", value: "IN_PROGRESS" } },
|
condition: { readStatus: { operator: "is", value: "IN_PROGRESS" } },
|
||||||
}),
|
}),
|
||||||
...homeOpts,
|
|
||||||
}
|
}
|
||||||
).catch(() => ({ content: [] as KomgaSeries[] })),
|
).catch(() => ({ content: [] as KomgaSeries[] })),
|
||||||
this.fetch<LibraryResponse<KomgaBook>>(
|
this.fetch<LibraryResponse<KomgaBook>>(
|
||||||
@@ -419,23 +400,19 @@ export class KomgaProvider implements IMediaProvider {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
condition: { readStatus: { operator: "is", value: "IN_PROGRESS" } },
|
condition: { readStatus: { operator: "is", value: "IN_PROGRESS" } },
|
||||||
}),
|
}),
|
||||||
...homeOpts,
|
|
||||||
}
|
}
|
||||||
).catch(() => ({ content: [] as KomgaBook[] })),
|
).catch(() => ({ content: [] as KomgaBook[] })),
|
||||||
this.fetch<LibraryResponse<KomgaBook>>(
|
this.fetch<LibraryResponse<KomgaBook>>(
|
||||||
"books/latest",
|
"books/latest",
|
||||||
{ page: "0", size: "10", media_status: "READY" },
|
{ page: "0", size: "10", media_status: "READY" }
|
||||||
{ ...homeOpts }
|
|
||||||
).catch(() => ({ content: [] as KomgaBook[] })),
|
).catch(() => ({ content: [] as KomgaBook[] })),
|
||||||
this.fetch<LibraryResponse<KomgaBook>>(
|
this.fetch<LibraryResponse<KomgaBook>>(
|
||||||
"books/ondeck",
|
"books/ondeck",
|
||||||
{ page: "0", size: "10", media_status: "READY" },
|
{ page: "0", size: "10", media_status: "READY" }
|
||||||
{ ...homeOpts }
|
|
||||||
).catch(() => ({ content: [] as KomgaBook[] })),
|
).catch(() => ({ content: [] as KomgaBook[] })),
|
||||||
this.fetch<LibraryResponse<KomgaSeries>>(
|
this.fetch<LibraryResponse<KomgaSeries>>(
|
||||||
"series/latest",
|
"series/latest",
|
||||||
{ page: "0", size: "10", media_status: "READY" },
|
{ page: "0", size: "10", media_status: "READY" }
|
||||||
{ ...homeOpts }
|
|
||||||
).catch(() => ({ content: [] as KomgaSeries[] })),
|
).catch(() => ({ content: [] as KomgaSeries[] })),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -59,8 +59,14 @@ export class StripstreamClient {
|
|||||||
"🔵 Stripstream Request"
|
"🔵 Stripstream Request"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isCacheDebug && options.revalidate) {
|
if (isCacheDebug) {
|
||||||
logger.info({ url, cache: "enabled", ttl: options.revalidate }, "💾 Cache enabled");
|
if (options.tags) {
|
||||||
|
logger.info({ url, cache: "tags", tags: options.tags }, "💾 Cache tags");
|
||||||
|
} else if (options.revalidate !== undefined) {
|
||||||
|
logger.info({ url, cache: "revalidate", ttl: options.revalidate }, "💾 Cache revalidate");
|
||||||
|
} else {
|
||||||
|
logger.info({ url, cache: "none" }, "💾 Cache none");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextOptions = options.tags
|
const nextOptions = options.tags
|
||||||
@@ -106,10 +112,6 @@ export class StripstreamClient {
|
|||||||
"🟢 Stripstream Response"
|
"🟢 Stripstream Response"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isCacheDebug && options.revalidate) {
|
|
||||||
const cacheStatus = response.headers.get("x-nextjs-cache") ?? "UNKNOWN";
|
|
||||||
logger.info({ url, cacheStatus }, `💾 Cache ${cacheStatus}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
|
|||||||
Reference in New Issue
Block a user