import { useAuthStore } from '@/stores/auth-store' import { toQueryParams, type Page, type PageRequest } from './pagination' export abstract class ApiError { readonly message: string constructor(message: string) { this.message = message } } export class NetworkError extends ApiError {} export class StatusError extends ApiError { readonly status: number constructor(status: number, message: string) { super(message) this.status = status } } export abstract class ApiClient { private baseUrl: string = import.meta.env.VITE_API_BASE_URL protected async getJson(path: string): Promise { const r = await this.doRequest('GET', path) return await r.json() } protected async getJsonPage( path: string, paginationOptions: PageRequest | undefined = undefined, ): Promise> { let p = path if (paginationOptions !== undefined) { p += '?' + toQueryParams(paginationOptions) } return this.getJson(p) } protected async getText(path: string): Promise { const r = await this.doRequest('GET', path) return await r.text() } protected async postJson(path: string, body: object | undefined = undefined): Promise { const r = await this.doRequest('POST', path, body) return await r.json() } protected async postText(path: string, body: object | undefined = undefined): Promise { const r = await this.doRequest('POST', path, body) return await r.text() } protected async postNoResponse( path: string, body: object | undefined = undefined, ): Promise { await this.doRequest('POST', path, body) } protected async delete(path: string): Promise { await this.doRequest('DELETE', path) } protected async putJson(path: string, body: object | undefined = undefined): Promise { const r = await this.doRequest('PUT', path, body) return await r.json() } async getApiStatus(): Promise { try { await this.doRequest('GET', '/status') return true } catch { return false } } /** * Does a generic request, returning the response if successful, or throwing an ApiError if * any sort of error or non-OK status is returned. * @param method The HTTP method to use. * @param path The API path to request. * @param body The request body. * @returns A promise that resolves to an OK response. */ private async doRequest( method: string, path: string, body: object | undefined = undefined, ): Promise { const settings: RequestInit = { method } const headers: HeadersInit = {} if (body !== undefined && typeof body === 'object') { headers['Content-Type'] = 'application/json' settings.body = JSON.stringify(body) } const authStore = useAuthStore() if (authStore.state) { headers['Authorization'] = 'Bearer ' + authStore.state.token } settings.headers = headers try { const response = await fetch(this.baseUrl + path, settings) if (!response.ok) { const message = await response.text() throw new StatusError(response.status, message) } return response } catch (error) { if (error instanceof ApiError) throw error let message = 'Request to ' + path + ' failed.' if ( typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' ) { message = error.message } console.error(error) throw new NetworkError(message) } } }