Compare commits
13 Commits
7523ec06e1
...
7f361ce0a2
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f361ce0a2 | |||
| eec51b7ef8 | |||
| b40f59bec6 | |||
| 7134c069d7 | |||
| b815202529 | |||
| 0548215096 | |||
| 6180f9abb1 | |||
| d56b0fd7ae | |||
| 7308c0aa63 | |||
| 7e3fb22d3a | |||
| 546f3769c2 | |||
| 03cb46f81b | |||
| ecce0a9738 |
63
docs/api-get-cleanup.md
Normal file
63
docs/api-get-cleanup.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Plan - Suppression des routes API GET restantes
|
||||
|
||||
## État actuel
|
||||
|
||||
Routes GET encore présentes mais peu/n'utilisées :
|
||||
|
||||
| Route | Utilisation actuelle | Action |
|
||||
|-------|---------------------|--------|
|
||||
| `GET /api/komga/config` | ❌ Non utilisée | 🔴 Supprimer |
|
||||
| `GET /api/komga/favorites` | Sidebar, SeriesHeader (client) | 🟡 Optimiser |
|
||||
| `GET /api/preferences` | PreferencesContext (client) | 🟡 Optimiser |
|
||||
| `GET /api/komga/books/[bookId]` | ClientBookPage, DownloadManager | 🟡 Supprimer (données déjà en props) |
|
||||
| `GET /api/user/profile` | ? | 🔍 Vérifier |
|
||||
|
||||
## Actions proposées
|
||||
|
||||
### 1. Supprimer `GET /api/komga/config`
|
||||
|
||||
La config est déjà appelée directement dans `settings/page.tsx` via `ConfigDBService.getConfig()`.
|
||||
|
||||
**Action** : Supprimer la route API.
|
||||
|
||||
---
|
||||
|
||||
### 2. Optimiser les préférences
|
||||
|
||||
Les préférences sont déjà passées depuis `layout.tsx` via `PreferencesService.getPreferences()`.
|
||||
Le `PreferencesContext` refetch en client - c'est redondant.
|
||||
|
||||
**Action** : Le contexte utilise déjà les `initialPreferences`. Le fetch client n'est nécessaire que si on n'a pas les données initiales.
|
||||
|
||||
---
|
||||
|
||||
### 3. Supprimer `GET /api/komga/books/[bookId]`
|
||||
|
||||
Regardons ce que fait `ClientBookPage` :
|
||||
|
||||
```tsx
|
||||
// Server Component (page.tsx) fetch les données
|
||||
const data = await BookService.getBook(bookId);
|
||||
|
||||
// Passe à ClientBookPage
|
||||
<ClientBookPage bookId={bookId} initialData={{ ...data, nextBook }} />
|
||||
|
||||
// ClientClientBookPage refetch en client si pas de initialData
|
||||
useEffect(() => {
|
||||
if (!initialData) fetchBookData(); // Only if SSR failed
|
||||
}, [bookId, initialData]);
|
||||
```
|
||||
|
||||
**Action** : Supprimer le fetch client - les données sont déjà en props.
|
||||
|
||||
---
|
||||
|
||||
### 4. Garder pour l'instant
|
||||
|
||||
Ces routes nécessitent plus de refactoring :
|
||||
|
||||
- `GET /api/komga/favorites` - Utilisé dans des composants clients (Sidebar)
|
||||
- `GET /api/admin/users` - AdminContent
|
||||
- `GET /api/admin/stats` - AdminContent
|
||||
|
||||
Ces cas pourraient être résolus en passant les données depuis des Server Components parents.
|
||||
149
docs/server-actions-plan.md
Normal file
149
docs/server-actions-plan.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Plan de conversion API Routes → Server Actions
|
||||
|
||||
## État des lieux
|
||||
|
||||
### ✅ Converti
|
||||
|
||||
| Ancienne Route | Server Action | Status |
|
||||
|----------------|---------------|--------|
|
||||
| `PATCH /api/komga/books/[bookId]/read-progress` | `updateReadProgress()` | ✅ Done |
|
||||
| `DELETE /api/komga/books/[bookId]/read-progress` | `deleteReadProgress()` | ✅ Done |
|
||||
| `POST /api/komga/favorites` | `addToFavorites()` | ✅ Done |
|
||||
| `DELETE /api/komga/favorites` | `removeFromFavorites()` | ✅ Done |
|
||||
| `PUT /api/preferences` | `updatePreferences()` | ✅ Done |
|
||||
| `POST /api/komga/libraries/[libraryId]/scan` | `scanLibrary()` | ✅ Done |
|
||||
| `POST /api/komga/config` | `saveKomgaConfig()` | ✅ Done |
|
||||
| `POST /api/komga/test` | `testKomgaConnection()` | ✅ Done |
|
||||
| `PUT /api/user/password` | `changePassword()` | ✅ Done |
|
||||
| `POST /api/auth/register` | `registerUser()` | ✅ Done |
|
||||
| `PATCH /api/admin/users/[userId]` | `updateUserRoles()` | ✅ Done |
|
||||
| `DELETE /api/admin/users/[userId]` | `deleteUser()` | ✅ Done |
|
||||
| `PUT /api/admin/users/[userId]/password` | `resetUserPassword()` | ✅ Done |
|
||||
|
||||
---
|
||||
|
||||
## À convertir (priorité haute)
|
||||
|
||||
### 1. Scan de bibliothèque
|
||||
|
||||
**Route actuelle** : `api/komga/libraries/[libraryId]/scan/route.ts`
|
||||
|
||||
```typescript
|
||||
// Action à créer : src/app/actions/library.ts
|
||||
export async function scanLibrary(libraryId: string)
|
||||
```
|
||||
|
||||
**Appelants à migrer** :
|
||||
- `components/library/ScanButton.tsx` (POST fetch)
|
||||
|
||||
---
|
||||
|
||||
## À convertir (priorité moyenne)
|
||||
|
||||
### 2. Configuration Komga
|
||||
|
||||
**Route actuelle** : `api/komga/config/route.ts`
|
||||
|
||||
```typescript
|
||||
// Action à créer : src/app/actions/config.ts
|
||||
export async function saveKomgaConfig(config: KomgaConfigData)
|
||||
export async function getKomgaConfig()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Mot de passe utilisateur
|
||||
|
||||
**Route actuelle** : `api/user/password/route.ts`
|
||||
|
||||
```typescript
|
||||
// Action à créer : src/app/actions/password.ts
|
||||
export async function changePassword(currentPassword: string, newPassword: string)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Inscription
|
||||
|
||||
**Route actuelle** : `api/auth/register/route.ts`
|
||||
|
||||
```typescript
|
||||
// Action à créer : src/app/actions/auth.ts
|
||||
export async function registerUser(email: string, password: string)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## À convertir (priorité basse - admin)
|
||||
|
||||
### 5. Gestion des utilisateurs (admin)
|
||||
|
||||
**Routes** :
|
||||
- `api/admin/users/[userId]/route.ts` (PATCH, DELETE)
|
||||
- `api/admin/users/[userId]/password/route.ts` (PUT)
|
||||
|
||||
```typescript
|
||||
// Actions à créer : src/app/actions/admin.ts
|
||||
export async function updateUserRoles(userId: string, roles: string[])
|
||||
export async function deleteUser(userId: string)
|
||||
export async function resetUserPassword(userId: string, newPassword: string)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## À garder en API Routes
|
||||
|
||||
Ces routes ne doivent PAS être converties :
|
||||
|
||||
| Route | Raison |
|
||||
|-------|--------|
|
||||
| `api/komga/home` | GET - called from Server Components |
|
||||
| `api/komga/books/[bookId]` | GET - fetch données livre |
|
||||
| `api/komga/series/*` | GET - fetch séries |
|
||||
| `api/komga/libraries/*` | GET - fetch bibliothèques |
|
||||
| `api/komga/random-book` | GET - fetch aléatoire |
|
||||
| `api/komga/images/*` | GET - servir images (streaming) |
|
||||
| `api/auth/[...nextauth]/*` | NextAuth handler externe |
|
||||
| `api/admin/users` | GET - fetch liste users |
|
||||
| `api/admin/stats` | GET - fetch stats |
|
||||
| `api/user/profile` | GET - fetch profile |
|
||||
|
||||
---
|
||||
|
||||
## Pattern à suivre
|
||||
|
||||
```typescript
|
||||
// src/app/actions/[feature].ts
|
||||
"use server";
|
||||
|
||||
import { revalidatePath, revalidateTag } from "next/cache";
|
||||
import { Service } from "@/lib/services/service";
|
||||
|
||||
export async function actionName(params): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await Service.doSomething(params);
|
||||
|
||||
// Invalider le cache si nécessaire
|
||||
revalidateTag("cache-tag", "min");
|
||||
revalidatePath("/");
|
||||
|
||||
return { success: true, message: "Succès" };
|
||||
} catch (error) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/components/feature/Component.tsx
|
||||
"use client";
|
||||
|
||||
import { actionName } from "@/app/actions/feature";
|
||||
|
||||
const handleAction = async () => {
|
||||
const result = await actionName(params);
|
||||
if (!result.success) {
|
||||
// handle error
|
||||
}
|
||||
};
|
||||
```
|
||||
72
src/app/actions/admin.ts
Normal file
72
src/app/actions/admin.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
"use server";
|
||||
|
||||
import { AdminService } from "@/lib/services/admin.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { AuthServerService } from "@/lib/services/auth-server.service";
|
||||
|
||||
/**
|
||||
* Met à jour les rôles d'un utilisateur
|
||||
*/
|
||||
export async function updateUserRoles(
|
||||
userId: string,
|
||||
roles: string[]
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
if (roles.length === 0) {
|
||||
return { success: false, message: "L'utilisateur doit avoir au moins un rôle" };
|
||||
}
|
||||
|
||||
await AdminService.updateUserRoles(userId, roles);
|
||||
|
||||
return { success: true, message: "Rôles mis à jour" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la mise à jour des rôles" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un utilisateur
|
||||
*/
|
||||
export async function deleteUser(
|
||||
userId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await AdminService.deleteUser(userId);
|
||||
return { success: true, message: "Utilisateur supprimé" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la suppression" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise le mot de passe d'un utilisateur
|
||||
*/
|
||||
export async function resetUserPassword(
|
||||
userId: string,
|
||||
newPassword: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
if (!AuthServerService.isPasswordStrong(newPassword)) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Le mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre",
|
||||
};
|
||||
}
|
||||
|
||||
await AdminService.resetUserPassword(userId, newPassword);
|
||||
|
||||
return { success: true, message: "Mot de passe réinitialisé" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la réinitialisation du mot de passe" };
|
||||
}
|
||||
}
|
||||
23
src/app/actions/auth.ts
Normal file
23
src/app/actions/auth.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
"use server";
|
||||
|
||||
import { AuthServerService } from "@/lib/services/auth-server.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
|
||||
/**
|
||||
* Inscrit un nouvel utilisateur
|
||||
*/
|
||||
export async function registerUser(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await AuthServerService.registerUser(email, password);
|
||||
return { success: true, message: "Inscription réussie" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de l'inscription" };
|
||||
}
|
||||
}
|
||||
74
src/app/actions/config.ts
Normal file
74
src/app/actions/config.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { ConfigDBService } from "@/lib/services/config-db.service";
|
||||
import { TestService } from "@/lib/services/test.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import type { KomgaConfig, KomgaConfigData, KomgaLibrary } from "@/types/komga";
|
||||
|
||||
interface SaveConfigInput {
|
||||
url: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
authHeader?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Teste la connexion à Komga
|
||||
*/
|
||||
export async function testKomgaConnection(
|
||||
serverUrl: string,
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const authHeader = Buffer.from(`${username}:${password}`).toString("base64");
|
||||
|
||||
const { libraries }: { libraries: KomgaLibrary[] } = await TestService.testConnection({
|
||||
serverUrl,
|
||||
authHeader,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Connexion réussie ! ${libraries.length} bibliothèque${libraries.length > 1 ? "s" : ""} trouvée${libraries.length > 1 ? "s" : ""}`,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la connexion" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sauvegarde la configuration Komga
|
||||
*/
|
||||
export async function saveKomgaConfig(
|
||||
config: SaveConfigInput
|
||||
): Promise<{ success: boolean; message: string; data?: KomgaConfig }> {
|
||||
try {
|
||||
const configData: KomgaConfigData = {
|
||||
url: config.url,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
authHeader: config.authHeader || "",
|
||||
};
|
||||
const mongoConfig = await ConfigDBService.saveConfig(configData);
|
||||
|
||||
// Invalider le cache
|
||||
revalidatePath("/settings");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Configuration sauvegardée",
|
||||
data: mongoConfig,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la sauvegarde" };
|
||||
}
|
||||
}
|
||||
39
src/app/actions/favorites.ts
Normal file
39
src/app/actions/favorites.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
"use server";
|
||||
|
||||
import { FavoriteService } from "@/lib/services/favorite.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
|
||||
/**
|
||||
* Ajoute une série aux favoris
|
||||
*/
|
||||
export async function addToFavorites(
|
||||
seriesId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await FavoriteService.addToFavorites(seriesId);
|
||||
return { success: true, message: "Série ajoutée aux favoris" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de l'ajout aux favoris" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retire une série des favoris
|
||||
*/
|
||||
export async function removeFromFavorites(
|
||||
seriesId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await FavoriteService.removeFromFavorites(seriesId);
|
||||
return { success: true, message: "Série retirée des favoris" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la suppression des favoris" };
|
||||
}
|
||||
}
|
||||
28
src/app/actions/library.ts
Normal file
28
src/app/actions/library.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { LibraryService } from "@/lib/services/library.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
|
||||
/**
|
||||
* Lance un scan de bibliothèque
|
||||
*/
|
||||
export async function scanLibrary(
|
||||
libraryId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await LibraryService.scanLibrary(libraryId, false);
|
||||
|
||||
// Invalider le cache de la bibliothèque
|
||||
revalidatePath(`/libraries/${libraryId}`);
|
||||
revalidatePath("/libraries");
|
||||
|
||||
return { success: true, message: "Scan lancé" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors du scan" };
|
||||
}
|
||||
}
|
||||
33
src/app/actions/password.ts
Normal file
33
src/app/actions/password.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
"use server";
|
||||
|
||||
import { UserService } from "@/lib/services/user.service";
|
||||
import { AuthServerService } from "@/lib/services/auth-server.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
|
||||
/**
|
||||
* Change le mot de passe de l'utilisateur
|
||||
*/
|
||||
export async function changePassword(
|
||||
currentPassword: string,
|
||||
newPassword: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
// Vérifier que le nouveau mot de passe est fort
|
||||
if (!AuthServerService.isPasswordStrong(newPassword)) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Le nouveau mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre",
|
||||
};
|
||||
}
|
||||
|
||||
await UserService.changePassword(currentPassword, newPassword);
|
||||
|
||||
return { success: true, message: "Mot de passe modifié avec succès" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors du changement de mot de passe" };
|
||||
}
|
||||
}
|
||||
30
src/app/actions/preferences.ts
Normal file
30
src/app/actions/preferences.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { PreferencesService } from "@/lib/services/preferences.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import type { UserPreferences } from "@/types/preferences";
|
||||
|
||||
/**
|
||||
* Met à jour les préférences utilisateur
|
||||
*/
|
||||
export async function updatePreferences(
|
||||
newPreferences: Partial<UserPreferences>
|
||||
): Promise<{ success: boolean; message: string; data?: UserPreferences }> {
|
||||
try {
|
||||
const updatedPreferences = await PreferencesService.updatePreferences(newPreferences);
|
||||
|
||||
// Invalider les pages qui utilisent les préférences
|
||||
revalidatePath("/");
|
||||
revalidatePath("/libraries");
|
||||
revalidatePath("/series");
|
||||
|
||||
return { success: true, message: "Préférences mises à jour", data: updatedPreferences };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la mise à jour des préférences" };
|
||||
}
|
||||
}
|
||||
53
src/app/actions/read-progress.ts
Normal file
53
src/app/actions/read-progress.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
"use server";
|
||||
|
||||
import { revalidateTag } from "next/cache";
|
||||
import { BookService } from "@/lib/services/book.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
|
||||
const HOME_CACHE_TAG = "home-data";
|
||||
|
||||
/**
|
||||
* Met à jour la progression de lecture d'un livre
|
||||
* Note: ne pas utiliser "use server" avec redirect - on gère manuellement
|
||||
*/
|
||||
export async function updateReadProgress(
|
||||
bookId: string,
|
||||
page: number,
|
||||
completed: boolean = false
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await BookService.updateReadProgress(bookId, page, completed);
|
||||
|
||||
// Invalider le cache de la home (sans refresh auto)
|
||||
revalidateTag(HOME_CACHE_TAG, "min");
|
||||
|
||||
return { success: true, message: "Progression mise à jour" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la mise à jour" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime la progression de lecture d'un livre
|
||||
*/
|
||||
export async function deleteReadProgress(
|
||||
bookId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await BookService.deleteReadProgress(bookId);
|
||||
|
||||
// Invalider le cache de la home (sans refresh auto)
|
||||
revalidateTag(HOME_CACHE_TAG, "min");
|
||||
|
||||
return { success: true, message: "Progression supprimée" };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
return { success: false, message: "Erreur lors de la suppression" };
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { AdminService } from "@/lib/services/admin.service";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { AuthServerService } from "@/lib/services/auth-server.service";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ userId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { userId } = await params;
|
||||
const body = await request.json();
|
||||
const { newPassword } = body;
|
||||
|
||||
if (!newPassword) {
|
||||
return NextResponse.json({ error: "Nouveau mot de passe manquant" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Vérifier que le mot de passe est fort
|
||||
if (!AuthServerService.isPasswordStrong(newPassword)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Le mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
await AdminService.resetUserPassword(userId, newPassword);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la réinitialisation du mot de passe:");
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, code: error.code },
|
||||
{
|
||||
status:
|
||||
error.code === "AUTH_FORBIDDEN"
|
||||
? 403
|
||||
: error.code === "AUTH_UNAUTHENTICATED"
|
||||
? 401
|
||||
: error.code === "AUTH_USER_NOT_FOUND"
|
||||
? 404
|
||||
: error.code === "ADMIN_CANNOT_RESET_OWN_PASSWORD"
|
||||
? 400
|
||||
: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la réinitialisation du mot de passe" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { AdminService } from "@/lib/services/admin.service";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ userId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { userId } = await params;
|
||||
const body = await request.json();
|
||||
const { roles } = body;
|
||||
|
||||
if (!roles || !Array.isArray(roles)) {
|
||||
return NextResponse.json({ error: "Rôles invalides" }, { status: 400 });
|
||||
}
|
||||
|
||||
await AdminService.updateUserRoles(userId, roles);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la mise à jour de l'utilisateur:");
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, code: error.code },
|
||||
{
|
||||
status:
|
||||
error.code === "AUTH_FORBIDDEN"
|
||||
? 403
|
||||
: error.code === "AUTH_UNAUTHENTICATED"
|
||||
? 401
|
||||
: error.code === "AUTH_USER_NOT_FOUND"
|
||||
? 404
|
||||
: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la mise à jour de l'utilisateur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ userId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { userId } = await params;
|
||||
await AdminService.deleteUser(userId);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la suppression de l'utilisateur:");
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, code: error.code },
|
||||
{
|
||||
status:
|
||||
error.code === "AUTH_FORBIDDEN"
|
||||
? 403
|
||||
: error.code === "AUTH_UNAUTHENTICATED"
|
||||
? 401
|
||||
: error.code === "AUTH_USER_NOT_FOUND"
|
||||
? 404
|
||||
: error.code === "ADMIN_CANNOT_DELETE_SELF"
|
||||
? 400
|
||||
: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la suppression de l'utilisateur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { AuthServerService } from "@/lib/services/auth-server.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { ERROR_MESSAGES } from "@/constants/errorMessages";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { email, password } = await request.json();
|
||||
|
||||
if (!email || !password) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.AUTH.INVALID_USER_DATA,
|
||||
name: "Invalid user data",
|
||||
message: ERROR_MESSAGES[ERROR_CODES.AUTH.INVALID_USER_DATA],
|
||||
} as AppError,
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const userData = await AuthServerService.registerUser(email, password);
|
||||
|
||||
return NextResponse.json({ success: true, user: userData });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Registration error:");
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
} as AppError,
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.AUTH.REGISTRATION_FAILED,
|
||||
name: "Registration failed",
|
||||
message: ERROR_MESSAGES[ERROR_CODES.AUTH.REGISTRATION_FAILED],
|
||||
} as AppError,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
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 logger from "@/lib/logger";
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ bookId: string }> }
|
||||
) {
|
||||
try {
|
||||
const bookId: string = (await params).bookId;
|
||||
|
||||
// Handle empty or invalid body (can happen when request is aborted during navigation)
|
||||
let body: { page?: unknown; completed?: boolean };
|
||||
try {
|
||||
const text = await request.text();
|
||||
if (!text) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR,
|
||||
name: "Progress update error",
|
||||
message: "Empty request body",
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
body = JSON.parse(text);
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR,
|
||||
name: "Progress update error",
|
||||
message: "Invalid JSON body",
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const { page, completed } = body;
|
||||
|
||||
if (typeof page !== "number") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR,
|
||||
name: "Progress update error",
|
||||
message: getErrorMessage(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
await BookService.updateReadProgress(bookId, page, completed);
|
||||
|
||||
return NextResponse.json({ message: "📖 Progression mise à jour avec succès" });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la mise à jour de la progression:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: "Progress update error",
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR,
|
||||
name: "Progress update error",
|
||||
message: getErrorMessage(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ bookId: string }> }
|
||||
) {
|
||||
try {
|
||||
const bookId: string = (await params).bookId;
|
||||
|
||||
await BookService.deleteReadProgress(bookId);
|
||||
|
||||
return NextResponse.json({ message: "🗑️ Progression supprimée avec succès" });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la suppression de la progression:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: "Progress delete error",
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR,
|
||||
name: "Progress delete error",
|
||||
message: getErrorMessage(ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { ConfigDBService } from "@/lib/services/config-db.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import type { KomgaConfig, KomgaConfigData } from "@/types/komga";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import type { NextRequest } from "next/server";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const data: KomgaConfigData = await request.json();
|
||||
const mongoConfig: KomgaConfig = await ConfigDBService.saveConfig(data);
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: "⚙️ Configuration sauvegardée avec succès", mongoConfig },
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la sauvegarde de la configuration:");
|
||||
if (error instanceof Error && error.message === "Utilisateur non authentifié") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.MIDDLEWARE.UNAUTHORIZED,
|
||||
name: "Unauthorized",
|
||||
message: getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED),
|
||||
},
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.CONFIG.SAVE_ERROR,
|
||||
name: "Config save error",
|
||||
message: getErrorMessage(ERROR_CODES.CONFIG.SAVE_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const mongoConfig: KomgaConfig | null = await ConfigDBService.getConfig();
|
||||
|
||||
return NextResponse.json(mongoConfig, { status: 200 });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la récupération de la configuration:");
|
||||
if (error instanceof Error) {
|
||||
if (error.message === "Utilisateur non authentifié") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.MIDDLEWARE.UNAUTHORIZED,
|
||||
name: "Unauthorized",
|
||||
message: getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED),
|
||||
},
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
if (error.message === "Configuration non trouvée") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.KOMGA.MISSING_CONFIG,
|
||||
name: "Missing config",
|
||||
message: getErrorMessage(ERROR_CODES.KOMGA.MISSING_CONFIG),
|
||||
},
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.CONFIG.FETCH_ERROR,
|
||||
name: "Config fetch error",
|
||||
message: getErrorMessage(ERROR_CODES.CONFIG.FETCH_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import { SeriesService } from "@/lib/services/series.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import type { NextRequest } from "next/server";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
// GET reste utilisé par Sidebar et SeriesHeader pour récupérer la liste des favoris
|
||||
export async function GET() {
|
||||
try {
|
||||
const favoriteIds: string[] = await FavoriteService.getAllFavoriteIds();
|
||||
@@ -61,67 +61,3 @@ export async function GET() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { seriesId }: { seriesId: string } = await request.json();
|
||||
await FavoriteService.addToFavorites(seriesId);
|
||||
return NextResponse.json({ message: "⭐️ Série ajoutée aux favoris" });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de l'ajout du favori:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: "Favorite add error",
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.FAVORITE.ADD_ERROR,
|
||||
name: "Favorite add error",
|
||||
message: getErrorMessage(ERROR_CODES.FAVORITE.ADD_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
try {
|
||||
const { seriesId }: { seriesId: string } = await request.json();
|
||||
await FavoriteService.removeFromFavorites(seriesId);
|
||||
return NextResponse.json({ message: "💔 Série retirée des favoris" });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la suppression du favori:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: "Favorite delete error",
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.FAVORITE.DELETE_ERROR,
|
||||
name: "Favorite delete error",
|
||||
message: getErrorMessage(ERROR_CODES.FAVORITE.DELETE_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { LibraryService } from "@/lib/services/library.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import type { NextRequest } from "next/server";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ libraryId: string }> }
|
||||
) {
|
||||
try {
|
||||
const libraryId: string = (await params).libraryId;
|
||||
|
||||
// Scan library with deep=false
|
||||
await LibraryService.scanLibrary(libraryId, false);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "API Library Scan - Erreur");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: "Library scan error",
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.LIBRARY.SCAN_ERROR,
|
||||
name: "Library scan error",
|
||||
message: getErrorMessage(ERROR_CODES.LIBRARY.SCAN_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { TestService } from "@/lib/services/test.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import type { KomgaLibrary } from "@/types/komga";
|
||||
import type { NextRequest } from "next/server";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { serverUrl, username, password } = await request.json();
|
||||
const authHeader = Buffer.from(`${username}:${password}`).toString("base64");
|
||||
|
||||
const { libraries }: { libraries: KomgaLibrary[] } = await TestService.testConnection({
|
||||
serverUrl,
|
||||
authHeader,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
message: `✅ Connexion réussie ! ${libraries.length} bibliothèque${
|
||||
libraries.length > 1 ? "s" : ""
|
||||
} trouvée${libraries.length > 1 ? "s" : ""}`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors du test de connexion:");
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.KOMGA.CONNECTION_ERROR,
|
||||
name: "Connection error",
|
||||
message: getErrorMessage(ERROR_CODES.KOMGA.CONNECTION_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import type { UserPreferences } from "@/types/preferences";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
// GET reste utilisé par PreferencesContext pour récupérer les préférences
|
||||
export async function GET() {
|
||||
try {
|
||||
const preferences: UserPreferences = await PreferencesService.getPreferences();
|
||||
@@ -37,36 +38,3 @@ export async function GET() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const preferences: UserPreferences = await request.json();
|
||||
const updatedPreferences: UserPreferences =
|
||||
await PreferencesService.updatePreferences(preferences);
|
||||
return NextResponse.json(updatedPreferences);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la mise à jour des préférences:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
name: "Preferences update error",
|
||||
code: error.code,
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
name: "Preferences update error",
|
||||
code: ERROR_CODES.PREFERENCES.UPDATE_ERROR,
|
||||
message: getErrorMessage(ERROR_CODES.PREFERENCES.UPDATE_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { UserService } from "@/lib/services/user.service";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { AuthServerService } from "@/lib/services/auth-server.service";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { currentPassword, newPassword } = body;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
return NextResponse.json({ error: "Mots de passe manquants" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Vérifier que le nouveau mot de passe est fort
|
||||
if (!AuthServerService.isPasswordStrong(newPassword)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
"Le nouveau mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
await UserService.changePassword(currentPassword, newPassword);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors du changement de mot de passe:");
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, code: error.code },
|
||||
{
|
||||
status:
|
||||
error.code === "AUTH_INVALID_PASSWORD"
|
||||
? 400
|
||||
: error.code === "AUTH_UNAUTHENTICATED"
|
||||
? 401
|
||||
: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors du changement de mot de passe" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { Lock } from "lucide-react";
|
||||
import { changePassword } from "@/app/actions/password";
|
||||
|
||||
export function ChangePasswordForm() {
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
@@ -39,15 +40,10 @@ export function ChangePasswordForm() {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/user/password", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ currentPassword, newPassword }),
|
||||
});
|
||||
const result = await changePassword(currentPassword, newPassword);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || "Erreur lors du changement de mot de passe");
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||
import { deleteUser } from "@/app/actions/admin";
|
||||
|
||||
interface DeleteUserDialogProps {
|
||||
user: AdminUserData;
|
||||
@@ -29,13 +30,10 @@ export function DeleteUserDialog({ user, open, onOpenChange, onSuccess }: Delete
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${user.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const result = await deleteUser(user.id);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || "Erreur lors de la suppression");
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||
import { updateUserRoles } from "@/app/actions/admin";
|
||||
|
||||
interface EditUserDialogProps {
|
||||
user: AdminUserData;
|
||||
@@ -51,15 +52,10 @@ export function EditUserDialog({ user, open, onOpenChange, onSuccess }: EditUser
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${user.id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ roles: selectedRoles }),
|
||||
});
|
||||
const result = await updateUserRoles(user.id, selectedRoles);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || "Erreur lors de la mise à jour");
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { Lock } from "lucide-react";
|
||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||
import { resetUserPassword } from "@/app/actions/admin";
|
||||
|
||||
interface ResetPasswordDialogProps {
|
||||
user: AdminUserData;
|
||||
@@ -65,15 +66,10 @@ export function ResetPasswordDialog({
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${user.id}/password`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ newPassword }),
|
||||
});
|
||||
const result = await resetUserPassword(user.id, newPassword);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || "Erreur lors de la réinitialisation");
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { AppErrorType } from "@/types/global";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { registerUser } from "@/app/actions/auth";
|
||||
|
||||
interface RegisterFormProps {
|
||||
from?: string;
|
||||
@@ -41,24 +42,15 @@ export function RegisterForm({ from: _from }: RegisterFormProps) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Étape 1: Inscription via l'API
|
||||
const response = await fetch("/api/auth/register", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
// Étape 1: Inscription via Server Action
|
||||
const result = await registerUser(email, password);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
setError(
|
||||
data.error || {
|
||||
code: "AUTH_REGISTRATION_FAILED",
|
||||
name: "Registration failed",
|
||||
message: "Erreur lors de l'inscription",
|
||||
}
|
||||
);
|
||||
if (!result.success) {
|
||||
setError({
|
||||
code: "AUTH_REGISTRATION_FAILED",
|
||||
name: "Registration failed",
|
||||
message: result.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRouter } from "next/navigation";
|
||||
import logger from "@/lib/logger";
|
||||
import { scanLibrary } from "@/app/actions/library";
|
||||
|
||||
interface ScanButtonProps {
|
||||
libraryId: string;
|
||||
@@ -22,12 +23,10 @@ export function ScanButton({ libraryId }: ScanButtonProps) {
|
||||
const handleScan = async () => {
|
||||
setIsScanning(true);
|
||||
try {
|
||||
const response = await fetch(`/api/komga/libraries/${libraryId}/scan`, {
|
||||
method: "POST",
|
||||
});
|
||||
const result = await scanLibrary(libraryId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to scan library");
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
@@ -35,14 +34,9 @@ export function ScanButton({ libraryId }: ScanButtonProps) {
|
||||
description: t("library.scan.success.description"),
|
||||
});
|
||||
|
||||
// Attendre 5 secondes pour que le scan se termine, puis invalider le cache et rafraîchir
|
||||
// Attendre 5 secondes pour que le scan se termine, puis rafraîchir
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
// Invalider le cache
|
||||
await fetch(`/api/komga/libraries/${libraryId}/series`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
// Rafraîchir la page pour voir les changements
|
||||
router.refresh();
|
||||
|
||||
@@ -52,7 +46,7 @@ export function ScanButton({ libraryId }: ScanButtonProps) {
|
||||
description: t("library.scan.complete.description"),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Error invalidating cache after scan:");
|
||||
logger.error({ err: error }, "Error refreshing after scan:");
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("library.scan.error.title"),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useRouter } from "next/navigation";
|
||||
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
|
||||
import type { KomgaBook } from "@/types/komga";
|
||||
import logger from "@/lib/logger";
|
||||
import { updateReadProgress } from "@/app/actions/read-progress";
|
||||
|
||||
interface UsePageNavigationProps {
|
||||
book: KomgaBook;
|
||||
@@ -48,11 +49,7 @@ export function usePageNavigation({
|
||||
try {
|
||||
ClientOfflineBookService.setCurrentPage(bookRef.current, page);
|
||||
const completed = page === pagesLengthRef.current;
|
||||
await fetch(`/api/komga/books/${bookRef.current.id}/read-progress`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ page, completed }),
|
||||
});
|
||||
await updateReadProgress(bookRef.current.id, page, completed);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Sync error:");
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { SeriesCover } from "@/components/ui/series-cover";
|
||||
import { StatusBadge } from "@/components/ui/status-badge";
|
||||
import { IconButton } from "@/components/ui/icon-button";
|
||||
import logger from "@/lib/logger";
|
||||
import { addToFavorites, removeFromFavorites } from "@/app/actions/favorites";
|
||||
|
||||
interface SeriesHeaderProps {
|
||||
series: KomgaSeries;
|
||||
@@ -51,15 +52,10 @@ export const SeriesHeader = ({ series, refreshSeries }: SeriesHeaderProps) => {
|
||||
|
||||
const handleToggleFavorite = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/komga/favorites`, {
|
||||
method: isFavorite ? "DELETE" : "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ seriesId: series.id }),
|
||||
});
|
||||
const action = isFavorite ? removeFromFavorites : addToFavorites;
|
||||
const result = await action(series.id);
|
||||
|
||||
if (response.ok) {
|
||||
if (result.success) {
|
||||
setIsFavorite(!isFavorite);
|
||||
// Dispatcher l'événement avec le seriesId pour mise à jour optimiste de la sidebar
|
||||
const event = new CustomEvent("favoritesChanged", {
|
||||
@@ -70,10 +66,6 @@ export const SeriesHeader = ({ series, refreshSeries }: SeriesHeaderProps) => {
|
||||
title: t(isFavorite ? "series.header.favorite.remove" : "series.header.favorite.add"),
|
||||
description: series.metadata.title,
|
||||
});
|
||||
} else if (response.status === 500) {
|
||||
throw new AppError(ERROR_CODES.FAVORITE.SERVER_ERROR);
|
||||
} else if (response.status === 404) {
|
||||
throw new AppError(ERROR_CODES.FAVORITE.UPDATE_ERROR);
|
||||
} else {
|
||||
throw new AppError(
|
||||
isFavorite ? ERROR_CODES.FAVORITE.DELETE_ERROR : ERROR_CODES.FAVORITE.ADD_ERROR
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Network, Loader2 } from "lucide-react";
|
||||
import type { KomgaConfig } from "@/types/komga";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import logger from "@/lib/logger";
|
||||
import { saveKomgaConfig, testKomgaConnection } from "@/app/actions/config";
|
||||
|
||||
interface KomgaSettingsProps {
|
||||
initialConfig: KomgaConfig | null;
|
||||
@@ -40,21 +41,10 @@ export function KomgaSettings({ initialConfig }: KomgaSettingsProps) {
|
||||
const password = formData.get("password") as string;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/komga/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
serverUrl: serverUrl.trim(),
|
||||
username,
|
||||
password: password || config.password,
|
||||
}),
|
||||
});
|
||||
const result = await testKomgaConnection(serverUrl.trim(), username, password || config.password);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || t("settings.komga.error.message"));
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
@@ -90,31 +80,22 @@ export function KomgaSettings({ initialConfig }: KomgaSettingsProps) {
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/komga/config", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: newConfig.serverUrl,
|
||||
username: newConfig.username,
|
||||
password: newConfig.password,
|
||||
}),
|
||||
const result = await saveKomgaConfig({
|
||||
url: newConfig.serverUrl,
|
||||
username: newConfig.username,
|
||||
password: newConfig.password,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || t("settings.komga.error.message"));
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
const savedConfig = await response.json();
|
||||
|
||||
setConfig(newConfig);
|
||||
setLocalInitialConfig({
|
||||
url: newConfig.serverUrl,
|
||||
username: newConfig.username,
|
||||
userId: savedConfig.userId,
|
||||
authHeader: savedConfig.authHeader,
|
||||
userId: result.data?.userId || 0,
|
||||
authHeader: result.data?.authHeader || "",
|
||||
});
|
||||
setIsEditingConfig(false);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.serv
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import logger from "@/lib/logger";
|
||||
import { updateReadProgress } from "@/app/actions/read-progress";
|
||||
|
||||
interface MarkAsReadButtonProps {
|
||||
bookId: string;
|
||||
@@ -32,16 +33,11 @@ export function MarkAsReadButton({
|
||||
setIsLoading(true);
|
||||
try {
|
||||
ClientOfflineBookService.removeCurrentPageById(bookId);
|
||||
const response = await fetch(`/api/komga/books/${bookId}/read-progress`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ page: pagesCount, completed: true }),
|
||||
});
|
||||
|
||||
const result = await updateReadProgress(bookId, pagesCount, true);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(t("books.actions.markAsRead.error.update"));
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
toast({
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.serv
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import logger from "@/lib/logger";
|
||||
import { deleteReadProgress } from "@/app/actions/read-progress";
|
||||
|
||||
interface MarkAsUnreadButtonProps {
|
||||
bookId: string;
|
||||
@@ -23,12 +24,10 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea
|
||||
e.stopPropagation(); // Empêcher la propagation au parent
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch(`/api/komga/books/${bookId}/read-progress`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const result = await deleteReadProgress(bookId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(t("books.actions.markAsUnread.error.update"));
|
||||
if (!result.success) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
// On supprime la page courante du localStorage seulement après que l'API a répondu
|
||||
|
||||
@@ -7,10 +7,11 @@ import { AppError } from "../utils/errors";
|
||||
import type { UserPreferences } from "@/types/preferences";
|
||||
import { defaultPreferences } from "@/types/preferences";
|
||||
import logger from "@/lib/logger";
|
||||
import { updatePreferences as updatePreferencesAction } from "@/app/actions/preferences";
|
||||
|
||||
interface PreferencesContextType {
|
||||
preferences: UserPreferences;
|
||||
updatePreferences: (newPreferences: Partial<UserPreferences>) => Promise<void>;
|
||||
updatePreferences: (newPreferences: Partial<UserPreferences>) => Promise<UserPreferences | undefined>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
@@ -84,28 +85,22 @@ export function PreferencesProvider({
|
||||
}
|
||||
}, [status, fetchPreferences, hasValidInitialPreferences]);
|
||||
|
||||
const updatePreferences = useCallback(async (newPreferences: Partial<UserPreferences>) => {
|
||||
const updatePreferences = useCallback(async (newPreferences: Partial<UserPreferences>): Promise<UserPreferences | undefined> => {
|
||||
try {
|
||||
const response = await fetch("/api/preferences", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(newPreferences),
|
||||
});
|
||||
const result = await updatePreferencesAction(newPreferences);
|
||||
|
||||
if (!response.ok) {
|
||||
if (!result.success) {
|
||||
throw new AppError(ERROR_CODES.PREFERENCES.UPDATE_ERROR);
|
||||
}
|
||||
|
||||
const updatedPreferences = await response.json();
|
||||
if (result.data) {
|
||||
setPreferences((prev) => ({
|
||||
...prev,
|
||||
...result.data,
|
||||
}));
|
||||
}
|
||||
|
||||
setPreferences((prev) => ({
|
||||
...prev,
|
||||
...updatedPreferences,
|
||||
}));
|
||||
|
||||
return updatedPreferences;
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la mise à jour des préférences");
|
||||
throw error;
|
||||
|
||||
@@ -10,6 +10,8 @@ interface KomgaRequestInit extends RequestInit {
|
||||
noJson?: boolean;
|
||||
/** Next.js cache duration in seconds. Use false to disable cache, number for TTL */
|
||||
revalidate?: number | false;
|
||||
/** Cache tags for targeted invalidation */
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
interface KomgaUrlBuilder {
|
||||
@@ -137,10 +139,12 @@ export abstract class BaseApiService {
|
||||
connectTimeout: timeoutMs,
|
||||
bodyTimeout: timeoutMs,
|
||||
headersTimeout: timeoutMs,
|
||||
// Next.js cache
|
||||
next: options.revalidate !== undefined
|
||||
? { revalidate: options.revalidate }
|
||||
: undefined,
|
||||
// Next.js cache with tags support
|
||||
next: options.tags
|
||||
? { tags: options.tags }
|
||||
: options.revalidate !== undefined
|
||||
? { revalidate: options.revalidate }
|
||||
: undefined,
|
||||
});
|
||||
} catch (fetchError: any) {
|
||||
// Gestion spécifique des erreurs DNS
|
||||
@@ -158,10 +162,12 @@ export abstract class BaseApiService {
|
||||
// Force IPv4 si IPv6 pose problème
|
||||
// @ts-ignore
|
||||
family: 4,
|
||||
// Next.js cache
|
||||
next: options.revalidate !== undefined
|
||||
? { revalidate: options.revalidate }
|
||||
: undefined,
|
||||
// Next.js cache with tags support
|
||||
next: options.tags
|
||||
? { tags: options.tags }
|
||||
: options.revalidate !== undefined
|
||||
? { revalidate: options.revalidate }
|
||||
: undefined,
|
||||
});
|
||||
} else if (fetchError?.cause?.code === "UND_ERR_CONNECT_TIMEOUT") {
|
||||
// Retry automatique sur timeout de connexion (cold start)
|
||||
@@ -175,10 +181,12 @@ export abstract class BaseApiService {
|
||||
connectTimeout: timeoutMs,
|
||||
bodyTimeout: timeoutMs,
|
||||
headersTimeout: timeoutMs,
|
||||
// Next.js cache
|
||||
next: options.revalidate !== undefined
|
||||
? { revalidate: options.revalidate }
|
||||
: undefined,
|
||||
// Next.js cache with tags support
|
||||
next: options.tags
|
||||
? { tags: options.tags }
|
||||
: options.revalidate !== undefined
|
||||
? { revalidate: options.revalidate }
|
||||
: undefined,
|
||||
});
|
||||
} else {
|
||||
throw fetchError;
|
||||
|
||||
@@ -7,8 +7,12 @@ import { AppError } from "../../utils/errors";
|
||||
|
||||
export type { HomeData };
|
||||
|
||||
// Cache tag pour invalidation ciblée
|
||||
const HOME_CACHE_TAG = "home-data";
|
||||
|
||||
export class HomeService extends BaseApiService {
|
||||
private static readonly CACHE_TTL = 120; // 2 minutes
|
||||
private static readonly CACHE_TTL = 120; // 2 minutes fallback
|
||||
private static readonly CACHE_TAG = HOME_CACHE_TAG;
|
||||
|
||||
static async getHomeData(): Promise<HomeData> {
|
||||
try {
|
||||
@@ -25,7 +29,7 @@ export class HomeService extends BaseApiService {
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ revalidate: this.CACHE_TTL }
|
||||
{ revalidate: this.CACHE_TTL, tags: [this.CACHE_TAG] }
|
||||
),
|
||||
this.fetchFromApi<LibraryResponse<KomgaBook>>(
|
||||
{
|
||||
@@ -39,7 +43,7 @@ export class HomeService extends BaseApiService {
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ revalidate: this.CACHE_TTL }
|
||||
{ revalidate: this.CACHE_TTL, tags: [this.CACHE_TAG] }
|
||||
),
|
||||
this.fetchFromApi<LibraryResponse<KomgaBook>>(
|
||||
{
|
||||
@@ -51,7 +55,7 @@ export class HomeService extends BaseApiService {
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ revalidate: this.CACHE_TTL }
|
||||
{ revalidate: this.CACHE_TTL, tags: [this.CACHE_TAG] }
|
||||
),
|
||||
this.fetchFromApi<LibraryResponse<KomgaBook>>(
|
||||
{
|
||||
@@ -63,7 +67,7 @@ export class HomeService extends BaseApiService {
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ revalidate: this.CACHE_TTL }
|
||||
{ revalidate: this.CACHE_TTL, tags: [this.CACHE_TAG] }
|
||||
),
|
||||
this.fetchFromApi<LibraryResponse<KomgaSeries>>(
|
||||
{
|
||||
@@ -75,7 +79,7 @@ export class HomeService extends BaseApiService {
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ revalidate: this.CACHE_TTL }
|
||||
{ revalidate: this.CACHE_TTL, tags: [this.CACHE_TAG] }
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user