This commit is contained in:
@@ -115,168 +115,189 @@ async def get_authors_with_stats(
|
||||
"""
|
||||
Выполняет запрос к базе данных для получения авторов со статистикой.
|
||||
"""
|
||||
logger.debug(f"Выполняем запрос на получение авторов со статистикой: limit={limit}, offset={offset}, by={by}")
|
||||
try:
|
||||
with local_session() as session:
|
||||
# Базовый запрос для получения авторов
|
||||
base_query = select(Author).where(Author.deleted_at.is_(None))
|
||||
|
||||
with local_session() as session:
|
||||
# Базовый запрос для получения авторов
|
||||
base_query = select(Author).where(Author.deleted_at.is_(None))
|
||||
# vars for statistics sorting
|
||||
stats_sort_field = None
|
||||
default_sort_applied = False
|
||||
|
||||
# vars for statistics sorting
|
||||
stats_sort_field = None
|
||||
default_sort_applied = False
|
||||
|
||||
if by:
|
||||
if "order" in by:
|
||||
order_value = by["order"]
|
||||
logger.debug(f"Found order field with value: {order_value}")
|
||||
if order_value in ["shouts", "followers", "rating", "comments"]:
|
||||
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")
|
||||
default_sort_applied = True
|
||||
else:
|
||||
# If order is not a stats field, treat it as a regular field
|
||||
column = getattr(Author, order_value or "", "")
|
||||
if column:
|
||||
base_query = base_query.order_by(sql_desc(column))
|
||||
logger.debug(f"Applying sorting by column: {order_value}")
|
||||
if by:
|
||||
if "order" in by:
|
||||
order_value = by["order"]
|
||||
logger.debug(f"Found order field with value: {order_value}")
|
||||
if order_value in ["shouts", "followers", "rating", "comments"]:
|
||||
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")
|
||||
default_sort_applied = True
|
||||
else:
|
||||
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":
|
||||
# If order is not a stats field, treat it as a regular field
|
||||
column = getattr(Author, order_value or "", "")
|
||||
if column:
|
||||
base_query = base_query.order_by(sql_desc(column))
|
||||
logger.debug(f"Applying sorting by column: {order_value}")
|
||||
default_sort_applied = True
|
||||
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}")
|
||||
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 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"),
|
||||
# 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()
|
||||
)
|
||||
.select_from(AuthorFollower)
|
||||
.group_by(AuthorFollower.following)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
# Сбрасываем предыдущую сортировку и применяем новую
|
||||
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.author).order_by(
|
||||
sql_desc(func.coalesce(subquery.c.followers_count, 0))
|
||||
)
|
||||
logger.debug("Applied sorting by followers count")
|
||||
# Сбрасываем предыдущую сортировку и применяем новую
|
||||
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 followers sorting: {sql_query}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating SQL query: {e}")
|
||||
# Логирование для отладки сортировки
|
||||
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.limit(limit).offset(offset)
|
||||
# Сбрасываем предыдущую сортировку и применяем новую
|
||||
base_query = base_query.outerjoin(subquery, Author.id == subquery.c.following).order_by(
|
||||
sql_desc(func.coalesce(subquery.c.followers_count, 0))
|
||||
)
|
||||
logger.debug("Applied sorting by followers count")
|
||||
|
||||
# Получаем авторов
|
||||
authors = session.execute(base_query).scalars().unique().all()
|
||||
author_ids = [author.id for author in authors]
|
||||
# Логирование для отладки сортировки
|
||||
try:
|
||||
# Получаем SQL запрос для проверки
|
||||
sql_query = str(base_query.compile(compile_kwargs={"literal_binds": True}))
|
||||
logger.debug(f"Generated SQL query for followers sorting: {sql_query}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating SQL query: {e}")
|
||||
|
||||
if not author_ids:
|
||||
return []
|
||||
# Применяем лимит и смещение
|
||||
base_query = base_query.limit(limit).offset(offset)
|
||||
|
||||
# Логирование результатов для отладки сортировки
|
||||
if stats_sort_field:
|
||||
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
|
||||
# Получаем авторов
|
||||
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}")
|
||||
|
||||
# Оптимизированный запрос для получения статистики по публикациям для авторов
|
||||
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
|
||||
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
|
||||
"""
|
||||
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
|
||||
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
|
||||
if not author_ids:
|
||||
logger.debug("No authors found, returning empty list")
|
||||
return []
|
||||
|
||||
# Запрос на получение статистики по подписчикам для авторов
|
||||
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)}
|
||||
# Логирование результатов для отладки сортировки
|
||||
if stats_sort_field:
|
||||
logger.debug(f"Query returned {len(authors)} authors with sorting by {stats_sort_field}")
|
||||
|
||||
# Формируем результат с добавлением статистики
|
||||
result = []
|
||||
for author in authors:
|
||||
# Получаем словарь с учетом прав доступа
|
||||
author_dict = author.dict()
|
||||
author_dict["stat"] = {
|
||||
"shouts": shouts_stats.get(author.id, 0),
|
||||
"followers": followers_stats.get(author.id, 0),
|
||||
}
|
||||
# Оптимизированный запрос для получения статистики по публикациям для авторов
|
||||
logger.debug("Executing shouts statistics query")
|
||||
placeholders = ", ".join([f":id{i}" for i in range(len(author_ids))])
|
||||
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
|
||||
"""
|
||||
params = {f"id{i}": author_id for i, author_id in enumerate(author_ids)}
|
||||
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query), params)}
|
||||
logger.debug(f"Shouts stats retrieved: {shouts_stats}")
|
||||
|
||||
result.append(author_dict)
|
||||
# Запрос на получение статистики по подписчикам для авторов
|
||||
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}")
|
||||
|
||||
# Кешируем каждого автора отдельно для использования в других функциях
|
||||
# Важно: кэшируем полный словарь для админов
|
||||
await cache_author(author.dict())
|
||||
# Формируем результат с добавлением статистики
|
||||
logger.debug("Building final result with statistics")
|
||||
result = []
|
||||
for author in authors:
|
||||
try:
|
||||
# Получаем словарь с учетом прав доступа
|
||||
author_dict = author.dict()
|
||||
author_dict["stat"] = {
|
||||
"shouts": shouts_stats.get(author.id, 0),
|
||||
"followers": followers_stats.get(author.id, 0),
|
||||
}
|
||||
|
||||
return result
|
||||
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
|
||||
|
||||
# Используем универсальную функцию для кеширования запросов
|
||||
return await cached_query(cache_key, fetch_authors_with_stats)
|
||||
cached_result = await cached_query(cache_key, fetch_authors_with_stats)
|
||||
logger.debug(f"Cached result: {cached_result}")
|
||||
return cached_result
|
||||
|
||||
|
||||
# Функция для инвалидации кеша авторов
|
||||
@@ -285,8 +306,7 @@ async def invalidate_authors_cache(author_id=None) -> None:
|
||||
Инвалидирует кеши авторов при изменении данных.
|
||||
|
||||
Args:
|
||||
author_id: Опциональный ID автора для точечной инвалидации.
|
||||
Если не указан, инвалидируются все кеши авторов.
|
||||
author_id: Опциональный ID автора для точечной инвалидации. Если не указан, инвалидируются все кеши авторов.
|
||||
"""
|
||||
if author_id:
|
||||
# Точечная инвалидация конкретного автора
|
||||
|
||||
Reference in New Issue
Block a user