This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user