Files
core/resolvers/author.py

1099 lines
57 KiB
Python
Raw Normal View History

2024-06-11 22:46:35 +03:00
import asyncio
2023-11-03 13:10:22 +03:00
import time
2025-07-31 18:55:59 +03:00
import traceback
2025-08-17 16:33:54 +03:00
from typing import Any, TypedDict
2023-12-17 23:30:20 +03:00
from graphql import GraphQLResolveInfo
2025-07-31 18:55:59 +03:00
from sqlalchemy import and_, asc, func, select, text
from sqlalchemy.sql import desc as sql_desc
2023-10-23 17:47:11 +03:00
2024-08-07 08:57:56 +03:00
from cache.cache import (
2024-05-21 01:40:57 +03:00
cache_author,
2025-03-22 11:47:19 +03:00
cached_query,
2024-05-21 01:40:57 +03:00
get_cached_author_followers,
2024-05-30 19:42:38 +03:00
get_cached_follower_authors,
get_cached_follower_topics,
2025-03-22 11:47:19 +03:00
invalidate_cache_by_prefix,
2024-05-21 01:40:57 +03:00
)
[0.9.7] - 2025-08-18 ### 🔄 Изменения - **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации - **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)` ### 🧪 Тестирование - **Исправление тестов** - адаптация к новой структуре моделей - **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py` - **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев - **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями - **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода ### 🔧 Рефакторинг - **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру - **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль - **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры - **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей - **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки ### 🔧 Авторизация с cookies - **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization - **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно - **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token` - **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession` - **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author` - **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами - **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession` - **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации ### 📝 Документация - **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа - Обновлена документация RBAC - Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
from orm.author import Author, AuthorFollower
2025-07-31 18:55:59 +03:00
from orm.community import Community, CommunityAuthor, CommunityFollower
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
from orm.reaction import Reaction
from orm.shout import Shout, ShoutAuthor, ShoutTopic
2025-09-01 10:53:38 +03:00
from orm.topic import Topic
2024-08-12 11:00:01 +03:00
from resolvers.stat import get_with_stat
from services.auth import login_required
2025-08-23 10:47:52 +03:00
from services.search import search_service
2025-08-17 17:56:31 +03:00
from storage.db import local_session
from storage.redis import redis
from storage.schema import mutation, query
[0.9.7] - 2025-08-18 ### 🔄 Изменения - **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации - **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)` ### 🧪 Тестирование - **Исправление тестов** - адаптация к новой структуре моделей - **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py` - **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев - **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями - **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода ### 🔧 Рефакторинг - **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру - **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль - **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры - **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей - **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки ### 🔧 Авторизация с cookies - **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization - **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно - **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token` - **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession` - **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author` - **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами - **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession` - **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации ### 📝 Документация - **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа - Обновлена документация RBAC - Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
from utils.common_result import CommonResult
2024-08-12 11:00:01 +03:00
from utils.logger import root_logger as logger
2024-01-13 11:49:12 +03:00
2025-03-22 11:47:19 +03:00
DEFAULT_COMMUNITIES = [1]
2025-06-26 17:19:42 +03:00
# Определение типа AuthorsBy на основе схемы GraphQL
class AuthorsBy(TypedDict, total=False):
"""
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
Параметры фильтрации и сортировки авторов для GraphQL запроса load_authors_by.
📊 Поля сортировки:
order: Поле для сортировки авторов:
🔢 Базовые метрики: "shouts" (публикации), "followers" (подписчики)
🏷 Контент: "topics" (темы), "comments" (комментарии)
👥 Социальные: "coauthors" (соавторы), "replies_count" (ответы на контент)
2025-08-31 22:12:18 +03:00
Рейтинг: "rating_shouts" (публикации), "rating_comments" (комментарии)
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
👁 Вовлечённость: "viewed_shouts" (просмотры)
📝 Алфавит: "name" (по имени)
🔍 Поля фильтрации:
2025-06-26 17:19:42 +03:00
last_seen: Временная метка последнего посещения
created_at: Временная метка создания
slug: Уникальный идентификатор автора
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
name: Имя автора для поиска
2025-06-26 17:19:42 +03:00
topic: Тема, связанная с автором
after: Временная метка для фильтрации "после"
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
stat: Поле статистики для дополнительной фильтрации
2025-06-26 17:19:42 +03:00
"""
2025-08-17 16:33:54 +03:00
last_seen: int | None
created_at: int | None
slug: str | None
name: str | None
topic: str | None
order: str | None
after: int | None
stat: str | None
2025-08-31 22:29:40 +03:00
id: int | None # Добавляем поле id для фильтрации по ID
2025-06-26 17:19:42 +03:00
2025-03-22 11:47:19 +03:00
# Вспомогательная функция для получения всех авторов без статистики
2025-08-17 16:33:54 +03:00
async def get_all_authors(current_user_id: int | None = None) -> list[Any]:
2025-03-22 11:47:19 +03:00
"""
Получает всех авторов без статистики.
Используется для случаев, когда нужен полный список авторов без дополнительной информации.
2025-05-21 01:34:02 +03:00
Args:
current_user_id: ID текущего пользователя для проверки прав доступа
is_admin: Флаг, указывающий, является ли пользователь администратором
2025-03-22 11:47:19 +03:00
Returns:
list: Список всех авторов без статистики
"""
cache_key = "authors:all:basic"
# Функция для получения всех авторов из БД
async def fetch_all_authors() -> list[Any]:
"""
Выполняет запрос к базе данных для получения всех авторов.
"""
2025-03-22 11:47:19 +03:00
logger.debug("Получаем список всех авторов из БД и кешируем результат")
with local_session() as session:
# Запрос на получение базовой информации об авторах
authors_query = select(Author).where(Author.deleted_at.is_(None))
2025-05-30 08:51:24 +03:00
authors = session.execute(authors_query).scalars().unique().all()
2025-03-22 11:47:19 +03:00
2025-05-21 01:34:02 +03:00
# Преобразуем авторов в словари с учетом прав доступа
2025-07-31 18:55:59 +03:00
return [author.dict() for author in authors]
2025-03-22 11:47:19 +03:00
# Используем универсальную функцию для кеширования запросов
return await cached_query(cache_key, fetch_all_authors)
# Вспомогательная функция для получения авторов со статистикой с пагинацией
async def get_authors_with_stats(
2025-08-17 16:33:54 +03:00
limit: int = 10, offset: int = 0, by: AuthorsBy | None = None, current_user_id: int | None = None
2025-07-31 18:55:59 +03:00
) -> list[dict[str, Any]]:
2025-03-22 11:47:19 +03:00
"""
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
🧪 Получает авторов с полной статистикой и поддержкой сортировки.
📊 Рассчитывает все метрики AuthorStat:
- shouts: Количество опубликованных статей
- topics: Уникальные темы участия
- coauthors: Количество соавторов
- followers: Подписчики
2025-08-31 22:12:18 +03:00
- authors: Количество авторов, на которых подписан
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
- rating_shouts: Рейтинг публикаций (реакции)
- rating_comments: Рейтинг комментариев (реакции)
- comments: Созданные комментарии
- replies_count: Ответы на контент (комментарии на посты + ответы на комментарии)
- viewed_shouts: Просмотры публикаций (из ViewedStorage)
Оптимизации:
- Batch SQL-запросы для статистики
- Кеширование результатов
- Сортировка на уровне SQL для производительности
2025-03-22 11:47:19 +03:00
Args:
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
limit: Максимальное количество возвращаемых авторов (1-100)
2025-03-22 11:47:19 +03:00
offset: Смещение для пагинации
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
by: Параметры фильтрации и сортировки (AuthorsBy)
current_user_id: ID текущего пользователя для фильтрации доступа
2025-03-22 11:47:19 +03:00
Returns:
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
list[dict]: Список авторов с полной статистикой, отсортированных согласно параметрам
Raises:
Exception: При ошибках выполнения SQL-запросов или доступа к ViewedStorage
2025-03-22 11:47:19 +03:00
"""
# Формируем ключ кеша с помощью универсальной функции
2025-06-26 17:19:42 +03:00
order_value = by.get("order", "default") if by else "default"
2025-09-01 09:07:37 +03:00
# Добавляем фильтры в ключ кэша для правильного кэширования
filter_parts = []
if by:
if by.get("slug"):
filter_parts.append(f"slug={by['slug']}")
if by.get("id"):
filter_parts.append(f"id={by['id']}")
if by.get("stat"):
filter_parts.append(f"stat={by['stat']}")
2025-09-01 10:53:38 +03:00
if by.get("topic"):
filter_parts.append(f"topic={by['topic']}")
2025-09-01 09:07:37 +03:00
filter_str = ":".join(filter_parts) if filter_parts else "all"
cache_key = f"authors:stats:limit={limit}:offset={offset}:order={order_value}:filter={filter_str}"
2025-03-22 11:47:19 +03:00
# Функция для получения авторов из БД
async def fetch_authors_with_stats() -> list[Any]:
"""
Выполняет запрос к базе данных для получения авторов со статистикой.
"""
2025-08-26 14:12:49 +03:00
try:
with local_session() as session:
# Базовый запрос для получения авторов
base_query = select(Author).where(Author.deleted_at.is_(None))
2025-09-01 10:53:38 +03:00
# Специальная обработка фильтра по теме (topic)
if by and by.get("topic"):
topic_value = by["topic"]
logger.debug(f"🔍 Filtering authors by topic: {topic_value}")
# JOIN с таблицами для фильтрации по теме
# Авторы, которые публиковали статьи с данной темой
base_query = (
base_query.join(ShoutAuthor, Author.id == ShoutAuthor.author)
.join(Shout, ShoutAuthor.shout == Shout.id)
.join(ShoutTopic, Shout.id == ShoutTopic.shout)
.join(Topic, ShoutTopic.topic == Topic.id)
.where(Topic.slug == topic_value)
.where(Shout.deleted_at.is_(None))
.where(Shout.published_at.is_not(None))
.distinct() # Избегаем дубликатов авторов
)
2025-09-03 12:44:24 +03:00
# Указываем что фильтр применен, чтобы избежать сброса сортировки по умолчанию
default_sort_applied = True
logger.debug(f"✅ Topic filter applied for: {topic_value}")
2025-09-01 10:53:38 +03:00
2025-08-31 22:42:21 +03:00
# Применяем фильтрацию по параметрам из by
if by:
for key, value in by.items():
2025-09-01 10:53:38 +03:00
if key not in ("order", "topic") and value is not None: # order и topic обрабатываются отдельно
2025-08-31 22:42:21 +03:00
if hasattr(Author, key):
column = getattr(Author, key)
base_query = base_query.where(column == value)
logger.debug(f"Applied filter: {key} = {value}")
else:
logger.warning(f"Unknown filter field: {key}")
2025-08-26 14:12:49 +03:00
# vars for statistics sorting
stats_sort_field = None
2025-09-03 12:44:24 +03:00
# НЕ сбрасываем default_sort_applied если он уже установлен для фильтра по топику
if not default_sort_applied:
default_sort_applied = False
2025-08-26 14:12:49 +03:00
if by:
if "order" in by:
order_value = by["order"]
logger.debug(f"Found order field with value: {order_value}")
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
if order_value in [
"shouts",
"followers",
"comments",
"topics",
"coauthors",
"viewed_shouts",
"rating_shouts",
"rating_comments",
"replies_count",
]:
2025-08-26 14:12:49 +03:00
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")
2025-06-26 17:19:42 +03:00
default_sort_applied = True
else:
2025-08-26 14:12:49 +03:00
# If order is not a stats field, treat it as a regular field
column = getattr(Author, order_value or "", "")
if column:
2025-06-26 17:19:42 +03:00
base_query = base_query.order_by(sql_desc(column))
2025-08-26 14:12:49 +03:00
logger.debug(f"Applying sorting by column: {order_value}")
default_sort_applied = True
2025-06-26 17:19:42 +03:00
else:
2025-08-26 14:12:49 +03:00
logger.warning(f"Unknown order field: {order_value}")
else:
# Regular sorting by fields
for field, direction in by.items():
if field is None:
continue
column = getattr(Author, field, None)
if column:
if isinstance(direction, str) and direction.lower() == "desc":
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))
logger.debug("Applying default sorting by created_at (no by parameter)")
# If sorting by statistics, modify the query
if stats_sort_field == "shouts":
# Sorting by the number of shouts
logger.debug("Building subquery for shouts sorting")
subquery = (
select(ShoutAuthor.author, func.count(func.distinct(Shout.id)).label("shouts_count"))
.select_from(ShoutAuthor)
.join(Shout, ShoutAuthor.shout == Shout.id)
.where(and_(Shout.deleted_at.is_(None), Shout.published_at.is_not(None)))
.group_by(ShoutAuthor.author)
.subquery()
)
# Сбрасываем предыдущую сортировку и применяем новую
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.shouts_count, 0))
)
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
logger.debug("Building subquery for followers sorting")
subquery = (
select(
AuthorFollower.following,
func.count(func.distinct(AuthorFollower.follower)).label("followers_count"),
)
.select_from(AuthorFollower)
.group_by(AuthorFollower.following)
.subquery()
)
# Сбрасываем предыдущую сортировку и применяем новую
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.following).order_by(
sql_desc(func.coalesce(subquery.c.followers_count, 0))
)
2025-08-26 14:12:49 +03:00
logger.debug("Applied sorting by followers count")
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
elif stats_sort_field == "topics":
# 🏷️ Сортировка по количеству тем
logger.debug("Building subquery for topics sorting")
subquery = (
select(ShoutAuthor.author, func.count(func.distinct(ShoutTopic.topic)).label("topics_count"))
.select_from(ShoutAuthor)
.join(Shout, ShoutAuthor.shout == Shout.id)
.join(ShoutTopic, Shout.id == ShoutTopic.shout)
.where(and_(Shout.deleted_at.is_(None), Shout.published_at.is_not(None)))
.group_by(ShoutAuthor.author)
.subquery()
)
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.topics_count, 0))
)
logger.debug("Applied sorting by topics count")
elif stats_sort_field == "coauthors":
# ✍️ Сортировка по количеству соавторов
logger.debug("Building subquery for coauthors sorting")
sa1 = ShoutAuthor.__table__.alias("sa1")
sa2 = ShoutAuthor.__table__.alias("sa2")
subquery = (
select(sa1.c.author, func.count(func.distinct(sa2.c.author)).label("coauthors_count"))
.select_from(sa1.join(Shout, sa1.c.shout == Shout.id).join(sa2, sa2.c.shout == Shout.id))
.where(
and_(
Shout.deleted_at.is_(None),
Shout.published_at.is_not(None),
sa1.c.author != sa2.c.author, # исключаем самого автора из подсчёта
)
)
.group_by(sa1.c.author)
.subquery()
)
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
sql_desc(func.coalesce(subquery.c.coauthors_count, 0))
)
logger.debug("Applied sorting by coauthors count")
elif stats_sort_field == "comments":
# 💬 Сортировка по количеству комментариев
logger.debug("Building subquery for comments sorting")
subquery = (
select(Reaction.created_by, func.count(func.distinct(Reaction.id)).label("comments_count"))
.select_from(Reaction)
.join(Shout, Reaction.shout == Shout.id)
.where(
and_(
Reaction.deleted_at.is_(None),
Shout.deleted_at.is_(None),
Reaction.kind.in_(["COMMENT", "QUOTE"]),
)
)
.group_by(Reaction.created_by)
.subquery()
)
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.created_by).order_by(
sql_desc(func.coalesce(subquery.c.comments_count, 0))
)
logger.debug("Applied sorting by comments count")
elif stats_sort_field == "replies_count":
# 💬 Сортировка по общему количеству ответов (комментарии на посты + ответы на комментарии)
logger.debug("Building subquery for replies_count sorting")
# Подзапрос для ответов на комментарии автора
replies_to_comments_subq = (
select(
Reaction.created_by.label("author_id"),
func.count(func.distinct(Reaction.id)).label("replies_count"),
)
.select_from(Reaction)
.where(
and_(
Reaction.deleted_at.is_(None),
Reaction.reply_to.is_not(None),
Reaction.kind.in_(["COMMENT", "QUOTE"]),
)
)
.group_by(Reaction.created_by)
.subquery()
)
# Подзапрос для комментариев на посты автора
comments_on_posts_subq = (
select(
ShoutAuthor.author.label("author_id"),
func.count(func.distinct(Reaction.id)).label("replies_count"),
)
.select_from(ShoutAuthor)
.join(Shout, ShoutAuthor.shout == Shout.id)
.join(Reaction, Shout.id == Reaction.shout)
.where(
and_(
Shout.deleted_at.is_(None),
Shout.published_at.is_not(None),
Reaction.deleted_at.is_(None),
Reaction.kind.in_(["COMMENT", "QUOTE"]),
)
)
.group_by(ShoutAuthor.author)
.subquery()
)
# Объединяем оба подзапроса через UNION ALL
combined_replies_subq = (
select(
func.coalesce(
replies_to_comments_subq.c.author_id, comments_on_posts_subq.c.author_id
).label("author_id"),
func.coalesce(
func.coalesce(replies_to_comments_subq.c.replies_count, 0)
+ func.coalesce(comments_on_posts_subq.c.replies_count, 0),
0,
).label("total_replies"),
)
.select_from(
2025-08-31 20:03:44 +03:00
replies_to_comments_subq.outerjoin(
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
comments_on_posts_subq,
replies_to_comments_subq.c.author_id == comments_on_posts_subq.c.author_id,
)
)
.subquery()
)
base_query = base_query.outerjoin(
combined_replies_subq, Author.id == combined_replies_subq.c.author_id
).order_by(sql_desc(func.coalesce(combined_replies_subq.c.total_replies, 0)))
logger.debug("Applied sorting by replies_count")
2025-08-26 14:12:49 +03:00
# Логирование для отладки сортировки
try:
# Получаем SQL запрос для проверки
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
logger.debug(f"Generated SQL query for replies_count sorting: {sql_query}")
2025-08-26 14:12:49 +03:00
except Exception as e:
logger.error(f"Error generating SQL query: {e}")
# Применяем лимит и смещение
base_query = base_query.limit(limit).offset(offset)
# Получаем авторов
logger.debug("Executing main query for authors")
authors = session.execute(base_query).scalars().unique().all()
author_ids = [author.id for author in authors]
logger.debug(f"Retrieved {len(authors)} authors with IDs: {author_ids}")
if not author_ids:
logger.debug("No authors found, returning empty list")
return []
# Логирование результатов для отладки сортировки
if stats_sort_field:
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
# 🧪 Оптимизированные запросы для получения всей статистики авторов
logger.debug("Executing comprehensive statistics queries")
2025-08-26 14:12:49 +03:00
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
# 📊 Статистика по публикациям
logger.debug("Executing shouts statistics query")
2025-08-26 14:12:49 +03: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})
GROUP BY sa.author
"""
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
logger.debug(f"Shouts stats retrieved: {shouts_stats}")
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
# 👥 Статистика по подписчикам
2025-08-26 14:12:49 +03:00
logger.debug("Executing followers statistics query")
followers_stats_query = f"""
SELECT following, COUNT(DISTINCT follower) as followers_count
FROM author_follower
WHERE following IN ({placeholders})
GROUP BY following
"""
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query), params)}
logger.debug(f"Followers stats retrieved: {followers_stats}")
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
# 🏷️ Статистика по темам (количество уникальных тем, в которых участвовал автор)
logger.debug("Executing topics statistics query")
topics_stats_query = f"""
SELECT sa.author, COUNT(DISTINCT st.topic) as topics_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
JOIN shout_topic st ON s.id = st.shout
WHERE sa.author IN ({placeholders})
GROUP BY sa.author
"""
topics_stats = {row[0]: row[1] for row in session.execute(text(topics_stats_query), params)}
logger.debug(f"Topics stats retrieved: {topics_stats}")
# ✍️ Статистика по соавторам (количество уникальных соавторов)
logger.debug("Executing coauthors statistics query")
coauthors_stats_query = f"""
2025-09-01 06:16:44 +03:00
SELECT sa1.author, COALESCE(COUNT(DISTINCT sa2.author), 0) as coauthors_count
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
FROM shout_author sa1
JOIN shout s ON sa1.shout = s.id
AND s.deleted_at IS NULL
AND s.published_at IS NOT NULL
2025-09-01 06:16:44 +03:00
LEFT JOIN shout_author sa2 ON s.id = sa2.shout
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
AND sa2.author != sa1.author -- исключаем самого автора
WHERE sa1.author IN ({placeholders})
GROUP BY sa1.author
"""
coauthors_stats = {row[0]: row[1] for row in session.execute(text(coauthors_stats_query), params)}
logger.debug(f"Coauthors stats retrieved: {coauthors_stats}")
# 💬 Статистика по комментариям (количество созданных комментариев)
logger.debug("Executing comments statistics query")
comments_stats_query = f"""
SELECT r.created_by, COUNT(DISTINCT r.id) as comments_count
FROM reaction r
JOIN shout s ON r.shout = s.id AND s.deleted_at IS NULL
WHERE r.created_by IN ({placeholders}) AND r.deleted_at IS NULL
AND r.kind IN ('COMMENT', 'QUOTE')
GROUP BY r.created_by
"""
comments_stats = {row[0]: row[1] for row in session.execute(text(comments_stats_query), params)}
logger.debug(f"Comments stats retrieved: {comments_stats}")
2025-08-31 22:12:18 +03:00
# 👥 Статистика по количеству уникальных авторов, на которых подписан данный автор
logger.debug("Executing authors statistics query")
authors_stats_query = f"""
SELECT follower, COUNT(DISTINCT following) as authors_count
FROM author_follower
WHERE follower IN ({placeholders})
GROUP BY follower
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
"""
2025-08-31 22:12:18 +03:00
authors_stats = {row[0]: row[1] for row in session.execute(text(authors_stats_query), params)}
logger.debug(f"Authors stats retrieved: {authors_stats}")
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
# ⭐ Статистика по рейтингу публикаций (сумма реакций на публикации автора)
logger.debug("Executing rating_shouts statistics query")
rating_shouts_stats_query = f"""
SELECT sa.author,
2025-09-01 06:16:44 +03:00
COALESCE(SUM(CASE
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
WHEN r.kind IN ('LIKE', 'AGREE', 'ACCEPT', 'PROOF', 'CREDIT') THEN 1
WHEN r.kind IN ('DISLIKE', 'DISAGREE', 'REJECT', 'DISPROOF') THEN -1
ELSE 0
2025-09-01 06:16:44 +03:00
END), 0) as rating_shouts
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
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
2025-09-01 06:16:44 +03:00
LEFT JOIN reaction r ON s.id = r.shout AND r.deleted_at IS NULL
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
WHERE sa.author IN ({placeholders})
GROUP BY sa.author
"""
rating_shouts_stats = {
row[0]: row[1] for row in session.execute(text(rating_shouts_stats_query), params)
}
logger.debug(f"Rating shouts stats retrieved: {rating_shouts_stats}")
# ⭐ Статистика по рейтингу комментариев (реакции на комментарии автора)
logger.debug("Executing rating_comments statistics query")
rating_comments_stats_query = f"""
SELECT r1.created_by,
2025-09-01 06:16:44 +03:00
COALESCE(SUM(CASE
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
WHEN r2.kind IN ('LIKE', 'AGREE', 'ACCEPT', 'PROOF', 'CREDIT') THEN 1
WHEN r2.kind IN ('DISLIKE', 'DISAGREE', 'REJECT', 'DISPROOF') THEN -1
ELSE 0
2025-09-01 06:16:44 +03:00
END), 0) as rating_comments
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
FROM reaction r1
2025-09-01 06:16:44 +03:00
LEFT JOIN reaction r2 ON r1.id = r2.reply_to AND r2.deleted_at IS NULL
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
WHERE r1.created_by IN ({placeholders}) AND r1.deleted_at IS NULL
AND r1.kind IN ('COMMENT', 'QUOTE')
GROUP BY r1.created_by
"""
rating_comments_stats = {
row[0]: row[1] for row in session.execute(text(rating_comments_stats_query), params)
}
logger.debug(f"Rating comments stats retrieved: {rating_comments_stats}")
2025-08-31 22:12:18 +03:00
# 💬 Статистика по вызванным комментариям (ответы на комментарии + комментарии на посты)
logger.debug("Executing replies_count statistics query")
# Ответы на комментарии автора
replies_to_comments_query = f"""
SELECT r1.created_by as author_id, COUNT(DISTINCT r2.id) as replies_count
FROM reaction r1
JOIN reaction r2 ON r1.id = r2.reply_to AND r2.deleted_at IS NULL
WHERE r1.created_by IN ({placeholders}) AND r1.deleted_at IS NULL
AND r1.kind IN ('COMMENT', 'QUOTE')
AND r2.kind IN ('COMMENT', 'QUOTE')
GROUP BY r1.created_by
"""
replies_to_comments_stats = {
row[0]: row[1] for row in session.execute(text(replies_to_comments_query), params)
}
logger.debug(f"Replies to comments stats retrieved: {replies_to_comments_stats}")
# Комментарии на посты автора
comments_on_posts_query = f"""
SELECT sa.author as author_id, COUNT(DISTINCT r.id) as replies_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
JOIN reaction r ON s.id = r.shout AND r.deleted_at IS NULL
WHERE sa.author IN ({placeholders})
AND r.kind IN ('COMMENT', 'QUOTE')
GROUP BY sa.author
"""
comments_on_posts_stats = {
row[0]: row[1] for row in session.execute(text(comments_on_posts_query), params)
}
logger.debug(f"Comments on posts stats retrieved: {comments_on_posts_stats}")
# Объединяем статистику
replies_count_stats = {}
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
for author_id in author_ids:
2025-08-31 22:12:18 +03:00
replies_to_comments = replies_to_comments_stats.get(author_id, 0)
comments_on_posts = comments_on_posts_stats.get(author_id, 0)
replies_count_stats[author_id] = replies_to_comments + comments_on_posts
logger.debug(f"Combined replies count stats: {replies_count_stats}")
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
# 👁️ Статистика по просмотрам публикаций (используем ViewedStorage для получения агрегированных данных)
logger.debug("Calculating viewed_shouts statistics from ViewedStorage")
from services.viewed import ViewedStorage
viewed_shouts_stats = {}
# Получаем общие просмотры для всех публикаций каждого автора
for author_id in author_ids:
total_views = 0
# Получаем все публикации автора и суммируем их просмотры
author_shouts_query = """
SELECT s.slug
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 = :author_id
"""
shout_rows = session.execute(text(author_shouts_query), {"author_id": author_id})
for shout_row in shout_rows:
shout_slug = shout_row[0]
shout_views = ViewedStorage.get_shout(shout_slug=shout_slug)
total_views += shout_views
viewed_shouts_stats[author_id] = total_views
logger.debug(f"Viewed shouts stats calculated: {viewed_shouts_stats}")
# 🎯 Формируем результат с добавлением полной статистики
logger.debug("Building final result with comprehensive statistics")
2025-08-26 14:12:49 +03:00
result = []
for author in authors:
try:
# Получаем словарь с учетом прав доступа
author_dict = author.dict()
author_dict["stat"] = {
"shouts": shouts_stats.get(author.id, 0),
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
"topics": topics_stats.get(author.id, 0),
"coauthors": coauthors_stats.get(author.id, 0),
2025-08-26 14:12:49 +03:00
"followers": followers_stats.get(author.id, 0),
2025-08-31 22:12:18 +03:00
"authors": authors_stats.get(author.id, 0),
[0.9.17] - 2025-08-31 ### 👥 Author Statistics Enhancement - **📊 Полная статистика авторов**: Добавлены все недостающие счётчики в AuthorStat - `topics`: Количество уникальных тем, в которых участвовал автор - `coauthors`: Количество соавторов - `replies_count`: Количество вызванных комментариев - `rating_shouts`: Рейтинг публикаций автора (сумма реакций LIKE/AGREE/ACCEPT/PROOF/CREDIT минус DISLIKE/DISAGREE/REJECT/DISPROOF) - `rating_comments`: Рейтинг комментариев автора (реакции на его комментарии) - `replies_count`: Количество вызванных комментариев - `comments`: Количество созданных комментариев и цитат - `viewed_shouts`: Общее количество просмотров всех публикаций автора - **🔄 Улучшенная сортировка**: Поддержка сортировки по всем новым полям статистики - **⚡ Оптимизированные запросы**: Batch-запросы для получения всей статистики одним вызовом - **🧪 Подробное логирование**: Эмодзи-маркеры для каждого типа статистики ### 🔧 Technical Implementation - **Resolvers**: Обновлён `load_authors_by` для включения всех счётчиков - **Database**: Оптимизированные SQL-запросы с JOIN для статистики - **Caching**: Интеграция с ViewedStorage для подсчёта просмотров - **GraphQL Schema**: Обновлён тип AuthorStat с новыми полями
2025-08-31 20:01:40 +03:00
"rating_shouts": rating_shouts_stats.get(author.id, 0),
"rating_comments": rating_comments_stats.get(author.id, 0),
"comments": comments_stats.get(author.id, 0),
"replies_count": replies_count_stats.get(author.id, 0),
"viewed_shouts": viewed_shouts_stats.get(author.id, 0),
2025-08-26 14:12:49 +03:00
}
result.append(author_dict)
# Кешируем каждого автора отдельно для использования в других функциях
# Важно: кэшируем полный словарь для админов
logger.debug(f"Caching author {author.id}")
await cache_author(author.dict())
except Exception as e:
logger.error(f"Error processing author {getattr(author, 'id', 'unknown')}: {e}")
# Продолжаем обработку других авторов
continue
logger.debug(f"Successfully processed {len(result)} authors")
return result
except Exception as e:
logger.error(f"Error in fetch_authors_with_stats: {e}")
logger.error(f"Traceback: {traceback.format_exc()}")
raise
2025-03-22 11:47:19 +03:00
# Используем универсальную функцию для кеширования запросов
2025-09-03 12:44:24 +03:00
# Для фильтра по топику принудительно обновляем кеш (временное решение для отладки)
force_refresh = by is not None and by.get("topic") is not None
if force_refresh and by:
logger.debug(f"🚨 Force refresh enabled for topic filter: {by.get('topic')}")
cached_result = await cached_query(cache_key, fetch_authors_with_stats, force_refresh=force_refresh)
2025-08-26 14:12:49 +03:00
logger.debug(f"Cached result: {cached_result}")
return cached_result
2025-03-22 11:47:19 +03:00
# Функция для инвалидации кеша авторов
async def invalidate_authors_cache(author_id=None) -> None:
2025-03-22 11:47:19 +03:00
"""
Инвалидирует кеши авторов при изменении данных.
Args:
2025-08-26 14:12:49 +03:00
author_id: Опциональный ID автора для точечной инвалидации. Если не указан, инвалидируются все кеши авторов.
2025-03-22 11:47:19 +03:00
"""
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 12:15:06 +03:00
# Получаем author_id автора, если есть
2025-03-22 11:47:19 +03:00
with local_session() as session:
2025-07-31 18:55:59 +03:00
author = session.query(Author).where(Author.id == author_id).first()
2025-05-21 01:34:02 +03:00
if author and Author.id:
2025-05-29 12:15:06 +03:00
specific_keys.append(f"author:id:{Author.id}")
2025-03-22 11:47:19 +03: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 17:47:11 +03:00
2024-04-17 18:32:23 +03:00
@mutation.field("update_author")
2023-10-23 17:47:11 +03:00
@login_required
async def update_author(_: None, info: GraphQLResolveInfo, profile: dict[str, Any]) -> CommonResult:
"""Update author profile"""
2025-05-22 04:34:30 +03:00
author_id = info.context.get("author", {}).get("id")
2025-05-21 01:34:02 +03:00
is_admin = info.context.get("is_admin", False)
2025-05-22 04:34:30 +03:00
if not author_id:
return CommonResult(error="unauthorized", author=None)
2024-03-18 15:01:10 +03:00
try:
with local_session() as session:
2025-05-22 04:34:30 +03:00
author = session.query(Author).where(Author.id == author_id).first()
2024-03-18 15:01:10 +03:00
if author:
Author.update(author, profile)
session.add(author)
session.commit()
2025-05-22 04:34:30 +03:00
author_query = select(Author).where(Author.id == author_id)
2024-06-11 22:46:35 +03:00
result = get_with_stat(author_query)
if result:
author_with_stat = result[0]
if isinstance(author_with_stat, Author):
2025-05-21 01:34:02 +03:00
# Кэшируем полную версию для админов
author_dict = author_with_stat.dict(is_admin)
2025-06-26 17:19:42 +03:00
_t = asyncio.create_task(cache_author(author_dict))
2025-05-29 12:37:39 +03:00
2025-05-21 01:34:02 +03:00
# Возвращаем обычную полную версию, т.к. это владелец
return CommonResult(error=None, author=author)
# Если мы дошли до сюда, значит автор не найден
return CommonResult(error="Author not found", author=None)
2024-03-18 15:01:10 +03:00
except Exception as exc:
2024-03-18 15:01:43 +03:00
logger.error(traceback.format_exc())
return CommonResult(error=str(exc), author=None)
2023-10-23 17:47:11 +03:00
2024-04-17 18:32:23 +03:00
@query.field("get_authors_all")
async def get_authors_all(_: None, info: GraphQLResolveInfo) -> list[Any]:
"""Get all authors"""
2025-05-21 01:34:02 +03:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 04:34:30 +03: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 11:47:19 +03:00
2024-04-17 18:32:23 +03:00
@query.field("get_author")
async def get_author(
2025-08-17 16:33:54 +03:00
_: None, info: GraphQLResolveInfo, slug: str | None = None, author_id: int | None = None
) -> dict[str, Any] | None:
"""Get specific author by slug or ID"""
2025-05-21 01:34:02 +03:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 04:34:30 +03:00
is_admin = info.context.get("is_admin", False)
2025-05-29 12:37:39 +03:00
2024-03-14 09:59:38 +03:00
author_dict = None
2024-02-24 13:22:35 +03:00
try:
2025-09-01 09:07:37 +03:00
logger.debug(f"🔍 get_author called with slug='{slug}', author_id={author_id}")
resolved_author_id = get_author_id_from(slug=slug, user="", author_id=author_id)
logger.debug(f"🔍 get_author_id_from returned: {resolved_author_id}")
if not resolved_author_id:
msg = "cant find"
raise ValueError(msg)
2025-05-29 12:37:39 +03:00
2025-09-01 06:16:44 +03:00
# Всегда используем новую логику статистики из get_authors_with_stats
# Это гарантирует консистентность с load_authors_by
try:
filter_by: AuthorsBy = {}
if slug:
filter_by["slug"] = slug
2025-09-01 09:07:37 +03:00
logger.debug(f"🔍 Using slug filter: {slug}")
elif resolved_author_id:
filter_by["id"] = resolved_author_id
logger.debug(f"🔍 Using id filter: {resolved_author_id}")
2025-09-01 06:16:44 +03:00
authors_with_stats = await get_authors_with_stats(limit=1, offset=0, by=filter_by)
if authors_with_stats and len(authors_with_stats) > 0:
author_dict = authors_with_stats[0]
# Кэшируем полные данные
_t = asyncio.create_task(cache_author(author_dict))
else:
# Fallback к старому методу если автор не найден
2025-08-31 22:29:40 +03:00
with local_session() as session:
if slug:
author = session.query(Author).filter_by(slug=slug).first()
else:
2025-09-01 09:07:37 +03:00
author = session.query(Author).filter_by(id=resolved_author_id).first()
2025-08-31 22:29:40 +03:00
if author:
author_dict = author.dict(is_admin)
2025-09-01 06:16:44 +03:00
except Exception as e:
logger.error(f"Error getting author stats: {e}")
# Fallback к старому методу
with local_session() as session:
if slug:
author = session.query(Author).filter_by(slug=slug).first()
else:
2025-09-01 09:07:37 +03:00
author = session.query(Author).filter_by(id=resolved_author_id).first()
2025-09-01 06:16:44 +03:00
if author:
author_dict = author.dict(is_admin)
2024-03-29 14:44:44 +03:00
except ValueError:
pass
2024-04-09 22:02:26 +03:00
except Exception as exc:
2024-04-17 18:32:23 +03:00
logger.error(f"{exc}:\n{traceback.format_exc()}")
2024-03-28 20:36:35 +03:00
return author_dict
2023-12-28 01:37:54 +03:00
2024-04-17 18:32:23 +03:00
@query.field("load_authors_by")
2025-06-26 17:19:42 +03: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 20:34:51 +03:00
try:
# Получаем ID текущего пользователя и флаг админа из контекста
viewer_id = info.context.get("author", {}).get("id")
info.context.get("is_admin", False)
2025-05-29 12:37:39 +03:00
2025-06-26 17:19:42 +03: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 20:34:51 +03:00
# Используем оптимизированную функцию для получения авторов
2025-05-30 08:54:20 +03:00
return await get_authors_with_stats(limit, offset, by, viewer_id)
2025-05-26 20:34:51 +03:00
except Exception as exc:
logger.error(f"{exc}:\n{traceback.format_exc()}")
return []
2024-03-12 16:18:07 +03:00
@query.field("load_authors_search")
async def load_authors_search(_: None, info: GraphQLResolveInfo, **kwargs: Any) -> list[Any]:
2025-08-23 10:47:52 +03:00
"""Search for authors by name or bio using Muvera search service"""
text = kwargs.get("text", "")
limit = kwargs.get("limit", 10)
offset = kwargs.get("offset", 0)
if not text or len(text.strip()) < 2:
return []
try:
# Use Muvera search service for authors
search_results = await search_service.search_authors(text, limit, offset)
if not search_results:
return []
# Extract author IDs from search results
author_ids = [int(result["id"]) for result in search_results if result.get("id", "").isdigit()]
if not author_ids:
return []
# Fetch full author data from database
with local_session() as session:
authors = session.query(Author).where(Author.id.in_(author_ids)).all()
# Sort by search relevance (maintain order from search results)
author_dict = {author.id: author for author in authors}
sorted_authors = [author_dict.get(aid) for aid in author_ids if aid in author_dict]
return [author.dict() for author in sorted_authors if author]
except Exception as e:
logger.exception(f"Error in author search for '{text}': {e}")
return []
2025-05-29 12:37:39 +03:00
2025-08-17 17:56:31 +03:00
def get_author_id_from(slug: str | None = None, user: str | None = None, author_id: int | None = None) -> int | None:
"""Get author ID from different identifiers"""
2025-03-22 11:47:19 +03:00
try:
if author_id:
return author_id
with local_session() as session:
author = None
if slug:
2025-07-31 18:55:59 +03:00
author = session.query(Author).where(Author.slug == slug).first()
2025-03-22 11:47:19 +03:00
if author:
return int(author.id)
2025-03-22 11:47:19 +03:00
if user:
2025-07-31 18:55:59 +03:00
author = session.query(Author).where(Author.id == user).first()
2025-03-22 11:47:19 +03:00
if author:
return int(author.id)
2025-03-22 11:47:19 +03:00
except Exception as exc:
logger.error(exc)
return None
2024-05-30 07:12:00 +03:00
2024-04-17 18:32:23 +03:00
@query.field("get_author_follows")
async def get_author_follows(
2025-08-17 16:33:54 +03:00
_, info: GraphQLResolveInfo, slug: str | None = None, user: str | None = None, author_id: int | None = None
) -> dict[str, Any]:
"""Get entities followed by author"""
2025-05-21 01:34:02 +03:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 04:34:30 +03:00
viewer_id = info.context.get("author", {}).get("id")
is_admin = info.context.get("is_admin", False)
2025-05-29 12:37:39 +03:00
2025-03-22 11:47:19 +03: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 14:09:11 +03:00
2025-05-21 01:34:02 +03:00
# Получаем данные из кэша
followed_authors_raw = await get_cached_follower_authors(author_id)
2025-03-22 11:47:19 +03:00
followed_topics = await get_cached_follower_topics(author_id)
2025-05-29 12:37:39 +03:00
2025-05-21 01:34:02 +03: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 18:29:46 +03:00
# temp_author - это объект Author, который мы хотим сериализовать
# current_user_id - ID текущего авторизованного пользователя (может быть None)
# is_admin - булево значение, является ли текущий пользователь админом
2025-05-22 04:34:30 +03: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 11:47:19 +03:00
2025-08-31 22:42:21 +03:00
followed_communities = DEFAULT_COMMUNITIES # TODO: get followed communities
2025-03-22 11:47:19 +03:00
return {
"authors": followed_authors,
"topics": followed_topics,
2025-08-31 22:42:21 +03:00
"communities": followed_communities,
2025-03-22 11:47:19 +03:00
"shouts": [],
"error": None,
2025-03-22 11:47:19 +03:00
}
2024-02-23 21:10:11 +03:00
2024-02-24 13:22:35 +03:00
2024-04-17 18:32:23 +03:00
@query.field("get_author_follows_topics")
async def get_author_follows_topics(
_,
_info: GraphQLResolveInfo,
2025-08-17 16:33:54 +03:00
slug: str | None = None,
user: str | None = None,
author_id: int | None = None,
) -> list[Any]:
"""Get topics followed by author"""
2025-03-22 11:47:19 +03: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 21:10:11 +03:00
2024-04-17 18:32:23 +03:00
@query.field("get_author_follows_authors")
async def get_author_follows_authors(
2025-08-17 16:33:54 +03:00
_, info: GraphQLResolveInfo, slug: str | None = None, user: str | None = None, author_id: int | None = None
) -> list[Any]:
"""Get authors followed by author"""
2025-05-21 01:34:02 +03:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 04:34:30 +03:00
viewer_id = info.context.get("author", {}).get("id")
is_admin = info.context.get("is_admin", False)
2025-05-29 12:37:39 +03:00
2025-03-22 11:47:19 +03: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 11:47:19 +03:00
if not author_id:
return []
2025-05-22 04:34:30 +03:00
2025-05-21 01:34:02 +03:00
# Получаем данные из кэша
followed_authors_raw = await get_cached_follower_authors(author_id)
2025-05-29 12:37:39 +03:00
2025-05-21 01:34:02 +03:00
# Фильтруем чувствительные данные авторов
followed_authors = []
for author_data in followed_authors_raw:
# Создаем объект автора для использования метода dict
temp_author = Author()
for key, value in author_data.items():
[0.9.13] - 2025-08-27 ### 🚨 Исправлено - **Удалено поле username из модели Author**: Поле `username` больше не является частью модели `Author` - Убрано свойство `@property def username` из `orm/author.py` - Обновлены все сервисы для использования `email` или `slug` вместо `username` - Исправлены резолверы для исключения `username` при обработке данных автора - Поле `username` теперь используется только в JWT токенах для совместимости ### 🧪 Исправлено - **E2E тесты админ-панели**: Полностью переработаны E2E тесты для работы с реальным API - Тесты теперь делают реальные HTTP запросы к GraphQL API - Бэкенд для тестов использует выделенную тестовую БД (`test_e2e.db`) - Создан фикстура `backend_server` для запуска тестового сервера - Добавлен фикстура `create_test_users_in_backend_db` для регистрации пользователей через API - Убраны несуществующие GraphQL запросы (`get_community_stats`) - Тесты корректно работают с системой ролей и правами администратора ### �� Техническое - **Рефакторинг аутентификации**: Упрощена логика работы с пользователями - Убраны зависимости от несуществующих полей в ORM моделях - Обновлены сервисы аутентификации для корректной работы без `username` - Исправлены все места использования `username` в коде - **Улучшена тестовая инфраструктура**: - Тесты теперь используют реальный HTTP API вместо прямых DB проверок - Правильная изоляция тестовых данных через отдельную БД - Корректная работа с системой ролей и правами
2025-08-27 12:15:01 +03:00
if hasattr(temp_author, key) and key != "username": # username - это свойство, нельзя устанавливать
2025-05-21 01:34:02 +03:00
setattr(temp_author, key, value)
# Добавляем отфильтрованную версию
2025-05-21 18:29:46 +03:00
# temp_author - это объект Author, который мы хотим сериализовать
# current_user_id - ID текущего авторизованного пользователя (может быть None)
# is_admin - булево значение, является ли текущий пользователь админом
2025-05-22 04:34:30 +03: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 12:37:39 +03:00
2025-03-22 11:47:19 +03:00
return followed_authors
2023-10-23 17:47:11 +03:00
def create_author(**kwargs) -> Author:
2025-07-02 22:30:21 +03:00
"""
Create new author with default community roles
Args:
**kwargs: Author data including user_id, slug, name, etc.
Returns:
Created Author object
"""
2025-03-22 11:47:19 +03:00
author = Author()
# Use setattr to avoid MyPy complaints about Column assignment
2025-09-01 10:53:38 +03:00
author.update(
{
"id": kwargs.get("user_id"), # Связь с user_id из системы авторизации
"slug": kwargs.get("slug"), # Идентификатор из системы авторизации
"created_at": int(time.time()),
"updated_at": int(time.time()),
"name": kwargs.get("name") or kwargs.get("slug"), # если не указано
}
)
2025-03-22 11:47:19 +03:00
2023-11-28 22:07:53 +03:00
with local_session() as session:
2025-03-22 11:47:19 +03:00
session.add(author)
2025-07-02 22:30:21 +03:00
session.flush() # Получаем ID автора
# Добавляем автора в основное сообщество с дефолтными ролями
target_community_id = kwargs.get("community_id", 1) # По умолчанию основное сообщество
# Получаем сообщество для назначения дефолтных ролей
2025-07-31 18:55:59 +03:00
community = session.query(Community).where(Community.id == target_community_id).first()
2025-07-02 22:30:21 +03:00
if community:
2025-07-25 01:04:15 +03:00
default_roles = community.get_default_roles()
2025-07-02 22:30:21 +03:00
# Создаем CommunityAuthor с дефолтными ролями
community_author = CommunityAuthor(
community_id=target_community_id, author_id=author.id, roles=",".join(default_roles)
)
session.add(community_author)
logger.info(f"Создана запись CommunityAuthor для автора {author.id} с ролями: {default_roles}")
# Добавляем автора в подписчики сообщества
follower = CommunityFollower(community=target_community_id, follower=int(author.id))
session.add(follower)
logger.info(f"Автор {author.id} добавлен в подписчики сообщества {target_community_id}")
2025-03-22 11:47:19 +03:00
session.commit()
2025-07-02 22:30:21 +03:00
logger.info(f"Автор {author.id} успешно создан с ролями в сообществе {target_community_id}")
2025-03-22 11:47:19 +03:00
return author
2024-02-21 12:34:12 +03:00
2024-04-17 18:32:23 +03: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-21 01:34:02 +03:00
# Получаем ID текущего пользователя и флаг админа из контекста
2025-05-22 04:34:30 +03:00
viewer_id = info.context.get("author", {}).get("id")
is_admin = info.context.get("is_admin", False)
2025-05-29 12:37:39 +03: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 11:47:19 +03:00
if not author_id:
return []
2025-05-29 12:37:39 +03:00
2025-05-21 01:34:02 +03:00
# Получаем данные из кэша
followers_raw = await get_cached_author_followers(author_id)
2025-05-29 12:37:39 +03:00
2025-05-21 01:34:02 +03:00
# Фильтруем чувствительные данные авторов
followers = []
for follower_data in followers_raw:
# Создаем объект автора для использования метода dict
temp_author = Author()
for key, value in follower_data.items():
[0.9.13] - 2025-08-27 ### 🚨 Исправлено - **Удалено поле username из модели Author**: Поле `username` больше не является частью модели `Author` - Убрано свойство `@property def username` из `orm/author.py` - Обновлены все сервисы для использования `email` или `slug` вместо `username` - Исправлены резолверы для исключения `username` при обработке данных автора - Поле `username` теперь используется только в JWT токенах для совместимости ### 🧪 Исправлено - **E2E тесты админ-панели**: Полностью переработаны E2E тесты для работы с реальным API - Тесты теперь делают реальные HTTP запросы к GraphQL API - Бэкенд для тестов использует выделенную тестовую БД (`test_e2e.db`) - Создан фикстура `backend_server` для запуска тестового сервера - Добавлен фикстура `create_test_users_in_backend_db` для регистрации пользователей через API - Убраны несуществующие GraphQL запросы (`get_community_stats`) - Тесты корректно работают с системой ролей и правами администратора ### �� Техническое - **Рефакторинг аутентификации**: Упрощена логика работы с пользователями - Убраны зависимости от несуществующих полей в ORM моделях - Обновлены сервисы аутентификации для корректной работы без `username` - Исправлены все места использования `username` в коде - **Улучшена тестовая инфраструктура**: - Тесты теперь используют реальный HTTP API вместо прямых DB проверок - Правильная изоляция тестовых данных через отдельную БД - Корректная работа с системой ролей и правами
2025-08-27 12:15:01 +03:00
if hasattr(temp_author, key) and key != "username": # username - это свойство, нельзя устанавливать
2025-05-21 01:34:02 +03:00
setattr(temp_author, key, value)
# Добавляем отфильтрованную версию
2025-05-21 18:29:46 +03:00
# temp_author - это объект Author, который мы хотим сериализовать
# current_user_id - ID текущего авторизованного пользователя (может быть None)
# is_admin - булево значение, является ли текущий пользователь админом
2025-05-22 04:34:30 +03: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 12:37:39 +03:00
2024-05-21 01:40:57 +03:00
return followers