feat: add multi-provider support (Komga + Stripstream Librarian)
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled

- Introduce provider abstraction layer (IMediaProvider, KomgaProvider, StripstreamProvider)
- Add Stripstream Librarian as second media provider with full feature parity
- Migrate all pages and components from direct Komga services to provider factory
- Remove dead service code (BaseApiService, HomeService, LibraryService, SearchService, TestService)
- Fix library/series page-based pagination for both providers (Komga 0-indexed, Stripstream 1-indexed)
- Fix unread filter and search on library page for both providers
- Fix read progress display for Stripstream (reading_status mapping)
- Fix series read status (books_read_count) for Stripstream
- Add global search with series results for Stripstream (series_hits from Meilisearch)
- Fix thumbnail proxy to return 404 gracefully instead of JSON on upstream error
- Replace duration-based cache debug detection with x-nextjs-cache header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 11:48:17 +01:00
parent a1a95775db
commit 7d0f1c4457
77 changed files with 2695 additions and 1705 deletions

View File

@@ -10,7 +10,7 @@ import { AuthProvider } from "@/components/providers/AuthProvider";
import { cookies, headers } from "next/headers";
import { defaultPreferences } from "@/types/preferences";
import type { UserPreferences } from "@/types/preferences";
import type { KomgaLibrary, KomgaSeries } from "@/types/komga";
import type { NormalizedLibrary, NormalizedSeries } from "@/lib/providers/types";
import logger from "@/lib/logger";
const inter = Inter({
@@ -77,8 +77,8 @@ export default async function RootLayout({ children }: { children: React.ReactNo
let preferences: UserPreferences = defaultPreferences;
let userIsAdmin = false;
let libraries: KomgaLibrary[] = [];
let favorites: KomgaSeries[] = [];
let libraries: NormalizedLibrary[] = [];
let favorites: NormalizedSeries[] = [];
try {
const currentUser = await import("@/lib/auth-utils").then((m) => m.getCurrentUser());
@@ -86,7 +86,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo
if (currentUser) {
const [preferencesData, librariesData, favoritesData] = await Promise.allSettled([
PreferencesService.getPreferences(),
import("@/lib/services/library.service").then((m) => m.LibraryService.getLibraries()),
import("@/lib/providers/provider.factory")
.then((m) => m.getProvider())
.then((provider) => provider?.getLibraries() ?? []),
import("@/lib/services/favorites.service").then((m) =>
m.FavoritesService.getFavorites({ requestPath, requestPathname })
),