tests-passed
This commit is contained in:
@@ -1,50 +1,22 @@
|
||||
from typing import Any
|
||||
|
||||
from graphql import GraphQLResolveInfo
|
||||
from sqlalchemy import distinct, func
|
||||
|
||||
from auth.orm import Author
|
||||
from orm.community import Community, CommunityFollower
|
||||
from auth.permissions import ContextualPermissionCheck
|
||||
from orm.community import Community, CommunityAuthor, CommunityFollower
|
||||
from orm.shout import Shout, ShoutAuthor
|
||||
from services.db import local_session
|
||||
from services.rbac import require_any_permission, require_permission
|
||||
from services.schema import mutation, query, type_community
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
|
||||
@query.field("get_communities_all")
|
||||
async def get_communities_all(_: None, _info: GraphQLResolveInfo) -> list[Community]:
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
with local_session() as session:
|
||||
# Загружаем сообщества с проверкой существования авторов
|
||||
communities = (
|
||||
session.query(Community)
|
||||
.options(joinedload(Community.created_by_author))
|
||||
.join(
|
||||
Author,
|
||||
Community.created_by == Author.id, # INNER JOIN - исключает сообщества без авторов
|
||||
)
|
||||
.filter(
|
||||
Community.created_by.isnot(None), # Дополнительная проверка
|
||||
Author.id.isnot(None), # Проверяем что автор существует
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Дополнительная проверка валидности данных
|
||||
valid_communities = []
|
||||
for community in communities:
|
||||
if (
|
||||
community.created_by
|
||||
and hasattr(community, "created_by_author")
|
||||
and community.created_by_author
|
||||
and community.created_by_author.id
|
||||
):
|
||||
valid_communities.append(community)
|
||||
else:
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
logger.warning(f"Исключено сообщество {community.id} ({community.slug}) - проблемы с автором")
|
||||
|
||||
return valid_communities
|
||||
return session.query(Community).all()
|
||||
|
||||
|
||||
@query.field("get_community")
|
||||
@@ -60,13 +32,17 @@ async def get_communities_by_author(
|
||||
with local_session() as session:
|
||||
q = session.query(Community).join(CommunityFollower)
|
||||
if slug:
|
||||
author_id = session.query(Author).where(Author.slug == slug).first().id
|
||||
q = q.where(CommunityFollower.author == author_id)
|
||||
author = session.query(Author).where(Author.slug == slug).first()
|
||||
if author:
|
||||
author_id = author.id
|
||||
q = q.where(CommunityFollower.follower == author_id)
|
||||
if user:
|
||||
author_id = session.query(Author).where(Author.id == user).first().id
|
||||
q = q.where(CommunityFollower.author == author_id)
|
||||
author = session.query(Author).where(Author.id == user).first()
|
||||
if author:
|
||||
author_id = author.id
|
||||
q = q.where(CommunityFollower.follower == author_id)
|
||||
if author_id:
|
||||
q = q.where(CommunityFollower.author == author_id)
|
||||
q = q.where(CommunityFollower.follower == author_id)
|
||||
return q.all()
|
||||
return []
|
||||
|
||||
@@ -76,11 +52,14 @@ async def get_communities_by_author(
|
||||
async def join_community(_: None, info: GraphQLResolveInfo, slug: str) -> dict[str, Any]:
|
||||
author_dict = info.context.get("author", {})
|
||||
author_id = author_dict.get("id")
|
||||
if not author_id:
|
||||
return {"ok": False, "error": "Unauthorized"}
|
||||
|
||||
with local_session() as session:
|
||||
community = session.query(Community).where(Community.slug == slug).first()
|
||||
if not community:
|
||||
return {"ok": False, "error": "Community not found"}
|
||||
session.add(CommunityFollower(community=community.id, follower=author_id))
|
||||
session.add(CommunityFollower(community=community.id, follower=int(author_id)))
|
||||
session.commit()
|
||||
return {"ok": True}
|
||||
|
||||
@@ -91,7 +70,7 @@ async def leave_community(_: None, info: GraphQLResolveInfo, slug: str) -> dict[
|
||||
author_id = author_dict.get("id")
|
||||
with local_session() as session:
|
||||
session.query(CommunityFollower).where(
|
||||
CommunityFollower.author == author_id, CommunityFollower.community == slug
|
||||
CommunityFollower.follower == author_id, CommunityFollower.community == slug
|
||||
).delete()
|
||||
session.commit()
|
||||
return {"ok": True}
|
||||
@@ -161,14 +140,20 @@ async def update_community(_: None, info: GraphQLResolveInfo, community_input: d
|
||||
try:
|
||||
with local_session() as session:
|
||||
# Находим сообщество для обновления
|
||||
community = session.query(Community).filter(Community.slug == slug).first()
|
||||
community = session.query(Community).where(Community.slug == slug).first()
|
||||
if not community:
|
||||
return {"error": "Сообщество не найдено"}
|
||||
|
||||
# Проверяем права на редактирование (создатель или админ/редактор)
|
||||
with local_session() as auth_session:
|
||||
author = auth_session.query(Author).filter(Author.id == author_id).first()
|
||||
user_roles = [role.id for role in author.roles] if author and author.roles else []
|
||||
# Получаем роли пользователя в сообществе
|
||||
community_author = (
|
||||
auth_session.query(CommunityAuthor)
|
||||
.where(CommunityAuthor.author_id == author_id, CommunityAuthor.community_id == community.id)
|
||||
.first()
|
||||
)
|
||||
|
||||
user_roles = community_author.role_list if community_author else []
|
||||
|
||||
# Разрешаем редактирование если пользователь - создатель или имеет роль admin/editor
|
||||
if community.created_by != author_id and "admin" not in user_roles and "editor" not in user_roles:
|
||||
@@ -188,81 +173,51 @@ async def update_community(_: None, info: GraphQLResolveInfo, community_input: d
|
||||
|
||||
@mutation.field("delete_community")
|
||||
@require_any_permission(["community:delete_own", "community:delete_any"])
|
||||
async def delete_community(_: None, info: GraphQLResolveInfo, slug: str) -> dict[str, Any]:
|
||||
# Получаем author_id из контекста через декоратор авторизации
|
||||
request = info.context.get("request")
|
||||
author_id = None
|
||||
|
||||
if hasattr(request, "auth") and request.auth and hasattr(request.auth, "author_id"):
|
||||
author_id = request.auth.author_id
|
||||
elif hasattr(request, "scope") and "auth" in request.scope:
|
||||
auth_info = request.scope.get("auth", {})
|
||||
if isinstance(auth_info, dict):
|
||||
author_id = auth_info.get("author_id")
|
||||
elif hasattr(auth_info, "author_id"):
|
||||
author_id = auth_info.author_id
|
||||
|
||||
if not author_id:
|
||||
return {"error": "Не удалось определить автора"}
|
||||
|
||||
async def delete_community(root, info, slug: str) -> dict[str, Any]:
|
||||
try:
|
||||
# Используем local_session как контекстный менеджер
|
||||
with local_session() as session:
|
||||
# Находим сообщество для удаления
|
||||
community = session.query(Community).filter(Community.slug == slug).first()
|
||||
# Находим сообщество по slug
|
||||
community = session.query(Community).where(Community.slug == slug).first()
|
||||
|
||||
if not community:
|
||||
return {"error": "Сообщество не найдено"}
|
||||
return {"error": "Сообщество не найдено", "success": False}
|
||||
|
||||
# Проверяем права на удаление (создатель или админ/редактор)
|
||||
with local_session() as auth_session:
|
||||
author = auth_session.query(Author).filter(Author.id == author_id).first()
|
||||
user_roles = [role.id for role in author.roles] if author and author.roles else []
|
||||
# Проверяем права на удаление
|
||||
user_id = info.context.get("user_id", 0)
|
||||
permission_check = ContextualPermissionCheck()
|
||||
|
||||
# Разрешаем удаление если пользователь - создатель или имеет роль admin/editor
|
||||
if community.created_by != author_id and "admin" not in user_roles and "editor" not in user_roles:
|
||||
return {"error": "Недостаточно прав для удаления этого сообщества"}
|
||||
# Проверяем права на удаление сообщества
|
||||
if not await permission_check.can_delete_community(user_id, community, session):
|
||||
return {"error": "Недостаточно прав", "success": False}
|
||||
|
||||
# Удаляем сообщество
|
||||
session.delete(community)
|
||||
session.commit()
|
||||
return {"error": None}
|
||||
|
||||
return {"success": True, "error": None}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": f"Ошибка удаления сообщества: {e!s}"}
|
||||
|
||||
|
||||
@type_community.field("created_by")
|
||||
def resolve_community_created_by(obj: Community, *_: Any) -> Author:
|
||||
"""
|
||||
Резолвер поля created_by для Community.
|
||||
Возвращает автора, создавшего сообщество.
|
||||
"""
|
||||
# Если связь уже загружена через joinedload и валидна
|
||||
if hasattr(obj, "created_by_author") and obj.created_by_author and obj.created_by_author.id:
|
||||
return obj.created_by_author
|
||||
|
||||
# Критическая ошибка - это не должно происходить после фильтрации в get_communities_all
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
logger.error(f"КРИТИЧЕСКАЯ ОШИБКА: Резолвер created_by вызван для сообщества {obj.id} без валидного автора")
|
||||
error_message = f"Сообщество {obj.id} не имеет валидного создателя"
|
||||
raise ValueError(error_message)
|
||||
# Логируем ошибку
|
||||
logger.error(f"Ошибка удаления сообщества: {e}")
|
||||
return {"error": str(e), "success": False}
|
||||
|
||||
|
||||
@type_community.field("stat")
|
||||
def resolve_community_stat(obj: Community, *_: Any) -> dict[str, int]:
|
||||
def resolve_community_stat(community: Community | dict[str, Any], *_: Any) -> dict[str, int]:
|
||||
"""
|
||||
Резолвер поля stat для Community.
|
||||
Возвращает статистику сообщества: количество публикаций, подписчиков и авторов.
|
||||
"""
|
||||
from sqlalchemy import distinct, func
|
||||
|
||||
from orm.shout import Shout, ShoutAuthor
|
||||
community_id = community.get("id") if isinstance(community, dict) else community.id
|
||||
|
||||
try:
|
||||
with local_session() as session:
|
||||
# Количество опубликованных публикаций в сообществе
|
||||
shouts_count = (
|
||||
session.query(func.count(Shout.id))
|
||||
.filter(Shout.community == obj.id, Shout.published_at.is_not(None), Shout.deleted_at.is_(None))
|
||||
.where(Shout.community == community_id, Shout.published_at.is_not(None), Shout.deleted_at.is_(None))
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
@@ -270,7 +225,7 @@ def resolve_community_stat(obj: Community, *_: Any) -> dict[str, int]:
|
||||
# Количество подписчиков сообщества
|
||||
followers_count = (
|
||||
session.query(func.count(CommunityFollower.follower))
|
||||
.filter(CommunityFollower.community == obj.id)
|
||||
.where(CommunityFollower.community == community_id)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
@@ -279,7 +234,7 @@ def resolve_community_stat(obj: Community, *_: Any) -> dict[str, int]:
|
||||
authors_count = (
|
||||
session.query(func.count(distinct(ShoutAuthor.author)))
|
||||
.join(Shout, ShoutAuthor.shout == Shout.id)
|
||||
.filter(Shout.community == obj.id, Shout.published_at.is_not(None), Shout.deleted_at.is_(None))
|
||||
.where(Shout.community == community_id, Shout.published_at.is_not(None), Shout.deleted_at.is_(None))
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
@@ -287,8 +242,6 @@ def resolve_community_stat(obj: Community, *_: Any) -> dict[str, int]:
|
||||
return {"shouts": int(shouts_count), "followers": int(followers_count), "authors": int(authors_count)}
|
||||
|
||||
except Exception as e:
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
logger.error(f"Ошибка при получении статистики сообщества {obj.id}: {e}")
|
||||
logger.error(f"Ошибка при получении статистики сообщества {community_id}: {e}")
|
||||
# Возвращаем нулевую статистику при ошибке
|
||||
return {"shouts": 0, "followers": 0, "authors": 0}
|
||||
|
Reference in New Issue
Block a user