feat(api): add TagService and enhance API error handling
Introduce `TagService` to manage tag-related API interactions. Add SSR cookie interceptor for API requests and implement token refresh logic on 401 errors. Update `FavoriteService` to use `favoritesOnly` filter for exploring content.
This commit is contained in:
@@ -8,6 +8,23 @@ const api = axios.create({
|
||||
},
|
||||
});
|
||||
|
||||
// Interceptor for Server-Side Rendering to pass cookies
|
||||
api.interceptors.request.use(async (config) => {
|
||||
if (typeof window === "undefined") {
|
||||
try {
|
||||
const { cookies } = await import("next/headers");
|
||||
const cookieStore = await cookies();
|
||||
const cookieHeader = cookieStore.toString();
|
||||
if (cookieHeader) {
|
||||
config.headers.Cookie = cookieHeader;
|
||||
}
|
||||
} catch (_error) {
|
||||
// Fail silently if cookies() is not available (e.g. during build)
|
||||
}
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Système anti-spam rudimentaire pour les erreurs répétitives
|
||||
const errorCache = new Map<string, number>();
|
||||
const SPAM_THRESHOLD_MS = 2000; // 2 secondes de silence après une erreur sur le même endpoint
|
||||
@@ -19,14 +36,35 @@ api.interceptors.response.use(
|
||||
errorCache.delete(url);
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// Handle Token Refresh (401 Unauthorized)
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
!originalRequest._retry &&
|
||||
!originalRequest.url?.includes("/auth/refresh") &&
|
||||
!originalRequest.url?.includes("/auth/login")
|
||||
) {
|
||||
originalRequest._retry = true;
|
||||
try {
|
||||
await api.post("/auth/refresh");
|
||||
return api(originalRequest);
|
||||
} catch (refreshError) {
|
||||
// If refresh fails, we might want to redirect to login on the client
|
||||
if (typeof window !== "undefined") {
|
||||
window.location.href = "/login";
|
||||
}
|
||||
return Promise.reject(refreshError);
|
||||
}
|
||||
}
|
||||
|
||||
const url = error.config?.url || "unknown";
|
||||
const now = Date.now();
|
||||
const lastErrorTime = errorCache.get(url);
|
||||
|
||||
if (lastErrorTime && now - lastErrorTime < SPAM_THRESHOLD_MS) {
|
||||
// Ignorer l'erreur si elle se produit trop rapidement (déjà signalée)
|
||||
// On retourne une promesse qui ne se résout jamais ou on rejette avec une marque spéciale
|
||||
return new Promise(() => {});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,15 @@ export const FavoriteService = {
|
||||
limit: number;
|
||||
offset: number;
|
||||
}): Promise<PaginatedResponse<Content>> {
|
||||
const { data } = await api.get<PaginatedResponse<Content>>("/favorites", {
|
||||
params,
|
||||
});
|
||||
const { data } = await api.get<PaginatedResponse<Content>>(
|
||||
"/contents/explore",
|
||||
{
|
||||
params: {
|
||||
...params,
|
||||
favoritesOnly: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
16
frontend/src/services/tag.service.ts
Normal file
16
frontend/src/services/tag.service.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import api from "@/lib/api";
|
||||
import type { Tag } from "@/types/content";
|
||||
|
||||
export const TagService = {
|
||||
async getAll(
|
||||
params: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
query?: string;
|
||||
sort?: "popular" | "recent";
|
||||
} = {},
|
||||
): Promise<Tag[]> {
|
||||
const { data } = await api.get<Tag[]>("/tags", { params });
|
||||
return data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user