refactor: replace book details GET route with server action
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m16s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m16s
This commit is contained in:
@@ -24,7 +24,6 @@ Routes GET actuellement présentes :
|
|||||||
|
|
||||||
| Route | Utilisation actuelle | Pourquoi garder maintenant | Piste de simplification |
|
| Route | Utilisation actuelle | Pourquoi garder maintenant | Piste de simplification |
|
||||||
|-------|----------------------|----------------------------|-------------------------|
|
|-------|----------------------|----------------------------|-------------------------|
|
||||||
| `GET /api/komga/books/[bookId]` | fallback dans `ClientBookPage.tsx`, usage `DownloadManager.tsx` | fallback utile hors flux page SSR | Limiter au fallback strict, éviter le double-fetch |
|
|
||||||
|
|
||||||
### B2. Migrees en Lot 2 (pagination server-first)
|
### B2. Migrees en Lot 2 (pagination server-first)
|
||||||
|
|
||||||
@@ -36,6 +35,7 @@ Routes GET actuellement présentes :
|
|||||||
| `GET /api/komga/home` | `src/app/page.tsx` consomme déjà `HomeService` côté server | Données agrégées directement via service server | ✅ Supprimée |
|
| `GET /api/komga/home` | `src/app/page.tsx` consomme déjà `HomeService` côté server | Données agrégées directement via service server | ✅ Supprimée |
|
||||||
| `GET /api/user/profile` | aucun consommateur client trouvé, page compte déjà server-first | Profil/statistiques via `UserService` en Server Component | ✅ Supprimée |
|
| `GET /api/user/profile` | aucun consommateur client trouvé, page compte déjà server-first | Profil/statistiques via `UserService` en Server Component | ✅ Supprimée |
|
||||||
| `GET /api/komga/series/[seriesId]` | plus de consommateur `fetch('/api/...')` (chargement via `SeriesService`) | Détail série chargé en Server Component | ✅ Supprimée |
|
| `GET /api/komga/series/[seriesId]` | plus de consommateur `fetch('/api/...')` (chargement via `SeriesService`) | Détail série chargé en Server Component | ✅ Supprimée |
|
||||||
|
| `GET /api/komga/books/[bookId]` | fallback client (`ClientBookPage`) et DownloadManager migrés vers server action | Données livre/pages/nextBook via `BookService` et action server | ✅ Supprimée |
|
||||||
|
|
||||||
### C. A conserver (API de transport / framework)
|
### C. A conserver (API de transport / framework)
|
||||||
|
|
||||||
|
|||||||
36
src/app/actions/books.ts
Normal file
36
src/app/actions/books.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { BookService } from "@/lib/services/book.service";
|
||||||
|
import { AppError } from "@/utils/errors";
|
||||||
|
import type { KomgaBook } from "@/types/komga";
|
||||||
|
|
||||||
|
interface BookDataResult {
|
||||||
|
success: boolean;
|
||||||
|
data?: {
|
||||||
|
book: KomgaBook;
|
||||||
|
pages: number[];
|
||||||
|
nextBook: KomgaBook | null;
|
||||||
|
};
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBookData(bookId: string): Promise<BookDataResult> {
|
||||||
|
try {
|
||||||
|
const data = await BookService.getBook(bookId);
|
||||||
|
const nextBook = await BookService.getNextBook(bookId, data.book.seriesId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
nextBook,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AppError) {
|
||||||
|
return { success: false, message: error.code };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, message: "BOOK_DATA_FETCH_ERROR" };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { NextResponse } from "next/server";
|
|
||||||
import { BookService } from "@/lib/services/book.service";
|
|
||||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
|
||||||
import { getErrorMessage } from "@/utils/errors";
|
|
||||||
import { AppError } from "@/utils/errors";
|
|
||||||
import type { KomgaBookWithPages } from "@/types/komga";
|
|
||||||
import type { NextRequest } from "next/server";
|
|
||||||
import logger from "@/lib/logger";
|
|
||||||
|
|
||||||
type ErrorWithStatusParams = AppError & { params?: { status?: number } };
|
|
||||||
|
|
||||||
// Cache handled in service via fetchFromApi options
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
request: NextRequest,
|
|
||||||
{ params }: { params: Promise<{ bookId: string }> }
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const bookId: string = (await params).bookId;
|
|
||||||
|
|
||||||
const data: KomgaBookWithPages = await BookService.getBook(bookId);
|
|
||||||
const nextBook = await BookService.getNextBook(bookId, data.book.seriesId);
|
|
||||||
|
|
||||||
return NextResponse.json({ ...data, nextBook });
|
|
||||||
} catch (error) {
|
|
||||||
logger.error({ err: error }, "API Books - Erreur:");
|
|
||||||
if (error instanceof AppError) {
|
|
||||||
const isNotFound =
|
|
||||||
error.code === ERROR_CODES.BOOK.NOT_FOUND ||
|
|
||||||
(error.code === ERROR_CODES.KOMGA.HTTP_ERROR &&
|
|
||||||
(error as ErrorWithStatusParams).params?.status === 404);
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: {
|
|
||||||
code: error.code,
|
|
||||||
name: "Book fetch error",
|
|
||||||
message: getErrorMessage(error.code),
|
|
||||||
} as AppError,
|
|
||||||
},
|
|
||||||
{ status: isNotFound ? 404 : 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: {
|
|
||||||
code: ERROR_CODES.BOOK.NOT_FOUND,
|
|
||||||
name: "Book fetch error",
|
|
||||||
message: getErrorMessage(ERROR_CODES.BOOK.NOT_FOUND),
|
|
||||||
} as AppError,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,7 @@ import { BookOfflineButton } from "@/components/ui/book-offline-button";
|
|||||||
import { useTranslate } from "@/hooks/useTranslate";
|
import { useTranslate } from "@/hooks/useTranslate";
|
||||||
import logger from "@/lib/logger";
|
import logger from "@/lib/logger";
|
||||||
import { Container } from "@/components/ui/container";
|
import { Container } from "@/components/ui/container";
|
||||||
|
import { getBookData } from "@/app/actions/books";
|
||||||
|
|
||||||
type BookStatus = "idle" | "downloading" | "available" | "error";
|
type BookStatus = "idle" | "downloading" | "available" | "error";
|
||||||
|
|
||||||
@@ -45,17 +46,18 @@ export function DownloadManager() {
|
|||||||
const key = localStorage.key(i);
|
const key = localStorage.key(i);
|
||||||
if (key?.startsWith("book-status-")) {
|
if (key?.startsWith("book-status-")) {
|
||||||
const bookId = key.replace("book-status-", "");
|
const bookId = key.replace("book-status-", "");
|
||||||
const status = JSON.parse(localStorage.getItem(key) || "");
|
const status = JSON.parse(localStorage.getItem(key) || "");
|
||||||
if (status.status !== "idle") {
|
if (status.status !== "idle") {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/komga/books/${bookId}`);
|
const result = await getBookData(bookId);
|
||||||
if (!response.ok) throw new Error("Livre non trouvé");
|
if (!result.success || !result.data) {
|
||||||
const bookData = await response.json();
|
throw new Error("Livre non trouvé");
|
||||||
books.push({
|
}
|
||||||
book: bookData.book,
|
books.push({
|
||||||
status,
|
book: result.data.book,
|
||||||
});
|
status,
|
||||||
} catch (error) {
|
});
|
||||||
|
} catch (error) {
|
||||||
logger.error({ err: error }, `Erreur lors de la récupération du livre ${bookId}:`);
|
logger.error({ err: error }, `Erreur lors de la récupération du livre ${bookId}:`);
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ErrorMessage } from "@/components/ui/ErrorMessage";
|
|||||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||||
import type { KomgaBook } from "@/types/komga";
|
import type { KomgaBook } from "@/types/komga";
|
||||||
import logger from "@/lib/logger";
|
import logger from "@/lib/logger";
|
||||||
|
import { getBookData } from "@/app/actions/books";
|
||||||
|
|
||||||
interface ClientBookPageProps {
|
interface ClientBookPageProps {
|
||||||
bookId: string;
|
bookId: string;
|
||||||
@@ -48,15 +49,12 @@ export function ClientBookPage({ bookId, initialData, initialError }: ClientBook
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const response = await fetch(`/api/komga/books/${bookId}`);
|
const result = await getBookData(bookId);
|
||||||
|
if (!result.success || !result.data) {
|
||||||
if (!response.ok) {
|
throw new Error(result.message || ERROR_CODES.BOOK.PAGES_FETCH_ERROR);
|
||||||
const errorData = await response.json();
|
|
||||||
throw new Error(errorData.error?.code || ERROR_CODES.BOOK.PAGES_FETCH_ERROR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookData = await response.json();
|
setData(result.data);
|
||||||
setData(bookData);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error({ err }, "Error fetching book");
|
logger.error({ err }, "Error fetching book");
|
||||||
setError(err instanceof Error ? err.message : ERROR_CODES.BOOK.PAGES_FETCH_ERROR);
|
setError(err instanceof Error ? err.message : ERROR_CODES.BOOK.PAGES_FETCH_ERROR);
|
||||||
|
|||||||
Reference in New Issue
Block a user