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:
|
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:
|
if entity_type not in entity_field_mapping:
|
||||||
msg = f"Unknown entity_type: {entity_type}"
|
msg = f"Unknown entity_type: {entity_type}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
@@ -36,8 +64,42 @@ def get_entity_field_name(entity_type: str) -> str:
|
|||||||
@mutation.field("follow")
|
@mutation.field("follow")
|
||||||
@login_required
|
@login_required
|
||||||
async def follow(
|
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]:
|
) -> 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'")
|
logger.debug("Начало выполнения функции 'follow'")
|
||||||
viewer_id = info.context.get("author", {}).get("id")
|
viewer_id = info.context.get("author", {}).get("id")
|
||||||
follower_dict = info.context.get("author") or {}
|
follower_dict = info.context.get("author") or {}
|
||||||
@@ -51,7 +113,9 @@ async def follow(
|
|||||||
await redis.execute("DEL", f"author:id:{viewer_id}")
|
await redis.execute("DEL", f"author:id:{viewer_id}")
|
||||||
logger.debug(f"Инвалидирован кеш подписок follower'а: {cache_key_pattern}")
|
logger.debug(f"Инвалидирован кеш подписок follower'а: {cache_key_pattern}")
|
||||||
|
|
||||||
|
# Проверка авторизации пользователя
|
||||||
if not viewer_id:
|
if not viewer_id:
|
||||||
|
logger.warning("Попытка подписаться без авторизации")
|
||||||
return {"error": "Access denied"}
|
return {"error": "Access denied"}
|
||||||
|
|
||||||
logger.debug(f"follower: {follower_dict}")
|
logger.debug(f"follower: {follower_dict}")
|
||||||
@@ -63,6 +127,7 @@ async def follow(
|
|||||||
follower_id = follower_dict.get("id")
|
follower_id = follower_dict.get("id")
|
||||||
logger.debug(f"follower_id: {follower_id}")
|
logger.debug(f"follower_id: {follower_id}")
|
||||||
|
|
||||||
|
# Маппинг типов сущностей на их классы и методы кеширования
|
||||||
entity_classes = {
|
entity_classes = {
|
||||||
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
||||||
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
||||||
@@ -205,13 +270,87 @@ async def follow(
|
|||||||
@mutation.field("unfollow")
|
@mutation.field("unfollow")
|
||||||
@login_required
|
@login_required
|
||||||
async def unfollow(
|
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]:
|
) -> 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'")
|
logger.debug("Начало выполнения функции 'unfollow'")
|
||||||
viewer_id = info.context.get("author", {}).get("id")
|
viewer_id = info.context.get("author", {}).get("id")
|
||||||
follower_dict = info.context.get("author") or {}
|
follower_dict = info.context.get("author") or {}
|
||||||
|
|
||||||
# ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, если пользователь авторизован
|
# ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, если пользователь авторизован
|
||||||
|
# чтобы предотвратить чтение старых данных при последующей перезагрузке
|
||||||
if viewer_id:
|
if viewer_id:
|
||||||
entity_type = what.lower()
|
entity_type = what.lower()
|
||||||
cache_key_pattern = f"author:follows-{entity_type}s:{viewer_id}"
|
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}")
|
await redis.execute("DEL", f"author:id:{viewer_id}")
|
||||||
logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции unfollow: {cache_key_pattern}")
|
logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции unfollow: {cache_key_pattern}")
|
||||||
|
|
||||||
|
# Проверка авторизации пользователя
|
||||||
if not viewer_id:
|
if not viewer_id:
|
||||||
|
logger.warning("Попытка отписаться без авторизации")
|
||||||
return {"error": "Access denied"}
|
return {"error": "Access denied"}
|
||||||
|
|
||||||
logger.debug(f"follower: {follower_dict}")
|
logger.debug(f"follower: {follower_dict}")
|
||||||
@@ -231,6 +372,7 @@ async def unfollow(
|
|||||||
follower_id = follower_dict.get("id")
|
follower_id = follower_dict.get("id")
|
||||||
logger.debug(f"follower_id: {follower_id}")
|
logger.debug(f"follower_id: {follower_id}")
|
||||||
|
|
||||||
|
# Маппинг типов сущностей на их классы и методы кеширования
|
||||||
entity_classes = {
|
entity_classes = {
|
||||||
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
||||||
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
||||||
|
|||||||
Reference in New Issue
Block a user