upgrade schema, resolvers, panel added
This commit is contained in:
189
panel/graphql.ts
Normal file
189
panel/graphql.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* API-клиент для работы с GraphQL
|
||||
* @module api
|
||||
*/
|
||||
|
||||
/**
|
||||
* Базовый URL для API
|
||||
*/
|
||||
// Всегда используем абсолютный путь к API
|
||||
const API_URL = window.location.origin + '/graphql'
|
||||
|
||||
/**
|
||||
* Константа для имени ключа токена в localStorage
|
||||
*/
|
||||
const AUTH_TOKEN_KEY = 'auth_token'
|
||||
|
||||
/**
|
||||
* Тип для произвольных данных GraphQL
|
||||
*/
|
||||
type GraphQLData = Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Получает токен авторизации из cookie
|
||||
* @returns Токен или пустую строку, если токен не найден
|
||||
*/
|
||||
function getAuthTokenFromCookie(): string {
|
||||
const cookieItems = document.cookie.split(';')
|
||||
for (const item of cookieItems) {
|
||||
const [name, value] = item.trim().split('=')
|
||||
if (name === 'auth_token') {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает ошибки от API
|
||||
* @param response - Ответ от сервера
|
||||
* @returns Обработанный текст ошибки
|
||||
*/
|
||||
async function handleApiError(response: Response): Promise<string> {
|
||||
try {
|
||||
const contentType = response.headers.get('content-type')
|
||||
|
||||
if (contentType?.includes('application/json')) {
|
||||
const errorData = await response.json()
|
||||
|
||||
// Проверяем GraphQL ошибки
|
||||
if (errorData.errors && errorData.errors.length > 0) {
|
||||
return errorData.errors[0].message
|
||||
}
|
||||
|
||||
// Проверяем сообщение об ошибке
|
||||
if (errorData.error || errorData.message) {
|
||||
return errorData.error || errorData.message
|
||||
}
|
||||
}
|
||||
|
||||
// Если не JSON или нет структурированной ошибки, читаем как текст
|
||||
const errorText = await response.text()
|
||||
return `Ошибка сервера: ${response.status} ${response.statusText}. ${errorText.substring(0, 100)}...`
|
||||
} catch (_e) {
|
||||
// Если не можем прочитать ответ
|
||||
return `Ошибка сервера: ${response.status} ${response.statusText}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет наличие ошибок авторизации в ответе GraphQL
|
||||
* @param errors - Массив ошибок GraphQL
|
||||
* @returns true если есть ошибки авторизации
|
||||
*/
|
||||
function hasAuthErrors(errors: Array<{ message?: string; extensions?: { code?: string } }>): boolean {
|
||||
return errors.some(
|
||||
(error) =>
|
||||
(error.message && (
|
||||
error.message.toLowerCase().includes('unauthorized') ||
|
||||
error.message.toLowerCase().includes('авторизации') ||
|
||||
error.message.toLowerCase().includes('authentication') ||
|
||||
error.message.toLowerCase().includes('unauthenticated') ||
|
||||
error.message.toLowerCase().includes('token')
|
||||
)) ||
|
||||
error.extensions?.code === 'UNAUTHENTICATED' ||
|
||||
error.extensions?.code === 'FORBIDDEN'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет GraphQL запрос
|
||||
* @param query - GraphQL запрос
|
||||
* @param variables - Переменные запроса
|
||||
* @returns Результат запроса
|
||||
*/
|
||||
export async function query<T = GraphQLData>(
|
||||
query: string,
|
||||
variables: Record<string, unknown> = {}
|
||||
): Promise<T> {
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
// Проверяем наличие токена в localStorage
|
||||
const localToken = localStorage.getItem(AUTH_TOKEN_KEY)
|
||||
|
||||
// Проверяем наличие токена в cookie
|
||||
const cookieToken = getAuthTokenFromCookie()
|
||||
|
||||
// Используем токен из localStorage или cookie
|
||||
const token = localToken || cookieToken
|
||||
|
||||
// Если есть токен, добавляем его в заголовок Authorization с префиксом Bearer
|
||||
if (token && token.length > 10) {
|
||||
// В соответствии с логами сервера, формат должен быть: Bearer <token>
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
// Для отладки
|
||||
console.debug('Отправка запроса с токеном авторизации')
|
||||
}
|
||||
|
||||
const response = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
// Важно: credentials: 'include' - для передачи cookies с запросом
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables
|
||||
})
|
||||
})
|
||||
|
||||
// Проверяем статус ответа
|
||||
if (!response.ok) {
|
||||
const errorMessage = await handleApiError(response)
|
||||
console.error('Ошибка API:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorMessage
|
||||
})
|
||||
|
||||
// Если получен 401 Unauthorized, перенаправляем на страницу входа
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem(AUTH_TOKEN_KEY)
|
||||
window.location.href = '/login'
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
// Проверяем, что ответ содержит JSON
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (!contentType?.includes('application/json')) {
|
||||
const text = await response.text()
|
||||
throw new Error(`Неверный формат ответа: ${text.substring(0, 100)}...`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.errors) {
|
||||
// Проверяем ошибки на признаки проблем с авторизацией
|
||||
if (hasAuthErrors(result.errors)) {
|
||||
localStorage.removeItem(AUTH_TOKEN_KEY)
|
||||
window.location.href = '/login'
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
|
||||
return result.data as T
|
||||
} catch (error) {
|
||||
console.error('API Error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет GraphQL мутацию
|
||||
* @param mutation - GraphQL мутация
|
||||
* @param variables - Переменные мутации
|
||||
* @returns Результат мутации
|
||||
*/
|
||||
export function mutate<T = GraphQLData>(
|
||||
mutation: string,
|
||||
variables: Record<string, unknown> = {}
|
||||
): Promise<T> {
|
||||
return query<T>(mutation, variables)
|
||||
}
|
Reference in New Issue
Block a user