| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- /**
- * API Client Service
- *
- * Handles all HTTP communication with the admin backend API.
- */
- import axios, { AxiosError, AxiosInstance } from 'axios';
- import { API_BASE_URL, API_TIMEOUT, HttpMethod } from '../constants.js';
- import type { ApiError } from '../types.js';
- /**
- * API Client class for making HTTP requests to the backend
- */
- export class ApiClient {
- private client: AxiosInstance;
- private token: string | null = null;
- constructor(baseURL: string = API_BASE_URL) {
- this.client = axios.create({
- baseURL,
- timeout: API_TIMEOUT,
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json'
- }
- });
- // Request interceptor to add auth token
- this.client.interceptors.request.use((config) => {
- if (this.token) {
- config.headers.Authorization = `Bearer ${this.token}`;
- }
- return config;
- });
- // Response interceptor for error handling
- this.client.interceptors.response.use(
- (response) => response,
- (error) => {
- throw this.handleError(error);
- }
- );
- }
- /**
- * Set authentication token
- */
- setToken(token: string): void {
- this.token = token;
- }
- /**
- * Get current authentication token
- */
- getToken(): string | null {
- return this.token;
- }
- /**
- * Clear authentication token
- */
- clearToken(): void {
- this.token = null;
- }
- /**
- * Make a generic API request
- */
- async request<T>(
- method: HttpMethod,
- endpoint: string,
- data?: unknown,
- params?: Record<string, unknown>
- ): Promise<T> {
- try {
- const response = await this.client.request<T>({
- method,
- url: endpoint,
- data,
- params
- });
- return response.data;
- } catch (error) {
- throw this.handleError(error);
- }
- }
- /**
- * Make a GET request
- */
- async get<T>(endpoint: string, params?: Record<string, unknown>): Promise<T> {
- return this.request<T>('GET', endpoint, undefined, params);
- }
- /**
- * Make a POST request
- */
- async post<T>(endpoint: string, data?: unknown): Promise<T> {
- return this.request<T>('POST', endpoint, data);
- }
- /**
- * Make a PUT request
- */
- async put<T>(endpoint: string, data?: unknown): Promise<T> {
- return this.request<T>('PUT', endpoint, data);
- }
- /**
- * Make a DELETE request
- */
- async delete<T>(endpoint: string): Promise<T> {
- return this.request<T>('DELETE', endpoint);
- }
- /**
- * Handle API errors and return formatted error messages
- */
- private handleError(error: unknown): Error {
- if (axios.isAxiosError(error)) {
- const axiosError = error as AxiosError<ApiError>;
- if (axiosError.response) {
- const { status, data } = axiosError.response;
- switch (status) {
- case 400:
- return new Error(
- `Error: ${data?.message || 'Invalid request parameters'}. ` +
- 'Please check your input and try again.'
- );
- case 401:
- return new Error(
- 'Error: Authentication failed. ' +
- 'Please check your credentials or re-login.'
- );
- case 403:
- return new Error(
- 'Error: Permission denied. ' +
- 'You do not have access to this resource.'
- );
- case 404:
- return new Error(
- 'Error: Resource not found. ' +
- 'Please check the ID is correct.'
- );
- case 429:
- return new Error(
- 'Error: Rate limit exceeded. ' +
- 'Please wait before making more requests.'
- );
- case 500:
- return new Error(
- `Error: Server error. ${data?.message || 'Please try again later.'}`
- );
- default:
- return new Error(
- `Error: API request failed with status ${status}. ` +
- (data?.message || '')
- );
- }
- } else if (axiosError.code === 'ECONNABORTED') {
- return new Error(
- 'Error: Request timed out. ' +
- 'Please try again or check your network connection.'
- );
- } else if (axiosError.code === 'ECONNREFUSED') {
- return new Error(
- `Error: Cannot connect to API server at ${this.client.defaults.baseURL}. ` +
- 'Please ensure the server is running.'
- );
- }
- }
- return new Error(
- `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`
- );
- }
- }
- /**
- * Singleton API client instance
- */
- let apiClientInstance: ApiClient | null = null;
- /**
- * Get or create the singleton API client instance
- */
- export function getApiClient(): ApiClient {
- if (!apiClientInstance) {
- apiClientInstance = new ApiClient();
- }
- return apiClientInstance;
- }
- /**
- * Reset the singleton API client instance (useful for testing)
- */
- export function resetApiClient(): void {
- apiClientInstance = null;
- }
|