sunatest / frontend /src /lib /api-client.ts
llama1's picture
Upload 781 files
5da4770 verified
import { createClient } from '@/lib/supabase/client';
import { handleApiError, handleNetworkError, ErrorContext, ApiError } from './error-handler';
const API_URL = process.env.NEXT_PUBLIC_BACKEND_URL || '';
export interface ApiClientOptions {
showErrors?: boolean;
errorContext?: ErrorContext;
timeout?: number;
}
export interface ApiResponse<T = any> {
data?: T;
error?: ApiError;
success: boolean;
}
export const apiClient = {
async request<T = any>(
url: string,
options: RequestInit & ApiClientOptions = {}
): Promise<ApiResponse<T>> {
const {
showErrors = true,
errorContext,
timeout = 30000,
...fetchOptions
} = options;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const supabase = createClient();
const { data: { session } } = await supabase.auth.getSession();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...fetchOptions.headers as Record<string, string>,
};
if (session?.access_token) {
headers['Authorization'] = `Bearer ${session.access_token}`;
}
const response = await fetch(url, {
...fetchOptions,
headers,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const error: ApiError = new Error(`HTTP ${response.status}: ${response.statusText}`);
error.status = response.status;
error.response = response;
try {
const errorData = await response.json();
error.details = errorData;
if (errorData.message) {
error.message = errorData.message;
}
} catch {
}
if (showErrors) {
handleApiError(error, errorContext);
}
return {
error,
success: false,
};
}
let data: T;
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
data = await response.json();
} else if (contentType?.includes('text/')) {
data = await response.text() as T;
} else {
data = await response.blob() as T;
}
return {
data,
success: true,
};
} catch (error: any) {
const apiError: ApiError = error instanceof Error ? error : new Error(String(error));
if (error.name === 'AbortError') {
apiError.message = 'Request timeout';
apiError.code = 'TIMEOUT';
}
if (showErrors) {
handleNetworkError(apiError, errorContext);
}
return {
error: apiError,
success: false,
};
}
},
get: async <T = any>(
url: string,
options: Omit<RequestInit & ApiClientOptions, 'method' | 'body'> = {}
): Promise<ApiResponse<T>> => {
return apiClient.request<T>(url, {
...options,
method: 'GET',
});
},
post: async <T = any>(
url: string,
data?: any,
options: Omit<RequestInit & ApiClientOptions, 'method'> = {}
): Promise<ApiResponse<T>> => {
return apiClient.request<T>(url, {
...options,
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
});
},
put: async <T = any>(
url: string,
data?: any,
options: Omit<RequestInit & ApiClientOptions, 'method'> = {}
): Promise<ApiResponse<T>> => {
return apiClient.request<T>(url, {
...options,
method: 'PUT',
body: data ? JSON.stringify(data) : undefined,
});
},
patch: async <T = any>(
url: string,
data?: any,
options: Omit<RequestInit & ApiClientOptions, 'method'> = {}
): Promise<ApiResponse<T>> => {
return apiClient.request<T>(url, {
...options,
method: 'PATCH',
body: data ? JSON.stringify(data) : undefined,
});
},
delete: async <T = any>(
url: string,
options: Omit<RequestInit & ApiClientOptions, 'method' | 'body'> = {}
): Promise<ApiResponse<T>> => {
return apiClient.request<T>(url, {
...options,
method: 'DELETE',
});
},
upload: async <T = any>(
url: string,
formData: FormData,
options: Omit<RequestInit & ApiClientOptions, 'method' | 'body'> = {}
): Promise<ApiResponse<T>> => {
const { headers, ...restOptions } = options;
const uploadHeaders = { ...headers as Record<string, string> };
delete uploadHeaders['Content-Type'];
return apiClient.request<T>(url, {
...restOptions,
method: 'POST',
body: formData,
headers: uploadHeaders,
});
},
};
export const supabaseClient = {
async execute<T = any>(
queryFn: () => Promise<{ data: T | null; error: any }>,
errorContext?: ErrorContext
): Promise<ApiResponse<T>> {
try {
const { data, error } = await queryFn();
if (error) {
const apiError: ApiError = new Error(error.message || 'Database error');
apiError.code = error.code;
apiError.details = error;
handleApiError(apiError, errorContext);
return {
error: apiError,
success: false,
};
}
return {
data: data as T,
success: true,
};
} catch (error: any) {
const apiError: ApiError = error instanceof Error ? error : new Error(String(error));
handleApiError(apiError, errorContext);
return {
error: apiError,
success: false,
};
}
},
};
export const backendApi = {
get: <T = any>(endpoint: string, options?: Omit<RequestInit & ApiClientOptions, 'method' | 'body'>) =>
apiClient.get<T>(`${API_URL}${endpoint}`, options),
post: <T = any>(endpoint: string, data?: any, options?: Omit<RequestInit & ApiClientOptions, 'method'>) =>
apiClient.post<T>(`${API_URL}${endpoint}`, data, options),
put: <T = any>(endpoint: string, data?: any, options?: Omit<RequestInit & ApiClientOptions, 'method'>) =>
apiClient.put<T>(`${API_URL}${endpoint}`, data, options),
patch: <T = any>(endpoint: string, data?: any, options?: Omit<RequestInit & ApiClientOptions, 'method'>) =>
apiClient.patch<T>(`${API_URL}${endpoint}`, data, options),
delete: <T = any>(endpoint: string, options?: Omit<RequestInit & ApiClientOptions, 'method' | 'body'>) =>
apiClient.delete<T>(`${API_URL}${endpoint}`, options),
upload: <T = any>(endpoint: string, formData: FormData, options?: Omit<RequestInit & ApiClientOptions, 'method' | 'body'>) =>
apiClient.upload<T>(`${API_URL}${endpoint}`, formData, options),
};