|
|
import type { ApiRequestOptions, ApiError } from '../types/api.types.ts'; |
|
|
import { getUserId } from '../utils/index.ts'; |
|
|
|
|
|
const DEFAULT_HEADERS: HeadersInit = { |
|
|
'Content-Type': 'application/json', |
|
|
}; |
|
|
|
|
|
const BASE_URL = |
|
|
process.env.REACT_APP_API_BASE_URL?.replace(/\/$/, ''); |
|
|
|
|
|
export type { ApiRequestOptions, ApiError }; |
|
|
|
|
|
export async function apiRequest<TResponse = unknown, TBody = unknown>({ |
|
|
path, |
|
|
method = 'GET', |
|
|
body, |
|
|
headers, |
|
|
signal, |
|
|
}: ApiRequestOptions<TBody>): Promise<TResponse> { |
|
|
const url = `${BASE_URL}${path.startsWith('/') ? '' : '/'}${path}`; |
|
|
|
|
|
|
|
|
const userId = getUserId(); |
|
|
|
|
|
|
|
|
let baseHeaders: Record<string, string> = {}; |
|
|
if (headers instanceof Headers) { |
|
|
baseHeaders = Object.fromEntries(headers.entries()); |
|
|
} else if (headers) { |
|
|
baseHeaders = headers as Record<string, string>; |
|
|
} |
|
|
|
|
|
const requestHeaders: Record<string, string> = { |
|
|
...(DEFAULT_HEADERS as Record<string, string>), |
|
|
...baseHeaders, |
|
|
}; |
|
|
|
|
|
if (userId) { |
|
|
requestHeaders['X-User-ID'] = userId; |
|
|
} |
|
|
|
|
|
const response = await fetch(url, { |
|
|
method, |
|
|
headers: requestHeaders, |
|
|
body: body ? JSON.stringify(body) : undefined, |
|
|
signal, |
|
|
}); |
|
|
|
|
|
const contentType = response.headers.get('content-type'); |
|
|
const isJson = contentType?.includes('application/json'); |
|
|
const payload = isJson ? await response.json().catch(() => undefined) : undefined; |
|
|
|
|
|
if (!response.ok) { |
|
|
const error: ApiError = { |
|
|
status: response.status, |
|
|
message: |
|
|
(payload as { message?: string })?.message || |
|
|
response.statusText || |
|
|
'Request failed', |
|
|
details: payload, |
|
|
}; |
|
|
throw error; |
|
|
} |
|
|
|
|
|
return payload as TResponse; |
|
|
} |
|
|
|
|
|
export const api = { |
|
|
get: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) => |
|
|
apiRequest<TResponse>({ path, method: 'GET', ...init }), |
|
|
post: <TResponse, TBody = unknown>( |
|
|
path: string, |
|
|
body?: TBody, |
|
|
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>, |
|
|
) => apiRequest<TResponse, TBody>({ path, method: 'POST', body, ...init }), |
|
|
put: <TResponse, TBody = unknown>( |
|
|
path: string, |
|
|
body?: TBody, |
|
|
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>, |
|
|
) => apiRequest<TResponse, TBody>({ path, method: 'PUT', body, ...init }), |
|
|
patch: <TResponse, TBody = unknown>( |
|
|
path: string, |
|
|
body?: TBody, |
|
|
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>, |
|
|
) => apiRequest<TResponse, TBody>({ path, method: 'PATCH', body, ...init }), |
|
|
delete: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) => |
|
|
apiRequest<TResponse>({ path, method: 'DELETE', ...init }), |
|
|
}; |
|
|
|
|
|
export default api; |
|
|
|
|
|
|