const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || '/api').replace(/\/$/, '') const API_PREFIX = '/v1' let accessToken: string | null = null export class ApiError extends Error { constructor( message: string, public status: number, public details?: unknown, ) { super(message) this.name = 'ApiError' } } export function setApiAccessToken(token: string | null) { accessToken = token } export function getApiAccessToken() { return accessToken } function makeUrl(path: string, query?: Record) { const normalizedPath = path.startsWith('/') ? path : `/${path}` const url = new URL(`${API_BASE_URL}${API_PREFIX}${normalizedPath}`, window.location.origin) Object.entries(query ?? {}).forEach(([key, value]) => { if (value === undefined || value === null || value === '') return url.searchParams.set(key, String(value)) }) return url.toString() } export function buildApiUrl(path: string, query?: Record) { return makeUrl(path, query) } async function parseResponse(response: Response) { if (response.status === 204) return undefined const contentType = response.headers.get('content-type') ?? '' if (contentType.includes('application/json')) return response.json() const text = await response.text() return text || undefined } export async function apiRequest( path: string, options: RequestInit & { query?: Record } = {}, ): Promise { const headers = new Headers(options.headers) if (!headers.has('Accept')) headers.set('Accept', 'application/json') if (options.body && !headers.has('Content-Type')) headers.set('Content-Type', 'application/json') if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`) const response = await fetch(makeUrl(path, options.query), { ...options, headers, credentials: 'include', }) const body = await parseResponse(response) if (!response.ok) { const message = typeof body === 'object' && body && 'message' in body ? String((body as { message: unknown }).message) : typeof body === 'object' && body && 'detail' in body ? String((body as { detail: unknown }).detail) : `API request failed with status ${response.status}` throw new ApiError(message, response.status, body) } return body as T } export function extractItems(payload: T[] | { items?: T[] } | undefined): T[] { if (Array.isArray(payload)) return payload return payload?.items ?? [] }