- добавлен новый Nest backend для auth, projects и project access tokens - добавлена control-plane схема БД и миграция Drizzle - перенесён старый backend в old-backend - добавлен React/Vite cabinet с auth-only входом и Mantine layout - обновлены workspace scripts и lockfile
78 lines
1.9 KiB
TypeScript
78 lines
1.9 KiB
TypeScript
import { getAdminToken } from './token-storage'
|
|
import type { AdminSessionResponseDto, LoginRequestDto, LoginResponseDto } from './types/backend-api.type'
|
|
|
|
type RequestOptions = {
|
|
body?: unknown
|
|
isAuthorized?: boolean
|
|
method?: 'GET' | 'POST'
|
|
}
|
|
|
|
const API_BASE_URL = import.meta.env.VITE_BACKEND_API_BASE_URL ?? '/api'
|
|
|
|
export const backendApi = {
|
|
auth: {
|
|
login: (body: LoginRequestDto) => {
|
|
return request<LoginResponseDto>('/auth/login', { body, method: 'POST' })
|
|
},
|
|
me: () => {
|
|
return request<AdminSessionResponseDto>('/auth/me', { isAuthorized: true })
|
|
},
|
|
},
|
|
}
|
|
|
|
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
const response = await fetch(`${API_BASE_URL}${path}`, {
|
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
headers: buildHeaders(options),
|
|
method: options.method ?? 'GET',
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(await readErrorMessage(response))
|
|
}
|
|
|
|
return response.json() as Promise<T>
|
|
}
|
|
|
|
function buildHeaders(options: RequestOptions) {
|
|
const headers: Record<string, string> = {
|
|
Accept: 'application/json',
|
|
}
|
|
|
|
if (options.body) {
|
|
headers['Content-Type'] = 'application/json'
|
|
}
|
|
|
|
if (options.isAuthorized) {
|
|
const token = getAdminToken()
|
|
|
|
if (token) {
|
|
headers.Authorization = `Bearer ${token}`
|
|
}
|
|
}
|
|
|
|
return headers
|
|
}
|
|
|
|
async function readErrorMessage(response: Response) {
|
|
try {
|
|
const value = (await response.json()) as unknown
|
|
|
|
if (isRecord(value) && typeof value.message === 'string') {
|
|
return value.message
|
|
}
|
|
|
|
if (isRecord(value) && Array.isArray(value.message)) {
|
|
return value.message.join(', ')
|
|
}
|
|
} catch {
|
|
return `request failed with status ${response.status}`
|
|
}
|
|
|
|
return `request failed with status ${response.status}`
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
}
|