This commit is contained in:
parent
f29eb5f35a
commit
8c05589168
|
@ -1,6 +1,6 @@
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from sqlalchemy import ARRAY, Boolean, Column, ForeignKey, Integer, String
|
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String
|
||||||
|
|
||||||
from services.db import Base
|
from services.db import Base
|
||||||
|
|
||||||
|
@ -25,4 +25,4 @@ class Topic(Base):
|
||||||
community = Column(ForeignKey("community.id"), default=1)
|
community = Column(ForeignKey("community.id"), default=1)
|
||||||
oid = Column(String, nullable=True, comment="Old ID")
|
oid = Column(String, nullable=True, comment="Old ID")
|
||||||
|
|
||||||
parent_ids = Column(ARRAY(Integer), nullable=True, comment="Parent Topic IDs")
|
parent_ids = Column(JSON, nullable=True, comment="Parent Topic IDs")
|
||||||
|
|
|
@ -29,184 +29,137 @@ from utils.logger import root_logger as logger
|
||||||
|
|
||||||
def query_shouts():
|
def query_shouts():
|
||||||
"""
|
"""
|
||||||
Базовый запрос для получения публикаций с подзапросами статистики, авторов и тем.
|
Оптимизированный базовый запрос
|
||||||
"""
|
"""
|
||||||
# Подзапросы для статистики реакций
|
# Оптимизированный подзапрос статистики
|
||||||
rating_subquery = (
|
stats_subquery = (
|
||||||
select(
|
select(
|
||||||
|
Reaction.shout.label('shout_id'),
|
||||||
|
func.count(
|
||||||
|
case((Reaction.kind == ReactionKind.COMMENT.value, 1), else_=None)
|
||||||
|
).label('comments_count'),
|
||||||
func.sum(
|
func.sum(
|
||||||
case(
|
case(
|
||||||
(Reaction.kind == ReactionKind.LIKE.value, 1),
|
(Reaction.kind == ReactionKind.LIKE.value, 1),
|
||||||
(Reaction.kind == ReactionKind.DISLIKE.value, -1),
|
(Reaction.kind == ReactionKind.DISLIKE.value, -1),
|
||||||
else_=0,
|
else_=0
|
||||||
)
|
)
|
||||||
|
).label('rating'),
|
||||||
|
func.max(
|
||||||
|
case((Reaction.reply_to.is_(None), Reaction.created_at), else_=None)
|
||||||
|
).label('last_reacted_at')
|
||||||
)
|
)
|
||||||
)
|
.where(Reaction.deleted_at.is_(None))
|
||||||
.select_from(Reaction)
|
.group_by(Reaction.shout)
|
||||||
.where(and_(Reaction.shout == Shout.id, Reaction.reply_to.is_(None), Reaction.deleted_at.is_(None)))
|
.subquery()
|
||||||
.correlate(Shout)
|
|
||||||
.scalar_subquery()
|
|
||||||
.label("rating_stat")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
comments_subquery = (
|
|
||||||
select(func.count(distinct(case((Reaction.kind == ReactionKind.COMMENT.value, Reaction.id), else_=None))))
|
|
||||||
.select_from(Reaction)
|
|
||||||
.where(and_(Reaction.shout == Shout.id, Reaction.reply_to.is_(None), Reaction.deleted_at.is_(None)))
|
|
||||||
.correlate(Shout)
|
|
||||||
.scalar_subquery()
|
|
||||||
.label("comments_stat")
|
|
||||||
)
|
|
||||||
|
|
||||||
last_reaction_subquery = (
|
|
||||||
select(func.max(Reaction.created_at))
|
|
||||||
.select_from(Reaction)
|
|
||||||
.where(and_(Reaction.shout == Shout.id, Reaction.reply_to.is_(None), Reaction.deleted_at.is_(None)))
|
|
||||||
.correlate(Shout)
|
|
||||||
.scalar_subquery()
|
|
||||||
.label("last_reacted_at")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Остальные подзапросы остаются без изменений
|
|
||||||
authors_subquery = (
|
|
||||||
select(
|
|
||||||
func.json_agg(
|
|
||||||
func.json_build_object("id", Author.id, "name", Author.name, "slug", Author.slug, "pic", Author.pic)
|
|
||||||
).label("authors")
|
|
||||||
)
|
|
||||||
.select_from(ShoutAuthor)
|
|
||||||
.join(Author, ShoutAuthor.author == Author.id)
|
|
||||||
.where(ShoutAuthor.shout == Shout.id)
|
|
||||||
.correlate(Shout)
|
|
||||||
.scalar_subquery()
|
|
||||||
)
|
|
||||||
|
|
||||||
captions_subquery = (
|
|
||||||
select(
|
|
||||||
func.json_agg(func.json_build_object("author_id", Author.id, "caption", ShoutAuthor.caption)).label(
|
|
||||||
"captions"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.select_from(ShoutAuthor)
|
|
||||||
.join(Author, ShoutAuthor.author == Author.id)
|
|
||||||
.where(ShoutAuthor.shout == Shout.id)
|
|
||||||
.correlate(Shout)
|
|
||||||
.scalar_subquery()
|
|
||||||
)
|
|
||||||
|
|
||||||
topics_subquery = (
|
|
||||||
select(
|
|
||||||
func.json_agg(func.json_build_object("id", Topic.id, "title", Topic.title, "slug", Topic.slug)).label(
|
|
||||||
"topics"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.select_from(ShoutTopic)
|
|
||||||
.join(Topic, ShoutTopic.topic == Topic.id)
|
|
||||||
.where(ShoutTopic.shout == Shout.id)
|
|
||||||
.correlate(Shout)
|
|
||||||
.scalar_subquery()
|
|
||||||
)
|
|
||||||
|
|
||||||
main_topic_subquery = (
|
|
||||||
select(func.max(Topic.slug))
|
|
||||||
.select_from(ShoutTopic)
|
|
||||||
.join(Topic, ShoutTopic.topic == Topic.id)
|
|
||||||
.where(and_(ShoutTopic.shout == Shout.id, ShoutTopic.main.is_(True)))
|
|
||||||
.correlate(Shout)
|
|
||||||
.scalar_subquery()
|
|
||||||
.label("main_topic_slug")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Основной запрос
|
|
||||||
q = (
|
q = (
|
||||||
select(
|
select(Shout, stats_subquery)
|
||||||
Shout,
|
.outerjoin(stats_subquery, stats_subquery.c.shout_id == Shout.id)
|
||||||
rating_subquery,
|
.where(and_(
|
||||||
comments_subquery,
|
Shout.published_at.is_not(None),
|
||||||
last_reaction_subquery,
|
Shout.deleted_at.is_(None)
|
||||||
authors_subquery,
|
))
|
||||||
captions_subquery,
|
|
||||||
topics_subquery,
|
|
||||||
main_topic_subquery,
|
|
||||||
)
|
)
|
||||||
.outerjoin(Reaction, Reaction.shout == Shout.id)
|
|
||||||
.where(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None)))
|
|
||||||
.group_by(Shout.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
return q
|
return q
|
||||||
|
|
||||||
|
|
||||||
def get_shouts_with_stats(q, limit=20, offset=0, author_id=None):
|
def get_shouts_with_stats(q, limit=20, offset=0, author_id=None):
|
||||||
"""
|
"""
|
||||||
Получение публикаций со статистикой.
|
Оптимизированное получение данных
|
||||||
:param q: Базовый запрос публикаций
|
|
||||||
:param limit: Ограничение количества результатов
|
|
||||||
:param offset: Смещение для пагинации
|
|
||||||
:param author_id: Опциональный ID автора для фильтрации
|
|
||||||
:return: Список публикаций с статистикой
|
|
||||||
"""
|
"""
|
||||||
if author_id:
|
if author_id:
|
||||||
q = q.filter(Shout.created_by == author_id)
|
q = q.filter(Shout.created_by == author_id)
|
||||||
|
|
||||||
q = q.order_by(Shout.published_at.desc().nulls_last())
|
q = q.order_by(desc(Shout.published_at))
|
||||||
|
|
||||||
if limit:
|
if limit:
|
||||||
q = q.limit(limit)
|
q = q.limit(limit)
|
||||||
if offset:
|
if offset:
|
||||||
q = q.offset(offset)
|
q = q.offset(offset)
|
||||||
|
|
||||||
shouts = []
|
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
results = session.execute(q).all()
|
# 1. Получаем шауты одним запросом
|
||||||
|
shouts_result = session.execute(q).all()
|
||||||
|
shout_ids = [row.Shout.id for row in shouts_result]
|
||||||
|
|
||||||
for [
|
if not shout_ids:
|
||||||
shout,
|
return []
|
||||||
rating_stat,
|
|
||||||
comments_stat,
|
|
||||||
last_reacted_at,
|
|
||||||
authors_json,
|
|
||||||
captions_json,
|
|
||||||
topics_json,
|
|
||||||
main_topic_slug,
|
|
||||||
] in results:
|
|
||||||
shout_dict = shout.dict()
|
|
||||||
|
|
||||||
# Добавление статистики просмотров
|
# 2. Получаем авторов и топики пакетным запросом
|
||||||
viewed_stat = ViewedStorage.get_shout(shout_slug=shout.slug, shout_id=shout.id)
|
authors_and_topics = session.execute(
|
||||||
|
select(
|
||||||
# Обработка авторов и их подписей
|
ShoutAuthor.shout.label('shout_id'),
|
||||||
authors = authors_json or []
|
Author.id.label('author_id'),
|
||||||
captions = captions_json or []
|
Author.name.label('author_name'),
|
||||||
|
Author.slug.label('author_slug'),
|
||||||
for author in authors:
|
Author.pic.label('author_pic'),
|
||||||
caption_item = next((c for c in captions if c["author_id"] == author["id"]), None)
|
ShoutAuthor.caption.label('author_caption'),
|
||||||
if caption_item:
|
Topic.id.label('topic_id'),
|
||||||
author["caption"] = caption_item["caption"]
|
Topic.title.label('topic_title'),
|
||||||
|
Topic.slug.label('topic_slug'),
|
||||||
# Обработка тем
|
ShoutTopic.is_main.label('topic_is_main')
|
||||||
topics = topics_json or []
|
|
||||||
for topic in topics:
|
|
||||||
topic["is_main"] = topic["slug"] == main_topic_slug
|
|
||||||
|
|
||||||
# Формирование финальной структуры
|
|
||||||
shout_dict.update(
|
|
||||||
{
|
|
||||||
"authors": authors,
|
|
||||||
"topics": topics,
|
|
||||||
"main_topic": main_topic_slug,
|
|
||||||
"stat": {
|
|
||||||
"viewed": viewed_stat or 0,
|
|
||||||
"rating": rating_stat or 0,
|
|
||||||
"commented": comments_stat or 0,
|
|
||||||
"last_reacted_at": last_reacted_at,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
.outerjoin(Author, ShoutAuthor.author == Author.id)
|
||||||
|
.outerjoin(ShoutTopic, ShoutTopic.shout == ShoutAuthor.shout)
|
||||||
|
.outerjoin(Topic, ShoutTopic.topic == Topic.id)
|
||||||
|
.where(ShoutAuthor.shout.in_(shout_ids))
|
||||||
|
).all()
|
||||||
|
|
||||||
shouts.append(shout_dict)
|
# 3. Группируем данные эффективно
|
||||||
|
shouts_data = {}
|
||||||
|
for row in shouts_result:
|
||||||
|
shout = row.Shout.__dict__
|
||||||
|
shout_id = shout['id']
|
||||||
|
shouts_data[shout_id] = {
|
||||||
|
**shout,
|
||||||
|
'stat': {
|
||||||
|
'viewed': ViewedStorage.get_shout(shout_id=shout_id) or 0,
|
||||||
|
'commented': row.comments_count or 0,
|
||||||
|
'rating': row.rating or 0,
|
||||||
|
'last_reacted_at': row.last_reacted_at
|
||||||
|
},
|
||||||
|
'authors': [],
|
||||||
|
'topics': set() # используем set для уникальности
|
||||||
|
}
|
||||||
|
|
||||||
return shouts
|
# 4. Заполняем связанные данные
|
||||||
|
for row in authors_and_topics:
|
||||||
|
shout_data = shouts_data[row.shout_id]
|
||||||
|
|
||||||
|
# Добавляем автора
|
||||||
|
author = {
|
||||||
|
'id': row.author_id,
|
||||||
|
'name': row.author_name,
|
||||||
|
'slug': row.author_slug,
|
||||||
|
'pic': row.author_pic,
|
||||||
|
'caption': row.author_caption
|
||||||
|
}
|
||||||
|
if author not in shout_data['authors']:
|
||||||
|
shout_data['authors'].append(author)
|
||||||
|
|
||||||
|
# Добавляем топик если есть
|
||||||
|
if row.topic_id:
|
||||||
|
topic = {
|
||||||
|
'id': row.topic_id,
|
||||||
|
'title': row.topic_title,
|
||||||
|
'slug': row.topic_slug,
|
||||||
|
'is_main': row.topic_is_main
|
||||||
|
}
|
||||||
|
shout_data['topics'].add(tuple(topic.items()))
|
||||||
|
|
||||||
|
# 5. Финальная обработка и сортировка
|
||||||
|
result = []
|
||||||
|
for shout_data in shouts_data.values():
|
||||||
|
# Конвертируем topics обратно в список словарей и сортируем
|
||||||
|
shout_data['topics'] = sorted(
|
||||||
|
[dict(t) for t in shout_data['topics']],
|
||||||
|
key=lambda x: (not x['is_main'], x['id'])
|
||||||
|
)
|
||||||
|
result.append(shout_data)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def filter_my(info, session, q):
|
def filter_my(info, session, q):
|
||||||
|
@ -519,10 +472,10 @@ async def load_shouts_coauthored(_, info, limit=50, offset=0):
|
||||||
"""
|
"""
|
||||||
Загрузка публикаций, написанных в соавторстве с пользователем.
|
Загрузка публикаций, написанных в соавторстве с пользователем.
|
||||||
|
|
||||||
:param info: Информаци<EFBFBD><EFBFBD> о контексте GraphQL.
|
:param info: Информаци о контексте GraphQL.
|
||||||
:param limit: Максимальное количество публикаций.
|
:param limit: Максимальное количество публикаций.
|
||||||
:param offset: Смещение для пагинации.
|
:param offset: Смещение для пагинации.
|
||||||
:return: Список публикаций в соавто<EFBFBD><EFBFBD>стве.
|
:return: Список публикаций в соавтостве.
|
||||||
"""
|
"""
|
||||||
author_id = info.context.get("author", {}).get("id")
|
author_id = info.context.get("author", {}).get("id")
|
||||||
if not author_id:
|
if not author_id:
|
||||||
|
@ -540,7 +493,7 @@ async def load_shouts_discussed(_, info, limit=50, offset=0):
|
||||||
|
|
||||||
:param info: Информация о контексте GraphQL.
|
:param info: Информация о контексте GraphQL.
|
||||||
:param limit: Максимальное количество публикаций.
|
:param limit: Максимальное количество публикаций.
|
||||||
:param offset: Смещене для пагинации.
|
:param offset: Смещне для пагинации.
|
||||||
:return: Список публикаций, обсужденных пользователем.
|
:return: Список публикаций, обсужденных пользователем.
|
||||||
"""
|
"""
|
||||||
author_id = info.context.get("author", {}).get("id")
|
author_id = info.context.get("author", {}).get("id")
|
||||||
|
@ -560,7 +513,7 @@ async def load_shouts_discussed(_, info, limit=50, offset=0):
|
||||||
|
|
||||||
async def reacted_shouts_updates(follower_id: int, limit=50, offset=0) -> List[Shout]:
|
async def reacted_shouts_updates(follower_id: int, limit=50, offset=0) -> List[Shout]:
|
||||||
"""
|
"""
|
||||||
Обновляет публикации, на которые подписан автор, с учетом реакций.
|
Обновляет публикации, на которые подписан автор, с учетом реакци.
|
||||||
|
|
||||||
:param follower_id: Идентификатор подписчика.
|
:param follower_id: Идентификатор подписчика.
|
||||||
:param limit: Количество публикаций для загрузки.
|
:param limit: Количество публикаций для загрузки.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user