feat: первое подключение фронтенда
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 8s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 54s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 27s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 8s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 54s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 27s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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<string, unknown>) {
|
||||
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<string, unknown>) {
|
||||
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<T>(
|
||||
path: string,
|
||||
options: RequestInit & { query?: Record<string, unknown> } = {},
|
||||
): Promise<T> {
|
||||
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)
|
||||
: `API request failed with status ${response.status}`
|
||||
throw new ApiError(message, response.status, body)
|
||||
}
|
||||
|
||||
return body as T
|
||||
}
|
||||
|
||||
export function extractItems<T>(payload: T[] | { items?: T[] } | undefined): T[] {
|
||||
if (Array.isArray(payload)) return payload
|
||||
return payload?.items ?? []
|
||||
}
|
||||
Reference in New Issue
Block a user