76 lines
No EOL
2.5 KiB
TypeScript
76 lines
No EOL
2.5 KiB
TypeScript
// Créez un nouveau fichier lib/secureApi.ts
|
|
|
|
/**
|
|
* Fetcher sécurisé qui passe par l'API Next.js (pas directement le Lambda)
|
|
* À utiliser pour les ressources sensibles comme les contrats individuels
|
|
*/
|
|
export async function secureApi<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const headers = new Headers(init.headers || {});
|
|
|
|
// Headers par défaut
|
|
headers.set("Accept", "application/json");
|
|
if (init.body && !headers.has("Content-Type")) {
|
|
headers.set("Content-Type", "application/json");
|
|
}
|
|
|
|
// URL locale (API Next.js) au lieu du Lambda direct
|
|
const url = `/api${path}`;
|
|
|
|
console.log("🔒 Secure API call:", { url, method: init.method || 'GET' });
|
|
|
|
const res = await fetch(url, {
|
|
...init,
|
|
credentials: "include", // Important pour les cookies de session
|
|
headers,
|
|
cache: "no-store",
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(text || `API error ${res.status}`);
|
|
}
|
|
|
|
return res.json();
|
|
}
|
|
|
|
/*
|
|
* Exemple d'utilisation (à mettre dans app/(app)/contrats/[id]/page.tsx)
|
|
* ---------------------------------------------------------------
|
|
* import { useQuery } from '@tanstack/react-query';
|
|
* import { secureApi } from '@/lib/secureApi';
|
|
*
|
|
* // Importe ton vrai type ContratDetail depuis tes types de domaine
|
|
* // ou déclare un type minimal local si nécessaire.
|
|
* // import type { ContratDetail } from '@/types/contrats';
|
|
*
|
|
* function useContratDetail(id: string) {
|
|
* return useQuery<ContratDetail>({
|
|
* queryKey: ['contrat', id],
|
|
* queryFn: async () => {
|
|
* try {
|
|
* // ✅ Utilise le fetcher sécurisé au lieu de api()
|
|
* return await secureApi<ContratDetail>(`/contrats/${id}`);
|
|
* } catch (error: unknown) {
|
|
* const message = error instanceof Error ? error.message : String(error);
|
|
* // Gestion spécifique des erreurs d'accès
|
|
* if (message.includes('403') || message.includes('forbidden')) {
|
|
* throw new Error('access_denied');
|
|
* }
|
|
* if (message.includes('404')) {
|
|
* throw new Error('not_found');
|
|
* }
|
|
* throw error;
|
|
* }
|
|
* },
|
|
* staleTime: 15_000,
|
|
* retry: (failureCount: number, error: unknown) => {
|
|
* const message = error instanceof Error ? error.message : String(error);
|
|
* // Ne pas réessayer si c'est un problème d'accès
|
|
* if (message === 'access_denied' || message === 'not_found') {
|
|
* return false;
|
|
* }
|
|
* return failureCount < 3;
|
|
* },
|
|
* });
|
|
* }
|
|
*/ |