diff --git a/resolvers/follower.py b/resolvers/follower.py index d1c132c1..692ee28b 100644 --- a/resolvers/follower.py +++ b/resolvers/follower.py @@ -52,6 +52,14 @@ async def follow( follower_id = follower_dict.get("id") logger.debug(f"follower_id: {follower_id}") + # ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, ДО любых операций + # чтобы предотвратить чтение старых данных при последующей перезагрузке + entity_type = what.lower() + cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}" + await redis.execute("DEL", cache_key_pattern) + await redis.execute("DEL", f"author:id:{follower_id}") + logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции: {cache_key_pattern}") + entity_classes = { "AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author), "TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic), @@ -68,6 +76,10 @@ async def follow( follows: list[dict[str, Any]] = [] error: str | None = None + # ✅ Сохраняем entity_id и error вне сессии для использования после её закрытия + entity_id_result: int | None = None + error_result: str | None = None + try: logger.debug("Попытка получить сущность из базы данных") with local_session() as session: @@ -110,15 +122,10 @@ async def follow( .first() ) - # 🔧 ИСПРАВЛЕНИЕ: Инвалидируем кэш ДО проверки existing_sub, - # чтобы всегда возвращать актуальный список подписок (даже при ошибке "already following") - cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}" - await redis.execute("DEL", cache_key_pattern) - logger.debug(f"Инвалидирован кэш подписок: {cache_key_pattern}") - if existing_sub: logger.info(f"Пользователь {follower_id} уже подписан на {what.lower()} с ID {entity_id}") - error = "already following" + error_result = "already following" + # ✅ КРИТИЧНО: Не делаем return - продолжаем для получения списка подписок else: logger.debug("Добавление новой записи в базу данных") sub = follower_class(follower=follower_id, **{entity_field: entity_id}) @@ -127,37 +134,36 @@ async def follow( session.commit() logger.info(f"Пользователь {follower_id} подписался на {what.lower()} с ID {entity_id}") - if cache_method: - logger.debug("Обновление кэша сущности") - await cache_method(entity_dict) + if cache_method: + logger.debug("Обновление кэша сущности") + await cache_method(entity_dict) - if what == "AUTHOR" and not existing_sub: - logger.debug("Отправка уведомления автору о подписке") - if isinstance(follower_dict, dict) and isinstance(entity_id, int): - # Получаем ID созданной записи подписки - subscription_id = getattr(sub, "id", None) if "sub" in locals() else None - await notify_follower( - follower=follower_dict, - author_id=entity_id, - action="follow", - subscription_id=subscription_id, - ) + if what == "AUTHOR": + logger.debug("Отправка уведомления автору о подписке") + if isinstance(follower_dict, dict) and isinstance(entity_id, int): + # Получаем ID созданной записи подписки + subscription_id = getattr(sub, "id", None) if "sub" in locals() else None + await notify_follower( + follower=follower_dict, + author_id=entity_id, + action="follow", + subscription_id=subscription_id, + ) - # Инвалидируем кеш статистики авторов для обновления счетчиков подписчиков - logger.debug("Инвалидируем кеш статистики авторов") - await invalidate_authors_cache(entity_id) + # Инвалидируем кеш статистики авторов для обновления счетчиков подписчиков + logger.debug("Инвалидируем кеш статистики авторов") + await invalidate_authors_cache(entity_id) - # ✅ КРИТИЧНО: Также инвалидируем кеш полных данных для корректной загрузки при рефреше - # Это гарантирует, что после рефреша клиент получит актуальные данные из БД - await redis.execute("DEL", f"author:id:{follower_id}") - logger.debug(f"Инвалидирован кеш полных данных пользователя: author:id:{follower_id}") + entity_id_result = entity_id - # Всегда получаем актуальный список подписок для возврата клиенту + # ✅ Получаем актуальный список подписок для возврата клиенту + # Кеш уже инвалидирован в начале функции, поэтому get_cached_follows_method + # вернет свежие данные из БД if get_cached_follows_method and isinstance(follower_id, int): - logger.debug("Получение актуального списка подписок из кэша") + logger.debug("Получение актуального списка подписок после закрытия сессии") existing_follows = await get_cached_follows_method(follower_id) logger.debug( - f"Получено подписок: {len(existing_follows)}, содержит target={entity_id in [f.get('id') for f in existing_follows] if existing_follows else False}" + f"Получено подписок: {len(existing_follows)}, содержит target={entity_id_result in [f.get('id') for f in existing_follows] if existing_follows else False}" ) # Если это авторы, получаем безопасную версию @@ -181,7 +187,7 @@ async def follow( logger.debug(f"Актуальный список подписок получен: {len(follows)} элементов") - return {f"{entity_type}s": follows, "error": error} + return {f"{entity_type}s": follows, "error": error_result} except Exception as exc: logger.exception("Произошла ошибка в функции 'follow'") @@ -207,6 +213,13 @@ async def unfollow( follower_id = follower_dict.get("id") logger.debug(f"follower_id: {follower_id}") + # ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, ДО любых операций + entity_type = what.lower() + cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}" + await redis.execute("DEL", cache_key_pattern) + await redis.execute("DEL", f"author:id:{follower_id}") + logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции unfollow: {cache_key_pattern}") + entity_classes = { "AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author), "TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),