docs-restruct
All checks were successful
Deploy on push / deploy (push) Successful in 3m11s

This commit is contained in:
2025-10-02 01:16:14 +03:00
parent 31cf6b6961
commit 3e7431b601

View File

@@ -25,8 +25,36 @@ from utils.logger import root_logger as logger
def get_entity_field_name(entity_type: str) -> str:
"""Возвращает имя поля для связи с сущностью в модели подписчика"""
entity_field_mapping = {"author": "following", "topic": "topic", "community": "community", "shout": "shout"}
"""
Возвращает имя поля для связи с сущностью в модели подписчика.
Эта функция используется для определения правильного поля в моделях подписчиков
(AuthorFollower, TopicFollower, CommunityFollower, ShoutReactionsFollower) при создании
или проверке подписки.
Args:
entity_type: Тип сущности в нижнем регистре ('author', 'topic', 'community', 'shout')
Returns:
str: Имя поля в модели подписчика ('following', 'topic', 'community', 'shout')
Raises:
ValueError: Если передан неизвестный тип сущности
Examples:
>>> get_entity_field_name('author')
'following'
>>> get_entity_field_name('topic')
'topic'
>>> get_entity_field_name('invalid')
ValueError: Unknown entity_type: invalid
"""
entity_field_mapping = {
"author": "following", # AuthorFollower.following -> Author
"topic": "topic", # TopicFollower.topic -> Topic
"community": "community", # CommunityFollower.community -> Community
"shout": "shout" # ShoutReactionsFollower.shout -> Shout
}
if entity_type not in entity_field_mapping:
msg = f"Unknown entity_type: {entity_type}"
raise ValueError(msg)
@@ -36,8 +64,42 @@ def get_entity_field_name(entity_type: str) -> str:
@mutation.field("follow")
@login_required
async def follow(
_: None, info: GraphQLResolveInfo, what: str, slug: str = "", entity_id: int | None = None
_: None,
info: GraphQLResolveInfo,
what: str,
slug: str = "",
entity_id: int | None = None
) -> dict[str, Any]:
"""
GraphQL мутация для создания подписки на автора, тему, сообщество или публикацию.
Эта функция обрабатывает все типы подписок в системе, включая:
- Подписку на автора (AUTHOR)
- Подписку на тему (TOPIC)
- Подписку на сообщество (COMMUNITY)
- Подписку на публикацию (SHOUT)
Args:
_: None - Стандартный параметр GraphQL (не используется)
info: GraphQLResolveInfo - Контекст GraphQL запроса, содержит информацию об авторизованном пользователе
what: str - Тип сущности для подписки ('AUTHOR', 'TOPIC', 'COMMUNITY', 'SHOUT')
slug: str - Slug сущности (например, 'author-slug' или 'topic-slug')
entity_id: int | None - ID сущности (альтернатива slug)
Returns:
dict[str, Any] - Результат операции:
{
"success": bool, # Успешность операции
"error": str | None, # Текст ошибки если есть
"authors": Author[], # Обновленные авторы (для кеширования)
"topics": Topic[], # Обновленные темы (для кеширования)
"entity_id": int | None # ID созданной подписки
}
Raises:
ValueError: При передаче некорректных параметров
DatabaseError: При проблемах с базой данных
"""
logger.debug("Начало выполнения функции 'follow'")
viewer_id = info.context.get("author", {}).get("id")
follower_dict = info.context.get("author") or {}
@@ -51,7 +113,9 @@ async def follow(
await redis.execute("DEL", f"author:id:{viewer_id}")
logger.debug(f"Инвалидирован кеш подписок follower'а: {cache_key_pattern}")
# Проверка авторизации пользователя
if not viewer_id:
logger.warning("Попытка подписаться без авторизации")
return {"error": "Access denied"}
logger.debug(f"follower: {follower_dict}")
@@ -63,6 +127,7 @@ async def follow(
follower_id = follower_dict.get("id")
logger.debug(f"follower_id: {follower_id}")
# Маппинг типов сущностей на их классы и методы кеширования
entity_classes = {
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
@@ -205,13 +270,87 @@ async def follow(
@mutation.field("unfollow")
@login_required
async def unfollow(
_: None, info: GraphQLResolveInfo, what: str, slug: str = "", entity_id: int | None = None
_: None,
info: GraphQLResolveInfo,
what: str,
slug: str = "",
entity_id: int | None = None
) -> dict[str, Any]:
"""
GraphQL мутация для отмены подписки на автора, тему, сообщество или публикацию.
Эта функция обрабатывает отмену всех типов подписок в системе, включая:
- Отписку от автора (AUTHOR)
- Отписку от темы (TOPIC)
- Отписку от сообщества (COMMUNITY)
- Отписку от публикации (SHOUT)
Процесс отмены подписки:
1. Проверка авторизации пользователя
2. Поиск существующей подписки в базе данных
3. Удаление подписки если она найдена
4. Инвалидация кеша для обновления данных
5. Отправка уведомлений об отписке
Args:
_: None - Стандартный параметр GraphQL (не используется)
info: GraphQLResolveInfo - Контекст GraphQL запроса, содержит информацию об авторизованном пользователе
what: str - Тип сущности для отписки ('AUTHOR', 'TOPIC', 'COMMUNITY', 'SHOUT')
slug: str - Slug сущности (например, 'author-slug' или 'topic-slug')
entity_id: int | None - ID сущности (альтернатива slug)
Returns:
dict[str, Any] - Результат операции:
{
"success": bool, # Успешность операции
"error": str | None, # Текст ошибки если есть
"authors": Author[], # Обновленные авторы (для кеширования)
"topics": Topic[], # Обновленные темы (для кеширования)
}
Raises:
ValueError: При передаче некорректных параметров
DatabaseError: При проблемах с базой данных
Examples:
# Отписка от автора
mutation {
unfollow(what: "AUTHOR", slug: "author-slug") {
success
error
}
}
# Отписка от темы
mutation {
unfollow(what: "TOPIC", slug: "topic-slug") {
success
error
}
}
# Отписка от сообщества
mutation {
unfollow(what: "COMMUNITY", slug: "community-slug") {
success
error
}
}
# Отписка от публикации
mutation {
unfollow(what: "SHOUT", entity_id: 123) {
success
error
}
}
"""
logger.debug("Начало выполнения функции 'unfollow'")
viewer_id = info.context.get("author", {}).get("id")
follower_dict = info.context.get("author") or {}
# ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, если пользователь авторизован
# чтобы предотвратить чтение старых данных при последующей перезагрузке
if viewer_id:
entity_type = what.lower()
cache_key_pattern = f"author:follows-{entity_type}s:{viewer_id}"
@@ -219,7 +358,9 @@ async def unfollow(
await redis.execute("DEL", f"author:id:{viewer_id}")
logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции unfollow: {cache_key_pattern}")
# Проверка авторизации пользователя
if not viewer_id:
logger.warning("Попытка отписаться без авторизации")
return {"error": "Access denied"}
logger.debug(f"follower: {follower_dict}")
@@ -231,6 +372,7 @@ async def unfollow(
follower_id = follower_dict.get("id")
logger.debug(f"follower_id: {follower_id}")
# Маппинг типов сущностей на их классы и методы кеширования
entity_classes = {
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),