2025-07-02 21:20:10 +00:00
|
|
|
|
"""
|
|
|
|
|
Админ-резолверы - тонкие GraphQL обёртки над AdminService
|
|
|
|
|
"""
|
|
|
|
|
|
2025-06-01 23:56:11 +00:00
|
|
|
|
from typing import Any
|
2025-05-29 09:37:39 +00:00
|
|
|
|
|
2025-06-01 23:56:11 +00:00
|
|
|
|
from graphql import GraphQLResolveInfo
|
2025-05-19 08:25:41 +00:00
|
|
|
|
from graphql.error import GraphQLError
|
|
|
|
|
|
|
|
|
|
from auth.decorators import admin_auth_required
|
2025-07-02 21:20:10 +00:00
|
|
|
|
from services.admin import admin_service
|
2025-05-29 09:37:39 +00:00
|
|
|
|
from services.schema import mutation, query
|
2025-05-19 08:25:41 +00:00
|
|
|
|
from utils.logger import root_logger as logger
|
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
def handle_error(operation: str, error: Exception) -> GraphQLError:
|
|
|
|
|
"""Обрабатывает ошибки в резолверах"""
|
|
|
|
|
logger.error(f"Ошибка при {operation}: {error}")
|
|
|
|
|
return GraphQLError(f"Не удалось {operation}: {error}")
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
# === ПОЛЬЗОВАТЕЛИ ===
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
2025-05-19 08:25:41 +00:00
|
|
|
|
|
|
|
|
|
@query.field("adminGetUsers")
|
|
|
|
|
@admin_auth_required
|
2025-06-01 23:56:11 +00:00
|
|
|
|
async def admin_get_users(
|
2025-07-02 19:30:21 +00:00
|
|
|
|
_: None, _info: GraphQLResolveInfo, limit: int = 20, offset: int = 0, search: str = ""
|
2025-06-01 23:56:11 +00:00
|
|
|
|
) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Получает список пользователей"""
|
2025-05-20 22:34:02 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return admin_service.get_users(limit, offset, search)
|
2025-05-20 22:34:02 +00:00
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
raise handle_error("получении списка пользователей", e) from e
|
2025-05-20 22:34:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("adminUpdateUser")
|
|
|
|
|
@admin_auth_required
|
2025-07-02 21:20:10 +00:00
|
|
|
|
async def admin_update_user(_: None, _info: GraphQLResolveInfo, user: dict[str, Any]) -> dict[str, Any]:
|
|
|
|
|
"""Обновляет данные пользователя"""
|
2025-05-20 22:34:02 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return admin_service.update_user(user)
|
2025-05-20 22:34:02 +00:00
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
logger.error(f"Ошибка обновления пользователя: {e}")
|
|
|
|
|
return {"success": False, "error": str(e)}
|
2025-06-28 10:47:08 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
# === ПУБЛИКАЦИИ ===
|
2025-06-28 10:47:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@query.field("adminGetShouts")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_get_shouts(
|
2025-07-02 19:30:21 +00:00
|
|
|
|
_: None,
|
2025-07-02 21:20:10 +00:00
|
|
|
|
_info: GraphQLResolveInfo,
|
2025-07-02 19:30:21 +00:00
|
|
|
|
limit: int = 20,
|
|
|
|
|
offset: int = 0,
|
|
|
|
|
search: str = "",
|
|
|
|
|
status: str = "all",
|
|
|
|
|
community: int = None,
|
2025-06-28 10:47:08 +00:00
|
|
|
|
) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Получает список публикаций"""
|
2025-06-28 10:47:08 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return admin_service.get_shouts(limit, offset, search, status, community)
|
2025-06-28 10:47:08 +00:00
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
raise handle_error("получении списка публикаций", e) from e
|
2025-06-28 10:47:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("adminUpdateShout")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_update_shout(_: None, info: GraphQLResolveInfo, shout: dict[str, Any]) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Обновляет публикацию через editor.py"""
|
2025-06-28 10:47:08 +00:00
|
|
|
|
try:
|
|
|
|
|
from resolvers.editor import update_shout
|
|
|
|
|
|
|
|
|
|
shout_id = shout.get("id")
|
|
|
|
|
if not shout_id:
|
|
|
|
|
return {"success": False, "error": "ID публикации не указан"}
|
|
|
|
|
|
|
|
|
|
shout_input = {k: v for k, v in shout.items() if k != "id"}
|
|
|
|
|
result = await update_shout(None, info, shout_id, shout_input)
|
|
|
|
|
|
|
|
|
|
if result.error:
|
|
|
|
|
return {"success": False, "error": result.error}
|
|
|
|
|
|
|
|
|
|
logger.info(f"Публикация {shout_id} обновлена через админ-панель")
|
|
|
|
|
return {"success": True}
|
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
logger.error(f"Ошибка обновления публикации: {e}")
|
|
|
|
|
return {"success": False, "error": str(e)}
|
2025-06-28 10:47:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("adminDeleteShout")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_delete_shout(_: None, info: GraphQLResolveInfo, shout_id: int) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Удаляет публикацию через editor.py"""
|
2025-06-28 10:47:08 +00:00
|
|
|
|
try:
|
|
|
|
|
from resolvers.editor import delete_shout
|
|
|
|
|
|
|
|
|
|
result = await delete_shout(None, info, shout_id)
|
|
|
|
|
if result.error:
|
|
|
|
|
return {"success": False, "error": result.error}
|
|
|
|
|
|
|
|
|
|
logger.info(f"Публикация {shout_id} удалена через админ-панель")
|
|
|
|
|
return {"success": True}
|
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
logger.error(f"Ошибка удаления публикации: {e}")
|
|
|
|
|
return {"success": False, "error": str(e)}
|
2025-06-28 10:47:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("adminRestoreShout")
|
|
|
|
|
@admin_auth_required
|
2025-07-02 21:20:10 +00:00
|
|
|
|
async def admin_restore_shout(_: None, _info: GraphQLResolveInfo, shout_id: int) -> dict[str, Any]:
|
|
|
|
|
"""Восстанавливает удаленную публикацию"""
|
2025-06-28 10:47:08 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return admin_service.restore_shout(shout_id)
|
2025-06-28 10:47:08 +00:00
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
logger.error(f"Ошибка восстановления публикации: {e}")
|
|
|
|
|
return {"success": False, "error": str(e)}
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
# === ПРИГЛАШЕНИЯ ===
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@query.field("adminGetInvites")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_get_invites(
|
2025-07-02 19:30:21 +00:00
|
|
|
|
_: None, _info: GraphQLResolveInfo, limit: int = 20, offset: int = 0, search: str = "", status: str = "all"
|
2025-06-30 19:19:46 +00:00
|
|
|
|
) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Получает список приглашений"""
|
2025-06-30 19:19:46 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return admin_service.get_invites(limit, offset, search, status)
|
2025-06-30 19:19:46 +00:00
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
raise handle_error("получении списка приглашений", e) from e
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("adminUpdateInvite")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_update_invite(_: None, _info: GraphQLResolveInfo, invite: dict[str, Any]) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Обновляет приглашение"""
|
2025-06-30 19:19:46 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return admin_service.update_invite(invite)
|
2025-06-30 19:19:46 +00:00
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
logger.error(f"Ошибка обновления приглашения: {e}")
|
|
|
|
|
return {"success": False, "error": str(e)}
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mutation.field("adminDeleteInvite")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_delete_invite(
|
|
|
|
|
_: None, _info: GraphQLResolveInfo, inviter_id: int, author_id: int, shout_id: int
|
|
|
|
|
) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Удаляет приглашение"""
|
2025-06-30 19:19:46 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return admin_service.delete_invite(inviter_id, author_id, shout_id)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Ошибка удаления приглашения: {e}")
|
|
|
|
|
return {"success": False, "error": str(e)}
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
# === ТОПИКИ ===
|
|
|
|
|
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
@query.field("adminGetTopics")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_get_topics(_: None, _info: GraphQLResolveInfo, community_id: int) -> list[dict[str, Any]]:
|
|
|
|
|
"""Получает все топики сообщества для админ-панели"""
|
|
|
|
|
try:
|
|
|
|
|
from orm.topic import Topic
|
|
|
|
|
from services.db import local_session
|
|
|
|
|
|
|
|
|
|
with local_session() as session:
|
|
|
|
|
# Получаем все топики сообщества без лимитов
|
|
|
|
|
topics = session.query(Topic).filter(Topic.community == community_id).order_by(Topic.id).all()
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
# Сериализуем топики в простой формат для админки
|
|
|
|
|
result: list[dict[str, Any]] = [
|
|
|
|
|
{
|
|
|
|
|
"id": topic.id,
|
|
|
|
|
"title": topic.title or "",
|
|
|
|
|
"slug": topic.slug or f"topic-{topic.id}",
|
|
|
|
|
"body": topic.body or "",
|
|
|
|
|
"community": topic.community,
|
|
|
|
|
"parent_ids": topic.parent_ids or [],
|
|
|
|
|
"pic": topic.pic,
|
|
|
|
|
"oid": getattr(topic, "oid", None),
|
|
|
|
|
"is_main": getattr(topic, "is_main", False),
|
|
|
|
|
}
|
|
|
|
|
for topic in topics
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
logger.info("Загружено топиков для сообщества", len(result))
|
|
|
|
|
return result
|
2025-06-30 19:19:46 +00:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
raise handle_error("получении списка топиков", e) from e
|
2025-06-30 20:37:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
# === ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ ===
|
2025-06-30 20:37:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
@query.field("getEnvVariables")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def get_env_variables(_: None, _info: GraphQLResolveInfo) -> list[dict[str, Any]]:
|
|
|
|
|
"""Получает переменные окружения"""
|
2025-06-30 20:37:21 +00:00
|
|
|
|
try:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
return await admin_service.get_env_variables()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Ошибка получения переменных окружения", e)
|
|
|
|
|
raise GraphQLError("Не удалось получить переменные окружения", e) from e
|
2025-06-30 20:37:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
@mutation.field("updateEnvVariable")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def update_env_variable(_: None, _info: GraphQLResolveInfo, key: str, value: str) -> dict[str, Any]:
|
|
|
|
|
"""Обновляет переменную окружения"""
|
|
|
|
|
return await admin_service.update_env_variable(key, value)
|
2025-06-30 20:37:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
@mutation.field("updateEnvVariables")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def update_env_variables(_: None, _info: GraphQLResolveInfo, variables: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
|
|
"""Массовое обновление переменных окружения"""
|
|
|
|
|
return await admin_service.update_env_variables(variables)
|
2025-06-30 20:37:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
# === РОЛИ ===
|
2025-06-30 20:37:21 +00:00
|
|
|
|
|
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
@query.field("adminGetRoles")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_get_roles(_: None, _info: GraphQLResolveInfo, community: int = None) -> list[dict[str, Any]]:
|
|
|
|
|
"""Получает список ролей"""
|
|
|
|
|
try:
|
|
|
|
|
return admin_service.get_roles(community)
|
2025-06-30 20:37:21 +00:00
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
logger.error("Ошибка получения ролей", e)
|
|
|
|
|
raise GraphQLError("Не удалось получить роли", e) from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# === ЗАГЛУШКИ ДЛЯ ОСТАЛЬНЫХ РЕЗОЛВЕРОВ ===
|
|
|
|
|
# [предположение] Эти резолверы пока оставляем как есть, но их тоже нужно будет упростить
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@query.field("adminGetUserCommunityRoles")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_get_user_community_roles(
|
2025-07-02 21:20:10 +00:00
|
|
|
|
_: None, _info: GraphQLResolveInfo, author_id: int, community_id: int
|
2025-07-02 19:30:21 +00:00
|
|
|
|
) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Получает роли пользователя в сообществе"""
|
|
|
|
|
# [непроверенное] Временная заглушка - нужно вынести в сервис
|
|
|
|
|
from orm.community import CommunityAuthor
|
|
|
|
|
from services.db import local_session
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with local_session() as session:
|
|
|
|
|
community_author = (
|
|
|
|
|
session.query(CommunityAuthor)
|
|
|
|
|
.filter(CommunityAuthor.author_id == author_id, CommunityAuthor.community_id == community_id)
|
|
|
|
|
.first()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
roles = []
|
|
|
|
|
if community_author and community_author.roles:
|
|
|
|
|
roles = [role.strip() for role in community_author.roles.split(",") if role.strip()]
|
|
|
|
|
|
|
|
|
|
return {"author_id": author_id, "community_id": community_id, "roles": roles}
|
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
raise handle_error("получении ролей пользователя в сообществе", e) from e
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@query.field("adminGetCommunityMembers")
|
|
|
|
|
@admin_auth_required
|
|
|
|
|
async def admin_get_community_members(
|
2025-07-02 21:20:10 +00:00
|
|
|
|
_: None, _info: GraphQLResolveInfo, community_id: int, limit: int = 20, offset: int = 0
|
2025-07-02 19:30:21 +00:00
|
|
|
|
) -> dict[str, Any]:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
"""Получает участников сообщества"""
|
|
|
|
|
# [непроверенное] Временная заглушка - нужно вынести в сервис
|
|
|
|
|
from sqlalchemy.sql import func
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
2025-07-02 21:20:10 +00:00
|
|
|
|
from auth.orm import Author
|
|
|
|
|
from orm.community import CommunityAuthor
|
|
|
|
|
from services.db import local_session
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with local_session() as session:
|
|
|
|
|
members_query = (
|
|
|
|
|
session.query(Author, CommunityAuthor)
|
|
|
|
|
.join(CommunityAuthor, Author.id == CommunityAuthor.author_id)
|
|
|
|
|
.filter(CommunityAuthor.community_id == community_id)
|
|
|
|
|
.offset(offset)
|
|
|
|
|
.limit(limit)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
members = []
|
|
|
|
|
for author, community_author in members_query:
|
|
|
|
|
roles = []
|
|
|
|
|
if community_author.roles:
|
|
|
|
|
roles = [role.strip() for role in community_author.roles.split(",") if role.strip()]
|
|
|
|
|
|
|
|
|
|
members.append(
|
|
|
|
|
{
|
|
|
|
|
"id": author.id,
|
|
|
|
|
"name": author.name,
|
|
|
|
|
"email": author.email,
|
|
|
|
|
"slug": author.slug,
|
|
|
|
|
"roles": roles,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
total = (
|
|
|
|
|
session.query(func.count(CommunityAuthor.author_id))
|
|
|
|
|
.filter(CommunityAuthor.community_id == community_id)
|
|
|
|
|
.scalar()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return {"members": members, "total": total, "community_id": community_id}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Ошибка получения участников сообщества: {e}")
|
|
|
|
|
return {"members": [], "total": 0, "community_id": community_id}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@query.field("adminGetCommunityRoleSettings")
|
|
|
|
|
@admin_auth_required
|
2025-07-02 21:20:10 +00:00
|
|
|
|
async def admin_get_community_role_settings(_: None, _info: GraphQLResolveInfo, community_id: int) -> dict[str, Any]:
|
|
|
|
|
"""Получает настройки ролей сообщества"""
|
|
|
|
|
# [непроверенное] Временная заглушка - нужно вынести в сервис
|
|
|
|
|
from orm.community import Community
|
|
|
|
|
from services.db import local_session
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with local_session() as session:
|
|
|
|
|
community = session.query(Community).filter(Community.id == community_id).first()
|
|
|
|
|
if not community:
|
|
|
|
|
return {
|
|
|
|
|
"community_id": community_id,
|
|
|
|
|
"default_roles": ["reader"],
|
|
|
|
|
"available_roles": ["reader", "author", "artist", "expert", "editor", "admin"],
|
|
|
|
|
"error": "Сообщество не найдено",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"community_id": community_id,
|
|
|
|
|
"default_roles": community.get_default_roles(),
|
|
|
|
|
"available_roles": community.get_available_roles(),
|
|
|
|
|
"error": None,
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
2025-07-02 21:20:10 +00:00
|
|
|
|
logger.error(f"Ошибка получения настроек ролей: {e}")
|
2025-07-02 19:30:21 +00:00
|
|
|
|
return {
|
|
|
|
|
"community_id": community_id,
|
|
|
|
|
"default_roles": ["reader"],
|
|
|
|
|
"available_roles": ["reader", "author", "artist", "expert", "editor", "admin"],
|
|
|
|
|
"error": str(e),
|
|
|
|
|
}
|