0.5.8-panel-upgrade-community-crud-fix
All checks were successful
Deploy on push / deploy (push) Successful in 6s

This commit is contained in:
2025-06-30 21:25:26 +03:00
parent 9de86c0fae
commit 952b294345
70 changed files with 11345 additions and 2655 deletions

139
panel/graphql/index.ts Normal file
View File

@@ -0,0 +1,139 @@
/**
* API-клиент для работы с GraphQL
* @module api
*/
import {
AUTH_TOKEN_KEY,
clearAuthTokens,
getAuthTokenFromCookie,
getCsrfTokenFromCookie
} from '../utils/auth'
/**
* Тип для произвольных данных GraphQL
*/
type GraphQLData = Record<string, unknown>
/**
* Возвращает заголовки для GraphQL запроса с учетом авторизации и CSRF
* @returns Объект с заголовками
*/
function getRequestHeaders(): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Accept: '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) {
headers['Authorization'] = `Bearer ${token}`
console.debug('Отправка запроса с токеном авторизации')
}
// Добавляем CSRF-токен, если он есть
const csrfToken = getCsrfTokenFromCookie()
if (csrfToken) {
headers['X-CSRF-Token'] = csrfToken
console.debug('Добавлен CSRF-токен в запрос')
}
return headers
}
/**
* Выполняет GraphQL запрос
* @param endpoint - URL эндпоинта GraphQL
* @param query - GraphQL запрос
* @param variables - Переменные запроса
* @returns Результат запроса
*/
export async function query<T = unknown>(
endpoint: string,
query: string,
variables?: Record<string, unknown>
): Promise<T> {
try {
console.log(`[GraphQL] Making request to ${endpoint}`)
console.log(`[GraphQL] Query: ${query.substring(0, 100)}...`)
const response = await fetch(endpoint, {
method: 'POST',
headers: getRequestHeaders(),
credentials: 'include',
body: JSON.stringify({
query,
variables
})
})
console.log(`[GraphQL] Response status: ${response.status}`)
if (!response.ok) {
if (response.status === 401) {
console.log('[GraphQL] Unauthorized response, clearing auth tokens')
clearAuthTokens()
// Перенаправляем на страницу входа только если мы не на ней
if (!window.location.pathname.includes('/login')) {
window.location.href = '/login'
}
}
const errorText = await response.text()
throw new Error(`HTTP error: ${response.status} ${errorText}`)
}
const result = await response.json()
console.log('[GraphQL] Response received:', result)
if (result.errors) {
// Проверяем ошибки авторизации
const hasUnauthorized = result.errors.some(
(error: { message?: string }) =>
error.message?.toLowerCase().includes('unauthorized') ||
error.message?.toLowerCase().includes('please login')
)
if (hasUnauthorized) {
console.log('[GraphQL] Unauthorized error in response, clearing auth tokens')
clearAuthTokens()
// Перенаправляем на страницу входа только если мы не на ней
if (!window.location.pathname.includes('/login')) {
window.location.href = '/login'
}
}
// Handle GraphQL errors
const errorMessage = result.errors.map((e: { message?: string }) => e.message).join(', ')
throw new Error(`GraphQL error: ${errorMessage}`)
}
return result.data
} catch (error) {
console.error('[GraphQL] Query error:', error)
throw error
}
}
/**
* Выполняет GraphQL мутацию
* @param url - URL для запроса
* @param mutation - GraphQL мутация
* @param variables - Переменные мутации
* @returns Результат мутации
*/
export function mutate<T = GraphQLData>(
url: string,
mutation: string,
variables: Record<string, unknown> = {}
): Promise<T> {
return query<T>(url, mutation, variables)
}

View File

@@ -0,0 +1,63 @@
export const ADMIN_LOGIN_MUTATION = `
mutation AdminLogin($email: String!, $password: String!) {
login(email: $email, password: $password) {
success
token
}
}
`
export const ADMIN_LOGOUT_MUTATION = `
mutation AdminLogout {
logout {
success
}
}
`
export const ADMIN_UPDATE_USER_MUTATION = `
mutation AdminUpdateUser($user: AdminUserUpdateInput!) {
adminUpdateUser(user: $user) {
success
error
}
}
`
export const ADMIN_UPDATE_ENV_VARIABLE_MUTATION = `
mutation AdminUpdateEnvVariable($key: String!, $value: String!) {
updateEnvVariable(key: $key, value: $value)
}
`
export const UPDATE_TOPIC_MUTATION = `
mutation UpdateTopic($topic_input: TopicInput!) {
update_topic(topic_input: $topic_input) {
error
}
}
`
export const DELETE_TOPIC_MUTATION = `
mutation DeleteTopic($id: Int!) {
delete_topic_by_id(id: $id) {
error
}
}
`
export const UPDATE_COMMUNITY_MUTATION = `
mutation UpdateCommunity($community_input: CommunityInput!) {
update_community(community_input: $community_input) {
error
}
}
`
export const DELETE_COMMUNITY_MUTATION = `
mutation DeleteCommunity($slug: String!) {
delete_community(slug: $slug) {
error
}
}
`

156
panel/graphql/queries.ts Normal file
View File

@@ -0,0 +1,156 @@
import { gql } from 'graphql-tag'
// Определяем GraphQL запрос
export const ADMIN_GET_SHOUTS_QUERY: string =
gql`
query AdminGetShouts($limit: Int, $offset: Int, $search: String, $status: String) {
adminGetShouts(limit: $limit, offset: $offset, search: $search, status: $status) {
shouts {
id
title
slug
body
lead
subtitle
layout
lang
cover
cover_caption
media {
url
title
body
source
pic
date
genre
artist
lyrics
}
seo
created_at
updated_at
published_at
featured_at
deleted_at
created_by {
id
email
name
}
authors {
id
name
email
}
topics {
id
title
slug
}
stat {
rating
comments_count
viewed
}
}
total
page
perPage
totalPages
}
}
`.loc?.source.body || ''
export const ADMIN_GET_USERS_QUERY: string =
gql`
query AdminGetUsers($limit: Int, $offset: Int, $search: String) {
adminGetUsers(limit: $limit, offset: $offset, search: $search) {
authors {
id
email
name
slug
roles
created_at
last_seen
}
total
page
perPage
totalPages
}
}
`.loc?.source.body || ''
export const ADMIN_GET_ROLES_QUERY: string =
gql`
query AdminGetRoles {
adminGetRoles {
id
name
description
}
}
`.loc?.source.body || ''
export const ADMIN_GET_ENV_VARIABLES_QUERY: string =
gql`
query GetEnvVariables {
getEnvVariables {
name
description
variables {
key
value
description
type
isSecret
}
}
}
`.loc?.source.body || ''
export const GET_COMMUNITIES_QUERY: string =
gql`
query GetCommunities {
get_communities_all {
id
slug
name
desc
pic
created_at
created_by {
id
name
email
}
stat {
shouts
followers
authors
}
}
}
`.loc?.source.body || ''
export const GET_TOPICS_QUERY: string =
gql`
query GetTopics {
get_topics_all {
id
slug
title
body
pic
community
parent_ids
stat {
shouts
authors
followers
}
}
}
`.loc?.source.body || ''