Files
tgex-frontend/lib/api/client.ts
2025-12-29 10:12:13 +03:00

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" }),
};