core/resolvers/admin.py
Untone c8728540ed
All checks were successful
Deploy on push / deploy (push) Successful in 7s
reactions-admin-tab
2025-07-04 12:39:41 +03:00

851 lines
36 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Админ-резолверы - тонкие GraphQL обёртки над AdminService
"""
from typing import Any
from graphql import GraphQLResolveInfo
from graphql.error import GraphQLError
from auth.decorators import admin_auth_required
from services.admin import admin_service
from services.schema import mutation, query
from utils.logger import root_logger as logger
def handle_error(operation: str, error: Exception) -> GraphQLError:
"""Обрабатывает ошибки в резолверах"""
logger.error(f"Ошибка при {operation}: {error}")
return GraphQLError(f"Не удалось {operation}: {error}")
# === ПОЛЬЗОВАТЕЛИ ===
@query.field("adminGetUsers")
@admin_auth_required
async def admin_get_users(
_: None, _info: GraphQLResolveInfo, limit: int = 20, offset: int = 0, search: str = ""
) -> dict[str, Any]:
"""Получает список пользователей"""
try:
return admin_service.get_users(limit, offset, search)
except Exception as e:
raise handle_error("получении списка пользователей", e) from e
@mutation.field("adminUpdateUser")
@admin_auth_required
async def admin_update_user(_: None, _info: GraphQLResolveInfo, user: dict[str, Any]) -> dict[str, Any]:
"""Обновляет данные пользователя"""
try:
return admin_service.update_user(user)
except Exception as e:
logger.error(f"Ошибка обновления пользователя: {e}")
return {"success": False, "error": str(e)}
# === ПУБЛИКАЦИИ ===
@query.field("adminGetShouts")
@admin_auth_required
async def admin_get_shouts(
_: None,
_info: GraphQLResolveInfo,
limit: int = 20,
offset: int = 0,
search: str = "",
status: str = "all",
community: int = None,
) -> dict[str, Any]:
"""Получает список публикаций"""
try:
return admin_service.get_shouts(limit, offset, search, status, community)
except Exception as e:
raise handle_error("получении списка публикаций", e) from e
@mutation.field("adminUpdateShout")
@admin_auth_required
async def admin_update_shout(_: None, info: GraphQLResolveInfo, shout: dict[str, Any]) -> dict[str, Any]:
"""Обновляет публикацию через editor.py"""
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:
logger.error(f"Ошибка обновления публикации: {e}")
return {"success": False, "error": str(e)}
@mutation.field("adminDeleteShout")
@admin_auth_required
async def admin_delete_shout(_: None, info: GraphQLResolveInfo, shout_id: int) -> dict[str, Any]:
"""Удаляет публикацию через editor.py"""
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:
logger.error(f"Ошибка удаления публикации: {e}")
return {"success": False, "error": str(e)}
@mutation.field("adminRestoreShout")
@admin_auth_required
async def admin_restore_shout(_: None, _info: GraphQLResolveInfo, shout_id: int) -> dict[str, Any]:
"""Восстанавливает удаленную публикацию"""
try:
return admin_service.restore_shout(shout_id)
except Exception as e:
logger.error(f"Ошибка восстановления публикации: {e}")
return {"success": False, "error": str(e)}
# === ПРИГЛАШЕНИЯ ===
@query.field("adminGetInvites")
@admin_auth_required
async def admin_get_invites(
_: None, _info: GraphQLResolveInfo, limit: int = 20, offset: int = 0, search: str = "", status: str = "all"
) -> dict[str, Any]:
"""Получает список приглашений"""
try:
return admin_service.get_invites(limit, offset, search, status)
except Exception as e:
raise handle_error("получении списка приглашений", e) from e
@mutation.field("adminUpdateInvite")
@admin_auth_required
async def admin_update_invite(_: None, _info: GraphQLResolveInfo, invite: dict[str, Any]) -> dict[str, Any]:
"""Обновляет приглашение"""
try:
return admin_service.update_invite(invite)
except Exception as e:
logger.error(f"Ошибка обновления приглашения: {e}")
return {"success": False, "error": str(e)}
@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]:
"""Удаляет приглашение"""
try:
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)}
# === ТОПИКИ ===
@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()
# Сериализуем топики в простой формат для админки
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(f"Загружено топиков для сообщества: {len(result)}")
return result
except Exception as e:
raise handle_error("получении списка топиков", e) from e
@mutation.field("adminUpdateTopic")
@admin_auth_required
async def admin_update_topic(_: None, _info: GraphQLResolveInfo, topic: dict[str, Any]) -> dict[str, Any]:
"""Обновляет топик через админ-панель"""
try:
from orm.topic import Topic
from resolvers.topic import invalidate_topics_cache
from services.db import local_session
from services.redis import redis
topic_id = topic.get("id")
if not topic_id:
return {"success": False, "error": "ID топика не указан"}
with local_session() as session:
existing_topic = session.query(Topic).filter(Topic.id == topic_id).first()
if not existing_topic:
return {"success": False, "error": "Топик не найден"}
# Сохраняем старый slug для удаления из кеша
old_slug = str(getattr(existing_topic, "slug", ""))
# Обновляем поля топика
for key, value in topic.items():
if key != "id" and hasattr(existing_topic, key):
setattr(existing_topic, key, value)
session.add(existing_topic)
session.commit()
# Инвалидируем кеш
await invalidate_topics_cache(topic_id)
# Если slug изменился, удаляем старый ключ
new_slug = str(getattr(existing_topic, "slug", ""))
if old_slug != new_slug:
await redis.execute("DEL", f"topic:slug:{old_slug}")
logger.debug(f"Удален ключ кеша для старого slug: {old_slug}")
logger.info(f"Топик {topic_id} обновлен через админ-панель")
return {"success": True, "topic": existing_topic}
except Exception as e:
logger.error(f"Ошибка обновления топика: {e}")
return {"success": False, "error": str(e)}
@mutation.field("adminCreateTopic")
@admin_auth_required
async def admin_create_topic(_: None, _info: GraphQLResolveInfo, topic: dict[str, Any]) -> dict[str, Any]:
"""Создает новый топик через админ-панель"""
try:
from orm.topic import Topic
from resolvers.topic import invalidate_topics_cache
from services.db import local_session
with local_session() as session:
# Создаем новый топик
new_topic = Topic(**topic)
session.add(new_topic)
session.commit()
# Инвалидируем кеш всех тем
await invalidate_topics_cache()
logger.info(f"Топик {new_topic.id} создан через админ-панель")
return {"success": True, "topic": new_topic}
except Exception as e:
logger.error(f"Ошибка создания топика: {e}")
return {"success": False, "error": str(e)}
@mutation.field("adminMergeTopics")
@admin_auth_required
async def admin_merge_topics(_: None, _info: GraphQLResolveInfo, merge_input: dict[str, Any]) -> dict[str, Any]:
"""
Административное слияние топиков с переносом всех публикаций и подписчиков
Args:
merge_input: Данные для слияния:
- target_topic_id: ID целевой темы (в которую сливаем)
- source_topic_ids: Список ID исходных тем (которые сливаем)
- preserve_target_properties: Сохранить свойства целевой темы
Returns:
dict: Результат операции с информацией о слиянии
"""
try:
from orm.draft import DraftTopic
from orm.shout import ShoutTopic
from orm.topic import Topic, TopicFollower
from resolvers.topic import invalidate_topic_followers_cache, invalidate_topics_cache
from services.db import local_session
from services.redis import redis
target_topic_id = merge_input["target_topic_id"]
source_topic_ids = merge_input["source_topic_ids"]
preserve_target = merge_input.get("preserve_target_properties", True)
# Проверяем что ID не пересекаются
if target_topic_id in source_topic_ids:
return {"success": False, "error": "Целевая тема не может быть в списке исходных тем"}
with local_session() as session:
# Получаем целевую тему
target_topic = session.query(Topic).filter(Topic.id == target_topic_id).first()
if not target_topic:
return {"success": False, "error": f"Целевая тема с ID {target_topic_id} не найдена"}
# Получаем исходные темы
source_topics = session.query(Topic).filter(Topic.id.in_(source_topic_ids)).all()
if len(source_topics) != len(source_topic_ids):
found_ids = [t.id for t in source_topics]
missing_ids = [topic_id for topic_id in source_topic_ids if topic_id not in found_ids]
return {"success": False, "error": f"Исходные темы с ID {missing_ids} не найдены"}
# Проверяем что все темы принадлежат одному сообществу
target_community = target_topic.community
for source_topic in source_topics:
if source_topic.community != target_community:
return {"success": False, "error": f"Тема '{source_topic.title}' принадлежит другому сообществу"}
# Собираем статистику для отчета
merge_stats = {"followers_moved": 0, "publications_moved": 0, "drafts_moved": 0, "source_topics_deleted": 0}
# Переносим подписчиков из исходных тем в целевую
for source_topic in source_topics:
# Получаем подписчиков исходной темы
source_followers = session.query(TopicFollower).filter(TopicFollower.topic == source_topic.id).all()
for follower in source_followers:
# Проверяем, не подписан ли уже пользователь на целевую тему
existing = (
session.query(TopicFollower)
.filter(TopicFollower.topic == target_topic_id, TopicFollower.follower == follower.follower)
.first()
)
if not existing:
# Создаем новую подписку на целевую тему
new_follower = TopicFollower(
topic=target_topic_id,
follower=follower.follower,
created_at=follower.created_at,
auto=follower.auto,
)
session.add(new_follower)
merge_stats["followers_moved"] += 1
# Удаляем старую подписку
session.delete(follower)
# Переносим публикации из исходных тем в целевую
for source_topic in source_topics:
# Получаем связи публикаций с исходной темой
shout_topics = session.query(ShoutTopic).filter(ShoutTopic.topic == source_topic.id).all()
for shout_topic in shout_topics:
# Проверяем, не связана ли уже публикация с целевой темой
existing = (
session.query(ShoutTopic)
.filter(ShoutTopic.topic == target_topic_id, ShoutTopic.shout == shout_topic.shout)
.first()
)
if not existing:
# Создаем новую связь с целевой темой
new_shout_topic = ShoutTopic(
topic=target_topic_id, shout=shout_topic.shout, main=shout_topic.main
)
session.add(new_shout_topic)
merge_stats["publications_moved"] += 1
# Удаляем старую связь
session.delete(shout_topic)
# Переносим черновики из исходных тем в целевую
for source_topic in source_topics:
# Получаем связи черновиков с исходной темой
draft_topics = session.query(DraftTopic).filter(DraftTopic.topic == source_topic.id).all()
for draft_topic in draft_topics:
# Проверяем, не связан ли уже черновик с целевой темой
existing = (
session.query(DraftTopic)
.filter(DraftTopic.topic == target_topic_id, DraftTopic.shout == draft_topic.shout)
.first()
)
if not existing:
# Создаем новую связь с целевой темой
new_draft_topic = DraftTopic(
topic=target_topic_id, shout=draft_topic.shout, main=draft_topic.main
)
session.add(new_draft_topic)
merge_stats["drafts_moved"] += 1
# Удаляем старую связь
session.delete(draft_topic)
# Обновляем parent_ids дочерних топиков
for source_topic in source_topics:
# Находим всех детей исходной темы
child_topics = session.query(Topic).filter(Topic.parent_ids.contains(int(source_topic.id))).all() # type: ignore[arg-type]
for child_topic in child_topics:
current_parent_ids = list(child_topic.parent_ids or [])
# Заменяем ID исходной темы на ID целевой темы
updated_parent_ids = [
target_topic_id if parent_id == source_topic.id else parent_id
for parent_id in current_parent_ids
]
child_topic.parent_ids = updated_parent_ids
# Объединяем parent_ids если не сохраняем только целевые свойства
if not preserve_target:
current_parent_ids = list(target_topic.parent_ids or [])
all_parent_ids = set(current_parent_ids)
for source_topic in source_topics:
source_parent_ids = list(source_topic.parent_ids or [])
if source_parent_ids:
all_parent_ids.update(source_parent_ids)
# Убираем IDs исходных тем из parent_ids
all_parent_ids.discard(target_topic_id)
for source_id in source_topic_ids:
all_parent_ids.discard(source_id)
target_topic.parent_ids = list(all_parent_ids) if all_parent_ids else []
# Инвалидируем кеши ПЕРЕД удалением тем
for source_topic in source_topics:
await invalidate_topic_followers_cache(int(source_topic.id))
if source_topic.slug:
await redis.execute("DEL", f"topic:slug:{source_topic.slug}")
await redis.execute("DEL", f"topic:id:{source_topic.id}")
# Удаляем исходные темы
for source_topic in source_topics:
session.delete(source_topic)
merge_stats["source_topics_deleted"] += 1
logger.info(f"Удалена исходная тема: {source_topic.title} (ID: {source_topic.id})")
# Сохраняем изменения
session.commit()
# Инвалидируем кеши целевой темы и общие кеши
await invalidate_topics_cache(target_topic_id)
await invalidate_topic_followers_cache(target_topic_id)
logger.info(f"Успешно слиты темы {source_topic_ids} в тему {target_topic_id} через админ-панель")
logger.info(f"Статистика слияния: {merge_stats}")
return {
"success": True,
"topic": target_topic,
"message": f"Успешно слито {len(source_topics)} тем в '{target_topic.title}'",
"stats": merge_stats,
}
except Exception as e:
logger.error(f"Ошибка при слиянии тем через админ-панель: {e}")
return {"success": False, "error": f"Ошибка при слиянии тем: {e}"}
# === ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ ===
@query.field("getEnvVariables")
@admin_auth_required
async def get_env_variables(_: None, _info: GraphQLResolveInfo) -> list[dict[str, Any]]:
"""Получает переменные окружения"""
try:
return await admin_service.get_env_variables()
except Exception as e:
logger.error(f"Ошибка получения переменных окружения: {e}")
raise GraphQLError("Не удалось получить переменные окружения") from e
@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)
@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)
# === РОЛИ ===
@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)
except Exception as e:
logger.error(f"Ошибка получения ролей: {e}")
raise GraphQLError("Не удалось получить роли") from e
# === ЗАГЛУШКИ ДЛЯ ОСТАЛЬНЫХ РЕЗОЛВЕРОВ ===
# [предположение] Эти резолверы пока оставляем как есть, но их тоже нужно будет упростить
@query.field("adminGetUserCommunityRoles")
@admin_auth_required
async def admin_get_user_community_roles(
_: None, _info: GraphQLResolveInfo, author_id: int, community_id: int
) -> dict[str, Any]:
"""Получает роли пользователя в сообществе"""
# [непроверенное] Временная заглушка - нужно вынести в сервис
from orm.community import CommunityAuthor
from services.db import local_session
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:
raise handle_error("получении ролей пользователя в сообществе", e) from e
@query.field("adminGetCommunityMembers")
@admin_auth_required
async def admin_get_community_members(
_: None, _info: GraphQLResolveInfo, community_id: int, limit: int = 20, offset: int = 0
) -> dict[str, Any]:
"""Получает участников сообщества"""
# [непроверенное] Временная заглушка - нужно вынести в сервис
from sqlalchemy.sql import func
from auth.orm import Author
from orm.community import CommunityAuthor
from services.db import local_session
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
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
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:
logger.error(f"Ошибка получения настроек ролей: {e}")
return {
"community_id": community_id,
"default_roles": ["reader"],
"available_roles": ["reader", "author", "artist", "expert", "editor", "admin"],
"error": str(e),
}
# === РЕАКЦИИ ===
@query.field("adminGetReactions")
@admin_auth_required
async def admin_get_reactions(
_: None,
_info: GraphQLResolveInfo,
limit: int = 20,
offset: int = 0,
search: str = "",
kind: str = None,
shout_id: int = None,
status: str = "all",
) -> dict[str, Any]:
"""Получает список реакций для админ-панели"""
try:
from sqlalchemy import and_, case, func, or_
from sqlalchemy.orm import aliased
from auth.orm import Author
from orm.reaction import Reaction
from orm.shout import Shout
from services.db import local_session
with local_session() as session:
# Базовый запрос с джойнами
query = (
session.query(Reaction, Author, Shout)
.join(Author, Reaction.created_by == Author.id)
.join(Shout, Reaction.shout == Shout.id)
)
# Фильтрация
filters = []
# Фильтр по статусу (как в публикациях)
if status == "active":
filters.append(Reaction.deleted_at.is_(None))
elif status == "deleted":
filters.append(Reaction.deleted_at.isnot(None))
# Если status == "all", не добавляем фильтр - показываем все
if search:
filters.append(
or_(
Reaction.body.ilike(f"%{search}%"),
Author.name.ilike(f"%{search}%"),
Author.email.ilike(f"%{search}%"),
Shout.title.ilike(f"%{search}%"),
)
)
if kind:
filters.append(Reaction.kind == kind)
if shout_id:
filters.append(Reaction.shout == shout_id)
if filters:
query = query.filter(and_(*filters))
# Общее количество
total = query.count()
# Получаем реакции с пагинацией
reactions_data = query.order_by(Reaction.created_at.desc()).offset(offset).limit(limit).all()
# Формируем результат
reactions = []
for reaction, author, shout in reactions_data:
# Получаем статистику для каждой реакции
aliased_reaction = aliased(Reaction)
stats = (
session.query(
func.count(aliased_reaction.id.distinct()).label("comments_count"),
func.sum(
case(
(aliased_reaction.kind == "LIKE", 1), (aliased_reaction.kind == "DISLIKE", -1), else_=0
)
).label("rating"),
)
.filter(
aliased_reaction.reply_to == reaction.id,
# Убираем фильтр deleted_at чтобы включить все реакции в статистику
)
.first()
)
reactions.append(
{
"id": reaction.id,
"kind": reaction.kind,
"body": reaction.body or "",
"created_at": reaction.created_at,
"updated_at": reaction.updated_at,
"deleted_at": reaction.deleted_at,
"reply_to": reaction.reply_to,
"created_by": {
"id": author.id,
"name": author.name,
"email": author.email,
"slug": author.slug,
},
"shout": {
"id": shout.id,
"title": shout.title,
"slug": shout.slug,
"layout": shout.layout,
"created_at": shout.created_at,
"published_at": shout.published_at,
"deleted_at": shout.deleted_at,
},
"stat": {
"comments_count": stats.comments_count or 0,
"rating": stats.rating or 0,
},
}
)
# Расчет пагинации
per_page = limit
total_pages = (total + per_page - 1) // per_page
page = (offset // per_page) + 1
logger.info(f"Загружено реакций для админ-панели: {len(reactions)}")
return {
"reactions": reactions,
"total": total,
"page": page,
"perPage": per_page,
"totalPages": total_pages,
}
except Exception as e:
raise handle_error("получении списка реакций", e) from e
@mutation.field("adminUpdateReaction")
@admin_auth_required
async def admin_update_reaction(_: None, _info: GraphQLResolveInfo, reaction: dict[str, Any]) -> dict[str, Any]:
"""Обновляет реакцию"""
try:
import time
from orm.reaction import Reaction
from services.db import local_session
reaction_id = reaction.get("id")
if not reaction_id:
return {"success": False, "error": "ID реакции не указан"}
with local_session() as session:
# Находим реакцию
db_reaction = session.query(Reaction).filter(Reaction.id == reaction_id).first()
if not db_reaction:
return {"success": False, "error": "Реакция не найдена"}
# Обновляем поля
if "body" in reaction:
db_reaction.body = reaction["body"]
if "deleted_at" in reaction:
db_reaction.deleted_at = reaction["deleted_at"]
# Обновляем время изменения
db_reaction.updated_at = int(time.time())
session.commit()
logger.info(f"Реакция {reaction_id} обновлена через админ-панель")
return {"success": True}
except Exception as e:
logger.error(f"Ошибка обновления реакции: {e}")
return {"success": False, "error": str(e)}
@mutation.field("adminDeleteReaction")
@admin_auth_required
async def admin_delete_reaction(_: None, _info: GraphQLResolveInfo, reaction_id: int) -> dict[str, Any]:
"""Удаляет реакцию (мягкое удаление)"""
try:
import time
from orm.reaction import Reaction
from services.db import local_session
with local_session() as session:
# Находим реакцию
db_reaction = session.query(Reaction).filter(Reaction.id == reaction_id).first()
if not db_reaction:
return {"success": False, "error": "Реакция не найдена"}
# Устанавливаем время удаления
db_reaction.deleted_at = int(time.time())
session.commit()
logger.info(f"Реакция {reaction_id} удалена через админ-панель")
return {"success": True}
except Exception as e:
logger.error(f"Ошибка удаления реакции: {e}")
return {"success": False, "error": str(e)}
@mutation.field("adminRestoreReaction")
@admin_auth_required
async def admin_restore_reaction(_: None, _info: GraphQLResolveInfo, reaction_id: int) -> dict[str, Any]:
"""Восстанавливает удаленную реакцию"""
try:
from orm.reaction import Reaction
from services.db import local_session
with local_session() as session:
# Находим реакцию
db_reaction = session.query(Reaction).filter(Reaction.id == reaction_id).first()
if not db_reaction:
return {"success": False, "error": "Реакция не найдена"}
# Убираем время удаления
db_reaction.deleted_at = None
session.commit()
logger.info(f"Реакция {reaction_id} восстановлена через админ-панель")
return {"success": True}
except Exception as e:
logger.error(f"Ошибка восстановления реакции: {e}")
return {"success": False, "error": str(e)}