168 lines
4.6 KiB
TypeScript
168 lines
4.6 KiB
TypeScript
// ============================================================================
|
|
// API Client
|
|
// ============================================================================
|
|
|
|
import { API_URL, STORAGE_KEYS } from "@/lib/utils/constants";
|
|
import type { ApiError } from "@/lib/types/api";
|
|
|
|
// ============================================================================
|
|
// Types
|
|
// ============================================================================
|
|
|
|
interface RequestOptions extends RequestInit {
|
|
params?: Record<string, any>;
|
|
requireAuth?: boolean;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Helper Functions
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Build URL with query parameters
|
|
*/
|
|
const buildUrl = (endpoint: string, params?: Record<string, any>): string => {
|
|
if (!params || Object.keys(params).length === 0) {
|
|
return endpoint;
|
|
}
|
|
|
|
const searchParams = new URLSearchParams();
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null) {
|
|
searchParams.append(key, String(value));
|
|
}
|
|
});
|
|
|
|
const queryString = searchParams.toString();
|
|
return queryString ? `${endpoint}?${queryString}` : endpoint;
|
|
};
|
|
|
|
/**
|
|
* Get auth token from storage
|
|
*/
|
|
const getAuthToken = (): string | null => {
|
|
if (typeof window === "undefined") return null;
|
|
return localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
};
|
|
|
|
/**
|
|
* Clear auth tokens from storage
|
|
*/
|
|
export const clearAuthTokens = (): void => {
|
|
if (typeof window === "undefined") return;
|
|
localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
|
|
localStorage.removeItem(STORAGE_KEYS.USER);
|
|
};
|
|
|
|
/**
|
|
* Save auth tokens to storage
|
|
*/
|
|
export const saveAuthTokens = (
|
|
accessToken: string,
|
|
refreshToken?: string
|
|
): void => {
|
|
if (typeof window === "undefined") return;
|
|
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken);
|
|
if (refreshToken) {
|
|
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken);
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// API Client
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Main API request function
|
|
*/
|
|
export const apiRequest = async <T = any>(
|
|
endpoint: string,
|
|
options: RequestOptions = {}
|
|
): Promise<T> => {
|
|
const { params, requireAuth = true, ...fetchOptions } = options;
|
|
|
|
// Build URL
|
|
const url = buildUrl(`${API_URL}${endpoint}`, params);
|
|
|
|
const headers: HeadersInit = {
|
|
"Content-Type": "application/json",
|
|
...fetchOptions.headers,
|
|
};
|
|
|
|
// Add auth token if required
|
|
if (requireAuth) {
|
|
const token = getAuthToken();
|
|
if (token) {
|
|
(headers as Record<string, string>)["Authorization"] = `Bearer ${token}`;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
...fetchOptions,
|
|
headers,
|
|
});
|
|
|
|
// Handle 204 No Content
|
|
if (response.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
|
|
// Parse response
|
|
const data = await response.json();
|
|
|
|
// Handle errors
|
|
if (!response.ok) {
|
|
// FastAPI returns errors in format { "detail": "..." }
|
|
const error: ApiError = {
|
|
detail: data.detail || "An error occurred",
|
|
};
|
|
throw error;
|
|
}
|
|
|
|
return data as T;
|
|
} catch (error: any) {
|
|
// Network error or parsing error
|
|
if (!error.detail) {
|
|
throw {
|
|
detail: error.message || "Network error occurred",
|
|
} as ApiError;
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Convenience Methods
|
|
// ============================================================================
|
|
|
|
export const api = {
|
|
get: <T = any>(endpoint: string, options?: RequestOptions) =>
|
|
apiRequest<T>(endpoint, { ...options, method: "GET" }),
|
|
|
|
post: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
|
apiRequest<T>(endpoint, {
|
|
...options,
|
|
method: "POST",
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
}),
|
|
|
|
put: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
|
apiRequest<T>(endpoint, {
|
|
...options,
|
|
method: "PUT",
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
}),
|
|
|
|
patch: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
|
apiRequest<T>(endpoint, {
|
|
...options,
|
|
method: "PATCH",
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
}),
|
|
|
|
delete: <T = any>(endpoint: string, options?: RequestOptions) =>
|
|
apiRequest<T>(endpoint, { ...options, method: "DELETE" }),
|
|
};
|