finnow/web-app/src/api/base.ts

129 lines
3.5 KiB
TypeScript

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<T>(path: string): Promise<T> {
const r = await this.doRequest('GET', path)
return await r.json()
}
protected async getJsonPage<T>(
path: string,
paginationOptions: PageRequest | undefined = undefined,
): Promise<Page<T>> {
let p = path
if (paginationOptions !== undefined) {
p += '?' + toQueryParams(paginationOptions)
}
return this.getJson(p)
}
protected async getText(path: string): Promise<string> {
const r = await this.doRequest('GET', path)
return await r.text()
}
protected async postJson<T>(path: string, body: object | undefined = undefined): Promise<T> {
const r = await this.doRequest('POST', path, body)
return await r.json()
}
protected async postText(path: string, body: object | undefined = undefined): Promise<string> {
const r = await this.doRequest('POST', path, body)
return await r.text()
}
protected async postNoResponse(
path: string,
body: object | undefined = undefined,
): Promise<void> {
await this.doRequest('POST', path, body)
}
protected async delete(path: string): Promise<void> {
await this.doRequest('DELETE', path)
}
protected async putJson<T>(path: string, body: object | undefined = undefined): Promise<T> {
const r = await this.doRequest('PUT', path, body)
return await r.json()
}
async getApiStatus(): Promise<boolean> {
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<Response> {
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)
}
}
}