reader-oneloop
Some checks failed
Deploy on push / deploy (push) Failing after 8s

This commit is contained in:
Untone 2024-08-07 12:38:15 +03:00
parent 3febfff1db
commit 7a8f0a1c21

View File

@ -10,6 +10,7 @@ from sqlalchemy.sql.expression import (
select, select,
text, text,
) )
from sqlalchemy.dialects.postgresql import array_agg
from orm.author import Author, AuthorFollower from orm.author import Author, AuthorFollower
from orm.reaction import Reaction, ReactionKind from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutAuthor, ShoutTopic, ShoutReactionsFollower from orm.shout import Shout, ShoutAuthor, ShoutTopic, ShoutReactionsFollower
@ -20,23 +21,23 @@ from services.db import local_session
from utils.logger import root_logger as logger from utils.logger import root_logger as logger
from services.schema import query from services.schema import query
from services.search import search_text from services.search import search_text
# from services.viewed import ViewedStorage FIXME: use in separate resolver
def query_shouts(): def query_shouts():
""" """
Базовый запрос для получения публикаций, с подзапросами статистики. Базовый запрос для получения публикаций с подзапросами статистики, авторов и тем.
:return: (Запрос для получения публикаций, aliased_reaction) :return: Запрос для получения публикаций.
""" """
# Создаем алиасы для таблиц для избежания конфликтов имен # Создаем алиасы для таблиц для избежания конфликтов имен
aliased_reaction = aliased(Reaction) aliased_reaction = aliased(Reaction)
shout_author = aliased(ShoutAuthor)
shout_topic = aliased(ShoutTopic)
# Подзапрос для получения статистики реакций # Основной запрос с подзапросами для получения статистики, авторов и тем
reaction_stats_subquery = ( q = (
select( select(
aliased_reaction.shout.label("shout_id"), Shout,
func.count(case((aliased_reaction.body.is_not(None), 1))).label("comments_stat"), func.count(case((aliased_reaction.body.is_not(None), 1))).label("comments_stat"),
func.sum( func.sum(
case( case(
@ -46,21 +47,38 @@ def query_shouts():
) )
).label("rating_stat"), ).label("rating_stat"),
func.max(aliased_reaction.created_at).label("last_reacted_at"), func.max(aliased_reaction.created_at).label("last_reacted_at"),
array_agg(
func.json_build_object(
"id",
Author.id,
"name",
Author.name,
"slug",
Author.slug,
"pic",
Author.pic,
) )
.where(aliased_reaction.deleted_at.is_(None)) ).label("authors"),
.group_by(aliased_reaction.shout) array_agg(
.subquery() func.json_build_object(
"id",
Topic.id,
"title",
Topic.title,
"body",
Topic.body,
"slug",
Topic.slug,
) )
).label("topics"),
q = (
select(
Shout,
reaction_stats_subquery.c.comments_stat,
reaction_stats_subquery.c.rating_stat,
reaction_stats_subquery.c.last_reacted_at,
) )
.outerjoin(reaction_stats_subquery, reaction_stats_subquery.c.shout_id == Shout.id) .outerjoin(aliased_reaction, aliased_reaction.shout == Shout.id)
.outerjoin(shout_author, shout_author.shout == Shout.id)
.outerjoin(Author, Author.id == shout_author.author)
.outerjoin(shout_topic, shout_topic.shout == Shout.id)
.outerjoin(Topic, Topic.id == shout_topic.topic)
.where(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None))) .where(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None)))
.group_by(Shout.id)
) )
return q, aliased_reaction return q, aliased_reaction
@ -78,62 +96,15 @@ def get_shouts_with_stats(q, limit, offset=0, author_id=None):
# Основной запрос для получения публикаций и объединения их с подзапросами # Основной запрос для получения публикаций и объединения их с подзапросами
q = q.limit(limit).offset(offset) q = q.limit(limit).offset(offset)
shout_author = aliased(ShoutAuthor)
shout_topic = aliased(ShoutTopic)
# Выполнение запроса и обработка результатов # Выполнение запроса и обработка результатов
with local_session() as session: with local_session() as session:
results = session.execute(q, {"author_id": author_id}).all() if author_id else session.execute(q).all() results = session.execute(q, {"author_id": author_id}).all() if author_id else session.execute(q).all()
# Получаем все ID публикаций
shout_ids = [row.Shout.id for row in results]
# Получаем авторов для этих публикаций
authors = (
session.query(shout_author.shout, Author.id, Author.name, Author.slug, Author.pic)
.join(Author, Author.id == shout_author.author)
.filter(shout_author.shout.in_(shout_ids))
.all()
)
# Получаем темы для этих публикаций
topics = (
session.query(shout_topic.shout, Topic.id, Topic.title, Topic.body, Topic.slug)
.join(Topic, Topic.id == shout_topic.topic)
.filter(shout_topic.shout.in_(shout_ids))
.all()
)
# Группировка авторов и тем по публикациям
authors_by_shout = {}
for author in authors:
shout_id = author[0] # ID публикации
if shout_id not in authors_by_shout:
authors_by_shout[shout_id] = []
authors_by_shout[shout_id].append({
"id": author[1],
"name": author[2],
"slug": author[3],
"pic": author[4],
})
topics_by_shout = {}
for topic in topics:
shout_id = topic[0] # ID публикации
if shout_id not in topics_by_shout:
topics_by_shout[shout_id] = []
topics_by_shout[shout_id].append({
"id": topic[1],
"title": topic[2],
"body": topic[3],
"slug": topic[4],
})
# Формирование списка публикаций с их данными # Формирование списка публикаций с их данными
shouts = [] shouts = []
for shout, comments_stat, rating_stat, last_reacted_at in results: for shout, comments_stat, rating_stat, last_reacted_at, authors, topics in results:
shout.authors = authors_by_shout.get(shout.id, []) shout.authors = authors or []
shout.topics = topics_by_shout.get(shout.id, []) shout.topics = topics or []
shout.stat = { shout.stat = {
"viewed": 0, # FIXME: use separate resolver "viewed": 0, # FIXME: use separate resolver
"followers": 0, # FIXME: implement followers_stat "followers": 0, # FIXME: implement followers_stat
@ -233,24 +204,20 @@ async def get_shout(_, info, slug: str):
""" """
try: try:
with local_session() as session: with local_session() as session:
q, aliased_reaction = query_shouts() q = query_shouts()
q = q.filter(Shout.slug == slug) q = q.filter(Shout.slug == slug)
q = q.group_by(Shout.id)
results = session.execute(q).first() results = session.execute(q).first()
if results: if results:
[ [shout, commented_stat, rating_stat, last_reaction_at, authors, topics] = results
shout,
commented_stat,
rating_stat,
last_reaction_at,
] = results
shout.stat = { shout.stat = {
"commented": commented_stat, "commented": commented_stat,
"rating": rating_stat, "rating": rating_stat,
"last_reacted_at": last_reaction_at, "last_reacted_at": last_reaction_at,
} }
shout.authors = authors or []
shout.topics = topics or []
for author_caption in ( for author_caption in (
session.query(ShoutAuthor) session.query(ShoutAuthor)
@ -296,9 +263,8 @@ async def load_shouts_by(_, _info, options):
:param options: Опции фильтрации и сортировки. :param options: Опции фильтрации и сортировки.
:return: Список публикаций, удовлетворяющих критериям. :return: Список публикаций, удовлетворяющих критериям.
""" """
# Базовый запрос # Базовый запрос
q, aliased_reaction = query_shouts() q = query_shouts()
# Применение фильтров # Применение фильтров
filters = options.get("filters", {}) filters = options.get("filters", {})
@ -333,7 +299,7 @@ async def load_shouts_feed(_, info, options):
:return: Список публикаций для ленты. :return: Список публикаций для ленты.
""" """
with local_session() as session: with local_session() as session:
q, aliased_reaction = query_shouts() q = query_shouts()
# Применение фильтров # Применение фильтров
filters = options.get("filters", {}) filters = options.get("filters", {})
@ -430,7 +396,6 @@ async def load_shouts_random_top(_, _info, options):
:param options: Опции фильтрации и сортировки. :param options: Опции фильтрации и сортировки.
:return: Список случайных публикаций. :return: Список случайных публикаций.
""" """
aliased_reaction = aliased(Reaction) aliased_reaction = aliased(Reaction)
subquery = ( subquery = (
@ -456,8 +421,7 @@ async def load_shouts_random_top(_, _info, options):
random_limit = options.get("random_limit", 100) random_limit = options.get("random_limit", 100)
if random_limit: if random_limit:
subquery = subquery.limit(random_limit) subquery = subquery.limit(random_limit)
q, aliased_reaction = query_shouts() q = query_shouts().where(Shout.id.in_(subquery))
q = q.where(Shout.id.in_(subquery))
q = q.order_by(func.random()) q = q.order_by(func.random())
limit = options.get("limit", 10) limit = options.get("limit", 10)
return get_shouts_with_stats(q, limit) return get_shouts_with_stats(q, limit)
@ -474,8 +438,7 @@ async def load_shouts_random_topic(_, info, limit: int = 10):
""" """
[topic] = get_topics_random(None, None, 1) [topic] = get_topics_random(None, None, 1)
if topic: if topic:
q, aliased_reaction = query_shouts() q = query_shouts().filter(Shout.topics.any(slug=topic.slug))
q = q.filter(Shout.topics.any(slug=topic.slug))
q = q.order_by(desc(Shout.created_at)) q = q.order_by(desc(Shout.created_at))
shouts = get_shouts_with_stats(q, limit) shouts = get_shouts_with_stats(q, limit)
if shouts: if shouts:
@ -497,8 +460,7 @@ async def load_shouts_coauthored(_, info, limit=50, offset=0):
author_id = info.context.get("author", {}).get("id") author_id = info.context.get("author", {}).get("id")
if not author_id: if not author_id:
return [] return []
q, aliased_reaction = query_shouts() q = query_shouts().filter(Shout.authors.any(id=author_id))
q = q.filter(Shout.authors.any(id=author_id))
return get_shouts_with_stats(q, limit, offset=offset) return get_shouts_with_stats(q, limit, offset=offset)
@ -516,7 +478,7 @@ async def load_shouts_discussed(_, info, limit=50, offset=0):
author_id = info.context.get("author", {}).get("id") author_id = info.context.get("author", {}).get("id")
if not author_id: if not author_id:
return [] return []
q, aliased_reaction = query_shouts() q = query_shouts()
q = q.outerjoin( q = q.outerjoin(
Reaction, Reaction,
and_( and_(