|
1 | | -import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; |
2 | | - |
3 | | -export interface ApiError { |
4 | | - status: number; |
5 | | - message: string; |
6 | | - data?: any; |
7 | | -} |
8 | | - |
9 | | -// Define classes for different error types |
10 | | -export class BadRequestError extends Error { |
11 | | - public readonly status = 400; |
12 | | - public readonly data?: any; |
13 | | - |
14 | | - constructor(message: string, data?: any) { |
15 | | - super(message); |
16 | | - this.name = 'BadRequestError'; |
17 | | - this.data = data; |
18 | | - } |
19 | | -} |
| 1 | +import { headerBuilder } from '../auth'; |
20 | 2 |
|
21 | | -export class UnauthorizedError extends Error { |
22 | | - public readonly status = 401; |
23 | | - public readonly data?: any; |
| 3 | +// Get base API URL |
| 4 | +const api = process.env.REACT_APP_API_BASE_URL || 'http://127.0.0.1:8000/api'; |
24 | 5 |
|
25 | | - constructor(message: string, data?: any) { |
26 | | - super(message); |
27 | | - this.name = 'UnauthorizedError'; |
28 | | - this.data = data; |
29 | | - } |
30 | | -} |
| 6 | +// Helper function to build URL with query parameters |
| 7 | +const buildUrl = (url: string, params?: Record<string, any>): string => { |
| 8 | + if (!params) return url; |
31 | 9 |
|
32 | | -export class NotFoundError extends Error { |
33 | | - public readonly status = 404; |
34 | | - public readonly data?: any; |
| 10 | + const searchParams = new URLSearchParams(); |
| 11 | + Object.entries(params).forEach(([key, value]) => { |
| 12 | + if (value !== undefined && value !== null) { |
| 13 | + searchParams.append(key, String(value)); |
| 14 | + } |
| 15 | + }); |
35 | 16 |
|
36 | | - constructor(message: string, data?: any) { |
37 | | - super(message); |
38 | | - this.name = 'NotFoundError'; |
39 | | - this.data = data; |
40 | | - } |
41 | | -} |
| 17 | + const queryString = searchParams.toString(); |
| 18 | + return queryString ? `${url}?${queryString}` : url; |
| 19 | +}; |
42 | 20 |
|
43 | | -export class ServerError extends Error { |
44 | | - public readonly status = 500; |
45 | | - public readonly data?: any; |
| 21 | +// Fetch with Authentication Headers |
| 22 | +const fetchWithAuth = async (url: string, method: string = "GET", body: BodyInit | null = null) => { |
| 23 | + const token = localStorage.getItem('token'); // Get the token from localStorage |
| 24 | + const authHeaders = headerBuilder(); // Get authentication headers |
46 | 25 |
|
47 | | - constructor(message: string, data?: any) { |
48 | | - super(message); |
49 | | - this.name = 'ServerError'; |
50 | | - this.data = data; |
51 | | - } |
52 | | -} |
53 | | - |
54 | | -export class ApiClient { |
55 | | - private client: AxiosInstance; |
56 | | - |
57 | | - constructor(baseURL: string) { |
58 | | - this.client = axios.create({ |
59 | | - baseURL, |
60 | | - headers: { |
61 | | - 'Content-Type': 'application/json', |
62 | | - }, |
63 | | - withCredentials: true, // Include credentials for cross-origin requests if needed |
64 | | - }); |
65 | | - |
66 | | - // Add request interceptor to handle authentication |
67 | | - this.client.interceptors.request.use( |
68 | | - (config) => { |
69 | | - // Get token from localStorage or other storage mechanism |
70 | | - const token = localStorage.getItem('authToken'); |
71 | | - if (token) { |
72 | | - config.headers.Authorization = `Bearer ${token}`; |
73 | | - } |
74 | | - return config; |
75 | | - }, |
76 | | - (error) => Promise.reject(error) |
77 | | - ); |
78 | | - |
79 | | - // Add response interceptor to handle errors |
80 | | - this.client.interceptors.response.use( |
81 | | - (response) => response, |
82 | | - (error: AxiosError) => { |
83 | | - const { response } = error; |
84 | | - |
85 | | - if (!response) { |
86 | | - return Promise.reject(new ServerError('Network error or server is not responding')); |
87 | | - } const { status, data } = response; |
88 | | - const message = (data as any)?.detail || (data as any)?.message || 'An error occurred'; |
89 | | - |
90 | | - switch (status) { |
91 | | - case 400: |
92 | | - return Promise.reject(new BadRequestError(message, data)); |
93 | | - case 401: |
94 | | - // Optionally clear auth state here |
95 | | - return Promise.reject(new UnauthorizedError(message, data)); |
96 | | - case 404: |
97 | | - return Promise.reject(new NotFoundError(message, data)); |
98 | | - case 500: |
99 | | - return Promise.reject(new ServerError(message, data)); |
100 | | - default: |
101 | | - return Promise.reject(new Error(`Unexpected error: ${message}`)); |
102 | | - } |
103 | | - } |
104 | | - ); |
105 | | - } |
| 26 | + const headers: Record<string, string> = { |
| 27 | + ...authHeaders, // Include auth headers from headerBuilder |
| 28 | + }; |
106 | 29 |
|
107 | | - async get<T>(path: string, config?: AxiosRequestConfig): Promise<T> { |
108 | | - const response: AxiosResponse<T> = await this.client.get(path, config); |
109 | | - return response.data; |
| 30 | + if (token) { |
| 31 | + headers['Authorization'] = `Bearer ${token}`; // Add the token to the Authorization header |
110 | 32 | } |
111 | 33 |
|
112 | | - async post<T>(path: string, data?: any, config?: AxiosRequestConfig): Promise<T> { |
113 | | - const response: AxiosResponse<T> = await this.client.post(path, data, config); |
114 | | - return response.data; |
| 34 | + // If body is FormData, do not set Content-Type header |
| 35 | + if (body && body instanceof FormData) { |
| 36 | + delete headers['Content-Type']; |
| 37 | + } else { |
| 38 | + headers['Content-Type'] = 'application/json'; |
| 39 | + body = body ? JSON.stringify(body) : null; |
115 | 40 | } |
116 | 41 |
|
117 | | - async put<T>(path: string, data?: any, config?: AxiosRequestConfig): Promise<T> { |
118 | | - const response: AxiosResponse<T> = await this.client.put(path, data, config); |
119 | | - return response.data; |
| 42 | + const options: RequestInit = { |
| 43 | + method, |
| 44 | + headers, |
| 45 | + body: body || undefined, |
| 46 | + }; |
| 47 | + |
| 48 | + try { |
| 49 | + const response = await fetch(`${api}${url}`, options); |
| 50 | + console.log('response', response); |
| 51 | + |
| 52 | + if (!response.ok) { |
| 53 | + const errorText = await response.text(); |
| 54 | + throw new Error(errorText || 'Something went wrong'); |
| 55 | + } |
| 56 | + |
| 57 | + const isJson = response.headers.get('content-type')?.includes('application/json'); |
| 58 | + return isJson ? await response.json() : null; |
| 59 | + } catch (error) { |
| 60 | + console.error('API Error:', (error as Error).message); |
| 61 | + throw error; |
120 | 62 | } |
121 | | - |
122 | | - async delete<T>(path: string, config?: AxiosRequestConfig): Promise<T> { |
123 | | - const response: AxiosResponse<T> = await this.client.delete(path, config); |
124 | | - return response.data; |
125 | | - } |
126 | | - |
127 | | - async patch<T>(path: string, data?: any, config?: AxiosRequestConfig): Promise<T> { |
128 | | - const response: AxiosResponse<T> = await this.client.patch(path, data, config); |
129 | | - return response.data; |
| 63 | +}; |
| 64 | + |
| 65 | +// Vanilla Fetch without Auth for Login |
| 66 | +const fetchWithoutAuth = async (url: string, method: string = "POST", body: BodyInit | null = null) => { |
| 67 | + const headers: Record<string, string> = { |
| 68 | + 'Content-Type': 'application/json', |
| 69 | + }; |
| 70 | + |
| 71 | + const options: RequestInit = { |
| 72 | + method, |
| 73 | + headers, |
| 74 | + body: body ? JSON.stringify(body) : undefined, |
| 75 | + }; |
| 76 | + |
| 77 | + try { |
| 78 | + const response = await fetch(`${api}${url}`, options); |
| 79 | + |
| 80 | + if (!response.ok) { |
| 81 | + const errorText = await response.text(); |
| 82 | + throw new Error(errorText || 'Login failed'); |
| 83 | + } |
| 84 | + |
| 85 | + const isJson = response.headers.get('content-type')?.includes('application/json'); |
| 86 | + return isJson ? await response.json() : null; |
| 87 | + } catch (error) { |
| 88 | + console.error('Login Error:', (error as Error).message); |
| 89 | + throw error; |
130 | 90 | } |
131 | | -} |
132 | | - |
133 | | -// Create and export a default API client instance |
134 | | -const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://127.0.0.1:8000/api'; |
135 | | -export const apiClient = new ApiClient(API_BASE_URL); |
| 91 | +}; |
| 92 | + |
| 93 | +// Authenticated requests (with token) and login (without token) |
| 94 | +export const apiClient = { |
| 95 | + get: (url: string, config?: { params?: Record<string, any> }) => { |
| 96 | + const finalUrl = buildUrl(url, config?.params); |
| 97 | + return fetchWithAuth(finalUrl, 'GET'); |
| 98 | + }, |
| 99 | + post: (url: string, body?: any) => fetchWithAuth(url, 'POST', body), |
| 100 | + put: (url: string, body?: any) => fetchWithAuth(url, 'PUT', body), |
| 101 | + delete: (url: string) => fetchWithAuth(url, 'DELETE'), |
| 102 | + upload: (url: string, formData: FormData) => fetchWithAuth(url, 'POST', formData), |
| 103 | + login: (url: string, body?: any) => fetchWithoutAuth(url, 'POST', body), // For login without auth |
| 104 | +}; |
0 commit comments