diff --git a/src/components/series/SeriesHeader.tsx b/src/components/series/SeriesHeader.tsx
index 7fe481a..8aa3636 100644
--- a/src/components/series/SeriesHeader.tsx
+++ b/src/components/series/SeriesHeader.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Book, BookOpen, BookMarked, Star, StarOff } from "lucide-react";
+import { Book, BookOpen, BookMarked, Star, StarOff, User } from "lucide-react";
import type { NormalizedSeries } from "@/lib/providers/types";
import { useState, useEffect } from "react";
import { useToast } from "@/components/ui/use-toast";
@@ -100,6 +100,9 @@ export const SeriesHeader = ({ series, refreshSeries, initialIsFavorite }: Serie
};
const statusInfo = getReadingStatusInfo();
+ const authorsText = series.authors?.length
+ ? series.authors.map((a) => a.name).join(", ")
+ : null;
return (
@@ -128,6 +131,12 @@ export const SeriesHeader = ({ series, refreshSeries, initialIsFavorite }: Serie
{/* Informations */}
{series.name}
+ {authorsText && (
+
+
+ {authorsText}
+
+ )}
{series.summary && (
{series.summary}
@@ -158,6 +167,7 @@ export const SeriesHeader = ({ series, refreshSeries, initialIsFavorite }: Serie
+
);
};
diff --git a/src/lib/providers/komga/komga.adapter.ts b/src/lib/providers/komga/komga.adapter.ts
index 71d1075..46d55c5 100644
--- a/src/lib/providers/komga/komga.adapter.ts
+++ b/src/lib/providers/komga/komga.adapter.ts
@@ -37,6 +37,7 @@ export class KomgaAdapter {
bookCount: series.booksCount,
booksReadCount: series.booksReadCount,
thumbnailUrl: `/api/komga/images/series/${series.id}/thumbnail`,
+ libraryId: series.libraryId,
summary: series.metadata?.summary ?? null,
authors: series.booksMetadata?.authors ?? [],
genres: series.metadata?.genres ?? [],
diff --git a/src/lib/providers/stripstream/stripstream.adapter.ts b/src/lib/providers/stripstream/stripstream.adapter.ts
index 875e718..679c2a1 100644
--- a/src/lib/providers/stripstream/stripstream.adapter.ts
+++ b/src/lib/providers/stripstream/stripstream.adapter.ts
@@ -73,6 +73,7 @@ export class StripstreamAdapter {
bookCount: series.book_count,
booksReadCount: series.books_read_count,
thumbnailUrl: `/api/stripstream/images/books/${series.first_book_id}/thumbnail`,
+ libraryId: series.library_id,
summary: null,
authors: [],
genres: [],
diff --git a/src/lib/providers/stripstream/stripstream.provider.ts b/src/lib/providers/stripstream/stripstream.provider.ts
index 73e6e9f..714da9b 100644
--- a/src/lib/providers/stripstream/stripstream.provider.ts
+++ b/src/lib/providers/stripstream/stripstream.provider.ts
@@ -12,8 +12,6 @@ import type {
import type { HomeData } from "@/types/home";
import { StripstreamClient } from "./stripstream.client";
import { StripstreamAdapter } from "./stripstream.adapter";
-import { ERROR_CODES } from "@/constants/errorCodes";
-import { AppError } from "@/utils/errors";
import type {
StripstreamLibraryResponse,
StripstreamBooksPage,
@@ -23,6 +21,7 @@ import type {
StripstreamBookDetails,
StripstreamReadingProgressResponse,
StripstreamSearchResponse,
+ StripstreamSeriesMetadata,
} from "@/types/stripstream";
import { HOME_CACHE_TAG, LIBRARY_SERIES_CACHE_TAG, SERIES_BOOKS_CACHE_TAG } from "@/constants/cacheConstants";
@@ -91,18 +90,20 @@ export class StripstreamProvider implements IMediaProvider {
const seriesInfo = await this.findSeriesByName(book.series, book.library_id);
if (seriesInfo) return seriesInfo;
- return {
+ const fallback: NormalizedSeries = {
id: seriesId,
name: book.series,
bookCount: 0,
booksReadCount: 0,
thumbnailUrl: `/api/stripstream/images/books/${seriesId}/thumbnail`,
+ libraryId: book.library_id,
summary: null,
authors: [],
genres: [],
tags: [],
createdAt: null,
};
+ return this.enrichSeriesWithMetadata(fallback, book.library_id, book.series);
} catch {
// Fall back: treat seriesId as a series name, find its first book
try {
@@ -117,18 +118,20 @@ export class StripstreamProvider implements IMediaProvider {
const seriesInfo = await this.findSeriesByName(seriesId, firstBook.library_id);
if (seriesInfo) return seriesInfo;
- return {
+ const fallback: NormalizedSeries = {
id: firstBook.id,
name: seriesId,
bookCount: 0,
booksReadCount: 0,
thumbnailUrl: `/api/stripstream/images/books/${firstBook.id}/thumbnail`,
+ libraryId: firstBook.library_id,
summary: null,
authors: [],
genres: [],
tags: [],
createdAt: null,
};
+ return this.enrichSeriesWithMetadata(fallback, firstBook.library_id, seriesId);
} catch {
return null;
}
@@ -143,13 +146,49 @@ export class StripstreamProvider implements IMediaProvider {
{ revalidate: CACHE_TTL_MED }
);
const match = seriesPage.items.find((s) => s.name === seriesName);
- if (match) return StripstreamAdapter.toNormalizedSeries(match);
+ if (match) {
+ const normalized = StripstreamAdapter.toNormalizedSeries(match);
+ return this.enrichSeriesWithMetadata(normalized, libraryId, seriesName);
+ }
} catch {
// ignore
}
return null;
}
+ private async enrichSeriesWithMetadata(
+ series: NormalizedSeries,
+ libraryId: string,
+ seriesName: string
+ ): Promise {
+ try {
+ const metadata = await this.client.fetch(
+ `libraries/${libraryId}/series/${encodeURIComponent(seriesName)}/metadata`,
+ undefined,
+ { revalidate: CACHE_TTL_MED }
+ );
+ return {
+ ...series,
+ summary: metadata.description ?? null,
+ authors: metadata.authors.map((name) => ({ name, role: "writer" })),
+ };
+ } catch (error) {
+ return series;
+ }
+ }
+
+ private async resolveSeriesInfo(seriesId: string): Promise<{ libraryId: string; seriesName: string } | null> {
+ try {
+ const book = await this.client.fetch(`books/${seriesId}`, undefined, {
+ revalidate: CACHE_TTL_MED,
+ });
+ if (!book.series) return null;
+ return { libraryId: book.library_id, seriesName: book.series };
+ } catch {
+ return null;
+ }
+ }
+
async getBooks(filter: BookListFilter): Promise {
const limit = filter.limit ?? 24;
const params: Record = { limit: String(limit) };
diff --git a/src/lib/providers/types.ts b/src/lib/providers/types.ts
index ba5aaee..d581315 100644
--- a/src/lib/providers/types.ts
+++ b/src/lib/providers/types.ts
@@ -18,6 +18,7 @@ export interface NormalizedSeries {
bookCount: number;
booksReadCount: number;
thumbnailUrl: string;
+ libraryId?: string;
// Optional metadata (Komga-rich, Stripstream-sparse)
summary?: string | null;
authors?: Array<{ name: string; role: string }>;
diff --git a/src/types/stripstream.ts b/src/types/stripstream.ts
index 5e7a379..03437c5 100644
--- a/src/types/stripstream.ts
+++ b/src/types/stripstream.ts
@@ -48,6 +48,7 @@ export interface StripstreamSeriesItem {
book_count: number;
books_read_count: number;
first_book_id: string;
+ library_id: string;
}
export interface StripstreamSeriesPage {
@@ -80,6 +81,15 @@ export interface StripstreamUpdateReadingProgressRequest {
current_page?: number | null;
}
+export interface StripstreamSeriesMetadata {
+ authors: string[];
+ publishers: string[];
+ description?: string | null;
+ start_year?: number | null;
+ book_author?: string | null;
+ book_language?: string | null;
+}
+
export interface StripstreamSearchResponse {
hits: StripstreamSearchHit[];
series_hits: StripstreamSeriesHit[];