core/resolvers/author.py

629 lines
30 KiB
Python
Raw Normal View History

2024-06-11 19:46:35 +00:00
import asyncio
2023-11-03 10:10:22 +00:00
import time
2025-06-26 14:19:42 +00:00
from typing import Any, Optional, TypedDict
2023-12-17 20:30:20 +00:00
from graphql import GraphQLResolveInfo
2025-03-22 08:47:19 +00:00
from sqlalchemy import select, text
2023-10-23 14:47:11 +00:00
2025-05-29 09:37:39 +00:00
from auth.orm import Author
2024-08-07 05:57:56 +00:00
from cache.cache import (
2024-05-20 22:40:57 +00:00
cache_author,
2025-03-22 08:47:19 +00:00
cached_query,
2024-05-20 22:40:57 +00:00
get_cached_author,
get_cached_author_followers,
2024-05-30 16:42:38 +00:00
get_cached_follower_authors,
get_cached_follower_topics,
2025-03-22 08:47:19 +00:00
invalidate_cache_by_prefix,
2024-05-20 22:40:57 +00:00
)
2024-08-12 08:00:01 +00:00
from resolvers.stat import get_with_stat
from services.auth import login_required
from services.common_result import CommonResult
2023-12-17 20:30:20 +00:00
from services.db import local_session
2025-03-22 08:47:19 +00:00
from services.redis import redis
2023-12-17 20:30:20 +00:00
from services.schema import mutation, query
2024-08-12 08:00:01 +00:00
from utils.logger import root_logger as logger
2024-01-13 08:49:12 +00:00
2025-03-22 08:47:19 +00:00
DEFAULT_COMMUNITIES = [1]
2025-06-26 14:19:42 +00:00
# Определение типа AuthorsBy на основе схемы GraphQL
class AuthorsBy(TypedDict, total=False):
"""
Тип для параметра сортировки авторов, соответствующий схеме GraphQL.
Поля:
last_seen: Временная метка последнего посещения
created_at: Временная метка создания
slug: Уникальный идентификатор автора
name: Имя автора
topic: Тема, связанная с автором
order: Поле для сортировки (shouts, followers, rating, comments, name)
after: Временная метка для фильтрации "после"
stat: Поле статистики
"""
last_seen: Optional[int]
created_at: Optional[int]
slug: Optional[str]
name: Optional[str]
topic: Optional[str]
order: Optional[str]
after: Optional[int]
stat: Optional[str]
2025-03-22 08:47:19 +00:00
# Вспомогательная функция для получения всех авторов без статистики
async def get_all_authors(current_user_id: Optional[int] = None) -> list[Any]:
2025-03-22 08:47:19 +00:00
"""
Получает всех авторов без статистики.
Используется для случаев, когда нужен полный список авторов без дополнительной информации.
2025-05-20 22:34:02 +00:00
Args:
current_user_id: ID текущего пользователя для проверки прав доступа
is_admin: Флаг, указывающий, является ли пользователь администратором
2025-03-22 08:47:19 +00:00
Returns:
list: Список всех авторов без статистики
"""
cache_key = "authors:all:basic"
# Функция для получения всех авторов из БД
async def fetch_all_authors() -> list[Any]:
"""
Выполняет запрос к базе данных для получения всех авторов.
"""
2025-03-22 08:47:19 +00:00
logger.debug("Получаем список всех авторов из БД и кешируем результат")
with local_session() as session:
# Запрос на получение базовой информации об авторах
authors_query = select(Author).where(Author.deleted_at.is_(None))
2025-05-30 05:51:24 +00:00
authors = session.execute(authors_query).scalars().unique().all()
2025-03-22 08:47:19 +00:00
2025-05-20 22:34:02 +00:00
# Преобразуем авторов в словари с учетом прав доступа
return [author.dict(False) for author in authors]
2025-03-22 08:47:19 +00:00
# Используем универсальную функцию для кеширования запросов
return await cached_query(cache_key, fetch_all_authors)
# Вспомогательная функция для получения авторов со статистикой с пагинацией
async def get_authors_with_stats(
2025-06-26 14:19:42 +00:00
limit: int = 10, offset: int = 0, by: Optional[AuthorsBy] = None, current_user_id: Optional[int] = None
):
2025-03-22 08:47:19 +00:00
"""
Получает авторов со статистикой с пагинацией.
Args:
limit: Максимальное количество возвращаемых авторов
offset: Смещение для пагинации
2025-06-26 14:19:42 +00:00
by: Опциональный параметр сортировки (AuthorsBy)
2025-05-20 22:34:02 +00:00
current_user_id: ID текущего пользователя
2025-03-22 08:47:19 +00:00
Returns:
list: Список авторов с их статистикой
"""
# Формируем ключ кеша с помощью универсальной функции
2025-06-26 14:19:42 +00:00
order_value = by.get("order", "default") if by else "default"
cache_key = f"authors:stats:limit={limit}:offset={offset}:order={order_value}"
2025-03-22 08:47:19 +00:00
# Функция для получения авторов из БД
async def fetch_authors_with_stats() -> list[Any]:
"""
Выполняет запрос к базе данных для получения авторов со статистикой.
"""
2025-05-29 09:37:39 +00:00
logger.debug(f"Выполняем запрос на получение авторов со статистикой: limit={limit}, offset={offset}, by={by}")
2025-03-22 08:47:19 +00:00
# Импорты SQLAlchemy для избежания конфликтов имен
from sqlalchemy import and_, asc, func
from sqlalchemy import desc as sql_desc
from auth.orm import AuthorFollower
from orm.shout import Shout, ShoutAuthor
2025-03-22 08:47:19 +00:00
with local_session() as session:
# Базовый запрос для получения авторов
base_query = select(Author).where(Author.deleted_at.is_(None))
# vars for statistics sorting
stats_sort_field = None
2025-06-26 14:19:42 +00:00
default_sort_applied = False
2025-05-29 09:37:39 +00:00
2025-03-22 08:47:19 +00:00
if by:
2025-06-26 14:19:42 +00:00
if "order" in by:
order_value = by["order"]
logger.debug(f"Found order field with value: {order_value}")
if order_value in ["shouts", "followers", "rating", "comments"]:
stats_sort_field = order_value
logger.debug(f"Applying statistics-based sorting by: {stats_sort_field}")
# Не применяем другую сортировку, так как будем использовать stats_sort_field
default_sort_applied = True
elif order_value == "name":
# Sorting by name in ascending order
base_query = base_query.order_by(asc(Author.name))
logger.debug("Applying alphabetical sorting by name")
default_sort_applied = True
else:
2025-06-26 14:19:42 +00:00
# If order is not a stats field, treat it as a regular field
2025-06-30 19:43:32 +00:00
column = getattr(Author, order_value or "", "")
2025-06-26 14:19:42 +00:00
if column:
base_query = base_query.order_by(sql_desc(column))
logger.debug(f"Applying sorting by column: {order_value}")
default_sort_applied = True
else:
logger.warning(f"Unknown order field: {order_value}")
2025-03-22 08:47:19 +00:00
else:
2025-06-26 14:19:42 +00:00
# Regular sorting by fields
for field, direction in by.items():
2025-06-30 19:43:32 +00:00
if field is None:
continue
2025-06-26 14:19:42 +00:00
column = getattr(Author, field, None)
if column:
2025-06-30 19:43:32 +00:00
if isinstance(direction, str) and direction.lower() == "desc":
2025-06-26 14:19:42 +00:00
base_query = base_query.order_by(sql_desc(column))
else:
base_query = base_query.order_by(column)
logger.debug(f"Applying sorting by field: {field}, direction: {direction}")
default_sort_applied = True
else:
logger.warning(f"Unknown field: {field}")
# Если сортировка еще не применена, используем сортировку по умолчанию
if not default_sort_applied and not stats_sort_field:
base_query = base_query.order_by(sql_desc(Author.created_at))
2025-06-26 14:19:42 +00:00
logger.debug("Applying default sorting by created_at (no by parameter)")
2025-03-22 08:47:19 +00:00
# If sorting by statistics, modify the query
if stats_sort_field == "shouts":
# Sorting by the number of shouts
2025-06-26 14:19:42 +00:00
logger.debug("Building subquery for shouts sorting")
subquery = (
2025-05-29 09:37:39 +00:00
select(ShoutAuthor.author, func.count(func.distinct(Shout.id)).label("shouts_count"))
.select_from(ShoutAuthor)
.join(Shout, ShoutAuthor.shout == Shout.id)
2025-05-29 09:37:39 +00:00
.where(and_(Shout.deleted_at.is_(None), Shout.published_at.is_not(None)))
.group_by(ShoutAuthor.author)
.subquery()
)
2025-05-29 09:37:39 +00:00
2025-06-26 14:19:42 +00:00
# Сбрасываем предыдущую сортировку и применяем новую
2025-05-29 09:37:39 +00:00
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.shouts_count, 0))
)
2025-06-26 14:19:42 +00:00
logger.debug("Applied sorting by shouts count")
# Логирование для отладки сортировки
try:
# Получаем SQL запрос для проверки
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
logger.debug(f"Generated SQL query for shouts sorting: {sql_query}")
except Exception as e:
logger.error(f"Error generating SQL query: {e}")
elif stats_sort_field == "followers":
# Sorting by the number of followers
2025-06-26 14:19:42 +00:00
logger.debug("Building subquery for followers sorting")
subquery = (
select(
AuthorFollower.author,
2025-05-29 09:37:39 +00:00
func.count(func.distinct(AuthorFollower.follower)).label("followers_count"),
)
.select_from(AuthorFollower)
.group_by(AuthorFollower.author)
.subquery()
)
2025-05-29 09:37:39 +00:00
2025-06-26 14:19:42 +00:00
# Сбрасываем предыдущую сортировку и применяем новую
2025-05-29 09:37:39 +00:00
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.followers_count, 0))
)
2025-06-26 14:19:42 +00:00
logger.debug("Applied sorting by followers count")
# Логирование для отладки сортировки
try:
# Получаем SQL запрос для проверки
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
logger.debug(f"Generated SQL query for followers sorting: {sql_query}")
except Exception as e:
logger.error(f"Error generating SQL query: {e}")
2025-03-22 08:47:19 +00:00
# Применяем лимит и смещение
base_query = base_query.limit(limit).offset(offset)
# Получаем авторов
2025-05-30 05:51:24 +00:00
authors = session.execute(base_query).scalars().unique().all()
2025-03-22 08:47:19 +00:00
author_ids = [author.id for author in authors]
if not author_ids:
return []
2025-06-26 14:19:42 +00:00
# Логирование результатов для отладки сортировки
if stats_sort_field:
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
2025-03-22 08:47:19 +00:00
# Оптимизированный запрос для получения статистики по публикациям для авторов
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
2025-03-22 08:47:19 +00:00
shouts_stats_query = f"""
SELECT sa.author, COUNT(DISTINCT s.id) as shouts_count
FROM shout_author sa
JOIN shout s ON sa.shout = s.id AND s.deleted_at IS NULL AND s.published_at IS NOT NULL
WHERE sa.author IN ({placeholders})
2025-03-22 08:47:19 +00:00
GROUP BY sa.author
"""
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
2025-03-22 08:47:19 +00:00
# Запрос на получение статистики по подписчикам для авторов
followers_stats_query = f"""
SELECT author, COUNT(DISTINCT follower) as followers_count
FROM author_follower
WHERE author IN ({placeholders})
2025-03-22 08:47:19 +00:00
GROUP BY author
"""
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query), params)}
2025-03-22 08:47:19 +00:00
# Формируем результат с добавлением статистики
result = []
for author in authors:
2025-05-20 22:34:02 +00:00
# Получаем словарь с учетом прав доступа
2025-03-22 08:47:19 +00:00
author_dict = author.dict()
author_dict["stat"] = {
"shouts": shouts_stats.get(author.id, 0),
"followers": followers_stats.get(author.id, 0),
}
2025-05-29 09:37:39 +00:00
2025-03-22 08:47:19 +00:00
result.append(author_dict)
# Кешируем каждого автора отдельно для использования в других функциях
2025-05-20 22:34:02 +00:00
# Важно: кэшируем полный словарь для админов
await cache_author(author.dict())
2025-03-22 08:47:19 +00:00
return result
# Используем универсальную функцию для кеширования запросов
return await cached_query(cache_key, fetch_authors_with_stats)
# Функция для инвалидации кеша авторов
async def invalidate_authors_cache(author_id=None) -> None:
2025-03-22 08:47:19 +00:00
"""
Инвалидирует кеши авторов при изменении данных.
Args:
author_id: Опциональный ID автора для точечной инвалидации.
Если не указан, инвалидируются все кеши авторов.
"""
if author_id:
# Точечная инвалидация конкретного автора
logger.debug(f"Инвалидация кеша для автора #{author_id}")
specific_keys = [
f"author:id:{author_id}",
f"author:followers:{author_id}",
f"author:follows-authors:{author_id}",
f"author:follows-topics:{author_id}",
f"author:follows-shouts:{author_id}",
]
2025-05-29 09:15:06 +00:00
# Получаем author_id автора, если есть
2025-03-22 08:47:19 +00:00
with local_session() as session:
author = session.query(Author).filter(Author.id == author_id).first()
2025-05-20 22:34:02 +00:00
if author and Author.id:
2025-05-29 09:15:06 +00:00
specific_keys.append(f"author:id:{Author.id}")
2025-03-22 08:47:19 +00:00
# Удаляем конкретные ключи
for key in specific_keys:
try:
await redis.execute("DEL", key)
logger.debug(f"Удален ключ кеша {key}")
except Exception as e:
logger.error(f"Ошибка при удалении ключа {key}: {e}")
# Также ищем и удаляем ключи коллекций, содержащих данные об этом авторе
collection_keys = await redis.execute("KEYS", "authors:stats:*")
if collection_keys:
await redis.execute("DEL", *collection_keys)
logger.debug(f"Удалено {len(collection_keys)} коллекционных ключей авторов")
else:
# Общая инвалидация всех кешей авторов
logger.debug("Полная инвалидация кеша авторов")
await invalidate_cache_by_prefix("authors")
2023-10-23 14:47:11 +00:00
2024-04-17 15:32:23 +00:00
@mutation.field("update_author")
2023-10-23 14:47:11 +00:00
@login_required
async def update_author(_: None, info: GraphQLResolveInfo, profile: dict[str, Any]) -> CommonResult:
"""Update author profile"""
2025-05-22 01:34:30 +00:00
author_id = info.context.get("author", {}).get("id")
2025-05-20 22:34:02 +00:00
is_admin = info.context.get("is_admin", False)
2025-05-22 01:34:30 +00:00
if not author_id:
return CommonResult(error="unauthorized", author=None)
2024-03-18 12:01:10 +00:00
try:
with local_session() as session:
2025-05-22 01:34:30 +00:00
author = session.query(Author).where(Author.id == author_id).first()
2024-03-18 12:01:10 +00:00
if author:
Author.update(author, profile)
session.add(author)
session.commit()
2025-05-22 01:34:30 +00:00
author_query = select(Author).where(Author.id == author_id)
2024-06-11 19:46:35 +00:00
result = get_with_stat(author_query)
if result:
author_with_stat = result[0]
if isinstance(author_with_stat, Author):
2025-05-20 22:34:02 +00:00
# Кэшируем полную версию для админов
author_dict = author_with_stat.dict(is_admin)
2025-06-26 14:19:42 +00:00
_t = asyncio.create_task(cache_author(author_dict))
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Возвращаем обычную полную версию, т.к. это владелец
return CommonResult(error=None, author=author)
# Если мы дошли до сюда, значит автор не найден
return CommonResult(error="Author not found", author=None)
2024-03-18 12:01:10 +00:00
except Exception as exc:
2024-03-18 12:01:43 +00:00
import traceback
logger.error(traceback.format_exc())
return CommonResult(error=str(exc), author=None)
2023-10-23 14:47:11 +00:00
2024-04-17 15:32:23 +00:00
@query.field("get_authors_all")
async def get_authors_all(_: None, info: GraphQLResolveInfo) -> list[Any]:
"""Get all authors"""
2025-05-20 22:34:02 +00:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 01:34:30 +00:00
viewer_id = info.context.get("author", {}).get("id")
info.context.get("is_admin", False)
return await get_all_authors(viewer_id)
2025-03-22 08:47:19 +00:00
2024-04-17 15:32:23 +00:00
@query.field("get_author")
async def get_author(
_: None, info: GraphQLResolveInfo, slug: Optional[str] = None, author_id: Optional[int] = None
) -> dict[str, Any] | None:
"""Get specific author by slug or ID"""
2025-05-20 22:34:02 +00:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 01:34:30 +00:00
is_admin = info.context.get("is_admin", False)
2025-05-29 09:37:39 +00:00
2024-03-14 06:59:38 +00:00
author_dict = None
2024-02-24 10:22:35 +00:00
try:
2024-05-30 09:49:46 +00:00
author_id = get_author_id_from(slug=slug, user="", author_id=author_id)
if not author_id:
msg = "cant find"
raise ValueError(msg)
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Получаем данные автора из кэша (полные данные)
cached_author = await get_cached_author(int(author_id), get_with_stat)
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Применяем фильтрацию на стороне клиента, так как в кэше хранится полная версия
if cached_author:
# Создаем объект автора для использования метода dict
temp_author = Author()
for key, value in cached_author.items():
if hasattr(temp_author, key):
setattr(temp_author, key, value)
# Получаем отфильтрованную версию
author_dict = temp_author.dict(is_admin)
2025-05-20 22:34:02 +00:00
# Добавляем статистику, которая могла быть в кэшированной версии
if "stat" in cached_author:
author_dict["stat"] = cached_author["stat"]
2024-05-25 23:17:45 +00:00
2024-04-17 15:32:23 +00:00
if not author_dict or not author_dict.get("stat"):
2024-05-25 23:17:45 +00:00
# update stat from db
2024-05-30 09:49:46 +00:00
author_query = select(Author).filter(Author.id == author_id)
2024-06-11 19:46:35 +00:00
result = get_with_stat(author_query)
if result:
author_with_stat = result[0]
if isinstance(author_with_stat, Author):
2025-05-20 22:34:02 +00:00
# Кэшируем полные данные для админов
original_dict = author_with_stat.dict(True)
2025-06-26 14:19:42 +00:00
_t = asyncio.create_task(cache_author(original_dict))
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Возвращаем отфильтрованную версию
author_dict = author_with_stat.dict(is_admin)
2025-05-20 22:34:02 +00:00
# Добавляем статистику
if hasattr(author_with_stat, "stat"):
author_dict["stat"] = author_with_stat.stat
2024-03-29 11:44:44 +00:00
except ValueError:
pass
2024-04-09 19:02:26 +00:00
except Exception as exc:
2024-02-25 23:07:46 +00:00
import traceback
2024-04-17 15:32:23 +00:00
logger.error(f"{exc}:\n{traceback.format_exc()}")
2024-03-28 17:36:35 +00:00
return author_dict
2023-12-27 22:37:54 +00:00
2024-04-17 15:32:23 +00:00
@query.field("load_authors_by")
2025-06-26 14:19:42 +00:00
async def load_authors_by(
_: None, info: GraphQLResolveInfo, by: AuthorsBy, limit: int = 10, offset: int = 0
) -> list[Any]:
"""Load authors by different criteria"""
2025-05-26 17:34:51 +00:00
try:
# Получаем ID текущего пользователя и флаг админа из контекста
viewer_id = info.context.get("author", {}).get("id")
info.context.get("is_admin", False)
2025-05-29 09:37:39 +00:00
2025-06-26 14:19:42 +00:00
# Логирование для отладки
logger.debug(f"load_authors_by called with by={by}, limit={limit}, offset={offset}")
# Проверяем наличие параметра order в словаре
if "order" in by:
logger.debug(f"Sorting by order={by['order']}")
2025-05-26 17:34:51 +00:00
# Используем оптимизированную функцию для получения авторов
2025-05-30 05:54:20 +00:00
return await get_authors_with_stats(limit, offset, by, viewer_id)
2025-05-26 17:34:51 +00:00
except Exception as exc:
import traceback
2025-05-29 09:37:39 +00:00
2025-05-26 17:34:51 +00:00
logger.error(f"{exc}:\n{traceback.format_exc()}")
return []
2024-03-12 13:18:07 +00:00
@query.field("load_authors_search")
async def load_authors_search(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> list[Any]:
"""Search for authors"""
# TODO: Implement search functionality
return []
2025-05-29 09:37:39 +00:00
def get_author_id_from(
slug: Optional[str] = None, user: Optional[str] = None, author_id: Optional[int] = None
) -> Optional[int]:
"""Get author ID from different identifiers"""
2025-03-22 08:47:19 +00:00
try:
if author_id:
return author_id
with local_session() as session:
author = None
if slug:
author = session.query(Author).filter(Author.slug == slug).first()
if author:
return int(author.id)
2025-03-22 08:47:19 +00:00
if user:
2025-05-20 22:34:02 +00:00
author = session.query(Author).filter(Author.id == user).first()
2025-03-22 08:47:19 +00:00
if author:
return int(author.id)
2025-03-22 08:47:19 +00:00
except Exception as exc:
logger.error(exc)
return None
2024-05-30 04:12:00 +00:00
2024-04-17 15:32:23 +00:00
@query.field("get_author_follows")
async def get_author_follows(
_, info: GraphQLResolveInfo, slug: Optional[str] = None, user: Optional[str] = None, author_id: Optional[int] = None
) -> dict[str, Any]:
"""Get entities followed by author"""
2025-05-20 22:34:02 +00:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 01:34:30 +00:00
viewer_id = info.context.get("author", {}).get("id")
is_admin = info.context.get("is_admin", False)
2025-05-29 09:37:39 +00:00
2025-03-22 08:47:19 +00:00
logger.debug(f"getting follows for @{slug}")
author_id = get_author_id_from(slug=slug, user=user, author_id=author_id)
if not author_id:
return {"error": "Author not found"}
2024-03-28 11:09:11 +00:00
2025-05-20 22:34:02 +00:00
# Получаем данные из кэша
followed_authors_raw = await get_cached_follower_authors(author_id)
2025-03-22 08:47:19 +00:00
followed_topics = await get_cached_follower_topics(author_id)
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Фильтруем чувствительные данные авторов
followed_authors = []
for author_data in followed_authors_raw:
# Создаем объект автора для использования метода dict
temp_author = Author()
for key, value in author_data.items():
if hasattr(temp_author, key):
setattr(temp_author, key, value)
# Добавляем отфильтрованную версию
2025-05-21 15:29:46 +00:00
# temp_author - это объект Author, который мы хотим сериализовать
# current_user_id - ID текущего авторизованного пользователя (может быть None)
# is_admin - булево значение, является ли текущий пользователь админом
2025-05-22 01:34:30 +00:00
has_access = is_admin or (viewer_id is not None and str(viewer_id) == str(temp_author.id))
followed_authors.append(temp_author.dict(has_access))
2025-03-22 08:47:19 +00:00
# TODO: Get followed communities too
return {
"authors": followed_authors,
"topics": followed_topics,
"communities": DEFAULT_COMMUNITIES,
"shouts": [],
"error": None,
2025-03-22 08:47:19 +00:00
}
2024-02-23 18:10:11 +00:00
2024-02-24 10:22:35 +00:00
2024-04-17 15:32:23 +00:00
@query.field("get_author_follows_topics")
async def get_author_follows_topics(
_,
_info: GraphQLResolveInfo,
slug: Optional[str] = None,
user: Optional[str] = None,
author_id: Optional[int] = None,
) -> list[Any]:
"""Get topics followed by author"""
2025-03-22 08:47:19 +00:00
logger.debug(f"getting followed topics for @{slug}")
author_id = get_author_id_from(slug=slug, user=user, author_id=author_id)
if not author_id:
return []
result = await get_cached_follower_topics(author_id)
# Ensure we return a list, not a dict
if isinstance(result, dict):
return result.get("topics", [])
return result if isinstance(result, list) else []
2024-02-23 18:10:11 +00:00
2024-04-17 15:32:23 +00:00
@query.field("get_author_follows_authors")
async def get_author_follows_authors(
_, info: GraphQLResolveInfo, slug: Optional[str] = None, user: Optional[str] = None, author_id: Optional[int] = None
) -> list[Any]:
"""Get authors followed by author"""
2025-05-20 22:34:02 +00:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 01:34:30 +00:00
viewer_id = info.context.get("author", {}).get("id")
is_admin = info.context.get("is_admin", False)
2025-05-29 09:37:39 +00:00
2025-03-22 08:47:19 +00:00
logger.debug(f"getting followed authors for @{slug}")
author_id = get_author_id_from(slug=slug, user=user, author_id=author_id)
2025-03-22 08:47:19 +00:00
if not author_id:
return []
2025-05-22 01:34:30 +00:00
2025-05-20 22:34:02 +00:00
# Получаем данные из кэша
followed_authors_raw = await get_cached_follower_authors(author_id)
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Фильтруем чувствительные данные авторов
followed_authors = []
for author_data in followed_authors_raw:
# Создаем объект автора для использования метода dict
temp_author = Author()
for key, value in author_data.items():
if hasattr(temp_author, key):
setattr(temp_author, key, value)
# Добавляем отфильтрованную версию
2025-05-21 15:29:46 +00:00
# temp_author - это объект Author, который мы хотим сериализовать
# current_user_id - ID текущего авторизованного пользователя (может быть None)
# is_admin - булево значение, является ли текущий пользователь админом
2025-05-22 01:34:30 +00:00
has_access = is_admin or (viewer_id is not None and str(viewer_id) == str(temp_author.id))
followed_authors.append(temp_author.dict(has_access))
2025-05-29 09:37:39 +00:00
2025-03-22 08:47:19 +00:00
return followed_authors
2023-10-23 14:47:11 +00:00
def create_author(**kwargs) -> Author:
"""Create new author"""
2025-03-22 08:47:19 +00:00
author = Author()
# Use setattr to avoid MyPy complaints about Column assignment
author.id = kwargs.get("user_id") # type: ignore[assignment] # Связь с user_id из системы авторизации # type: ignore[assignment]
author.slug = kwargs.get("slug") # type: ignore[assignment] # Идентификатор из системы авторизации # type: ignore[assignment]
author.created_at = int(time.time()) # type: ignore[assignment]
author.updated_at = int(time.time()) # type: ignore[assignment]
author.name = kwargs.get("name") or kwargs.get("slug") # type: ignore[assignment] # если не указано # type: ignore[assignment]
2025-03-22 08:47:19 +00:00
2023-11-28 19:07:53 +00:00
with local_session() as session:
2025-03-22 08:47:19 +00:00
session.add(author)
session.commit()
return author
2024-02-21 09:34:12 +00:00
2024-04-17 15:32:23 +00:00
@query.field("get_author_followers")
async def get_author_followers(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> list[Any]:
"""Get followers of an author"""
2025-05-20 22:34:02 +00:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 01:34:30 +00:00
viewer_id = info.context.get("author", {}).get("id")
is_admin = info.context.get("is_admin", False)
2025-05-29 09:37:39 +00:00
logger.debug(f"getting followers for author @{kwargs.get('slug')} or ID:{kwargs.get('author_id')}")
author_id = get_author_id_from(slug=kwargs.get("slug"), user=kwargs.get("user"), author_id=kwargs.get("author_id"))
2025-03-22 08:47:19 +00:00
if not author_id:
return []
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Получаем данные из кэша
followers_raw = await get_cached_author_followers(author_id)
2025-05-29 09:37:39 +00:00
2025-05-20 22:34:02 +00:00
# Фильтруем чувствительные данные авторов
followers = []
for follower_data in followers_raw:
# Создаем объект автора для использования метода dict
temp_author = Author()
for key, value in follower_data.items():
if hasattr(temp_author, key):
setattr(temp_author, key, value)
# Добавляем отфильтрованную версию
2025-05-21 15:29:46 +00:00
# temp_author - это объект Author, который мы хотим сериализовать
# current_user_id - ID текущего авторизованного пользователя (может быть None)
# is_admin - булево значение, является ли текущий пользователь админом
2025-05-22 01:34:30 +00:00
has_access = is_admin or (viewer_id is not None and str(viewer_id) == str(temp_author.id))
followers.append(temp_author.dict(has_access))
2025-05-29 09:37:39 +00:00
2024-05-20 22:40:57 +00:00
return followers