From 43f0c517b3efdc3ed8bbfdc7ee2827a13cc35211 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 23 Jan 2024 04:34:48 +0300 Subject: [PATCH] load-random-top-fix --- resolvers/follower.py | 4 ++-- resolvers/reaction.py | 47 ++++++++++++++++++++++++++++++------------- resolvers/reader.py | 34 +++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/resolvers/follower.py b/resolvers/follower.py index 96f2ccb7..e2f36288 100644 --- a/resolvers/follower.py +++ b/resolvers/follower.py @@ -105,8 +105,8 @@ async def get_my_followed(_, info): communities = [] with local_session() as session: author = session.query(Author).filter(Author.user == user_id).first() - if author: - author_id = int(author.id) + if isinstance(author, Author): + author_id = author.id authors_query = ( session.query(Author) diff --git a/resolvers/reaction.py b/resolvers/reaction.py index 8352ffbf..f80a8a7e 100644 --- a/resolvers/reaction.py +++ b/resolvers/reaction.py @@ -2,7 +2,7 @@ import time from typing import List import logging -from sqlalchemy import and_, asc, case, desc, func, select, text +from sqlalchemy import and_, asc, case, desc, func, select, text, or_ from sqlalchemy.orm import aliased, joinedload from orm.author import Author @@ -18,8 +18,7 @@ logging.basicConfig() logger = logging.getLogger("\t[resolvers.reaction]\t") logger.setLevel(logging.DEBUG) -def add_stat_columns(q): - aliased_reaction = aliased(Reaction) +def add_stat_columns(q, aliased_reaction): q = q.outerjoin(aliased_reaction).add_columns( func.sum(case((aliased_reaction.kind == ReactionKind.COMMENT.value, 1), else_=0)).label("comments_stat"), @@ -28,7 +27,7 @@ def add_stat_columns(q): func.max(case((aliased_reaction.kind != ReactionKind.COMMENT.value, None),else_=aliased_reaction.created_at)).label("last_comment"), ) - return q, aliased_reaction + return q def reactions_follow(author_id, shout_id, auto=False): @@ -270,7 +269,8 @@ async def update_reaction(_, info, rid, reaction): user_id = info.context["user_id"] with local_session() as session: q = select(Reaction).filter(Reaction.id == rid) - q, aliased_reaction = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) q = q.group_by(Reaction.id) [r, commented_stat, likes_stat, dislikes_stat, _l] = session.execute(q).unique().one() @@ -385,7 +385,8 @@ async def load_reactions_by(_, info, by, limit=50, offset=0): ) # calculate counters - q, aliased_reaction = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) # filter q = apply_reaction_filters(by, q) @@ -424,23 +425,41 @@ async def load_reactions_by(_, info, by, limit=50, offset=0): return reactions + def reacted_shouts_updates(follower_id: int, limit=50, offset=0) -> List[Shout]: shouts: List[Shout] = [] with local_session() as session: - author = session.query(Author).where(Author.id == follower_id).first() + author = session.query(Author).filter(Author.id == follower_id).first() if author: - shouts = ( + # Shouts where follower is the author + shouts_author, aliased_reaction = add_stat_columns( session.query(Shout) - .join(Reaction, Reaction.shout == Shout.id) - .options(joinedload(Reaction.created_by)) + .outerjoin(Reaction, and_(Reaction.shout_id == Shout.id, Reaction.created_by == follower_id)) + .outerjoin(Author, Shout.authors.any(id=follower_id)) + .options(joinedload(Shout.reactions), joinedload(Shout.authors)), + aliased(Reaction) + ).filter(Author.id == follower_id).group_by(Shout.id).all() + + # Shouts where follower has reactions + shouts_reactions = ( + session.query(Shout) + .join(Reaction, Reaction.shout_id == Shout.id) + .options(joinedload(Shout.reactions), joinedload(Shout.authors)) .filter(Reaction.created_by == follower_id) - .filter(Reaction.created_at > author.last_seen) - .limit(limit) - .offset(offset) + .group_by(Shout.id) .all() ) - return shouts + # Combine shouts from both queries + shouts = list(set(shouts_author + shouts_reactions)) + + # Sort shouts by the `last_comment` field + shouts.sort(key=lambda shout: shout.last_comment, reverse=True) + + # Apply limit and offset + shouts = shouts[offset: offset + limit] + + return shouts @query.field("load_shouts_followed") @login_required diff --git a/resolvers/reader.py b/resolvers/reader.py index 845d69f0..e6617fad 100644 --- a/resolvers/reader.py +++ b/resolvers/reader.py @@ -47,8 +47,8 @@ async def get_shout(_, _info, slug=None, shout_id=None): joinedload(Shout.authors), joinedload(Shout.topics), ) - - q, _ar = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) if slug is not None: q = q.filter(Shout.slug == slug) @@ -122,7 +122,8 @@ async def load_shouts_by(_, _info, options): ) # stats - q, _ar = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) # filters q = apply_filters(q, options.get("filters", {})) @@ -209,6 +210,7 @@ async def load_shouts_drafts(_, info): async def load_shouts_feed(_, info, options): user_id = info.context["user_id"] + shouts = [] with local_session() as session: reader = session.query(Author).filter(Author.user == user_id).first() if reader: @@ -231,7 +233,8 @@ async def load_shouts_feed(_, info, options): .where(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None), Shout.id.in_(subquery))) ) - q, _ar = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) q = apply_filters(q, options.get("filters", {}), reader.id) order_by = options.get("order_by", Shout.published_at) @@ -244,7 +247,6 @@ async def load_shouts_feed(_, info, options): # print(q.compile(compile_kwargs={"literal_binds": True})) - shouts = [] for [shout, reacted_stat, commented_stat, _last_comment] in session.execute(q).unique(): main_topic = ( session.query(Topic.slug) @@ -331,7 +333,8 @@ async def load_shouts_unrated(_, info, limit: int = 50, offset: int = 0): # 3 or fewer votes is 0, 1, 2 or 3 votes (null, reaction id1, reaction id2, reaction id3) q = q.having(func.count(distinct(Reaction.id)) <= 4) - q, _ar = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) q = q.group_by(Shout.id).order_by(func.random()).limit(limit).offset(offset) user_id = info.context.get("user_id") @@ -382,7 +385,13 @@ async def load_shouts_random_top(_, _info, options): subquery = select(Shout.id).outerjoin(aliased_reaction).where(Shout.deleted_at.is_(None)) subquery = apply_filters(subquery, options.get("filters", {})) - subquery = subquery.group_by(Shout.id).order_by(desc(get_rating_func(aliased_reaction))) + subquery = subquery.group_by(Shout.id).order_by(desc(func.sum( + case( + (aliased_reaction.kind == str(ReactionKind.LIKE.value), 1), + (aliased_reaction.kind == str(ReactionKind.DISLIKE.value), -1), + else_=0, + ) + ))) random_limit = options.get("random_limit") if random_limit: @@ -397,16 +406,18 @@ async def load_shouts_random_top(_, _info, options): .where(Shout.id.in_(subquery)) ) - q, _ar = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) - limit = options.get("limit", 10) - q = q.group_by(Shout.id).order_by(func.random()).limit(limit) + # Calculate the difference between likes_stat and dislikes_stat and use it for ordering + q = q.group_by(Shout.id).order_by(desc(func.sum(aliased_reaction.likes_stat - aliased_reaction.dislikes_stat))).limit(limit) # print(q.compile(compile_kwargs={"literal_binds": True})) return get_shouts_from_query(q) + @query.field("load_shouts_random_topic") async def load_shouts_random_topic(_, info, limit: int = 10): topic = get_random_topic() @@ -420,7 +431,8 @@ async def load_shouts_random_topic(_, info, limit: int = 10): .filter(and_(Shout.deleted_at.is_(None), Shout.visibility == "public", Shout.topics.any(slug=topic.slug))) ) - q, _ar = add_stat_columns(q) + aliased_reaction = aliased(Reaction) + q = add_stat_columns(q, aliased_reaction) q = q.group_by(Shout.id).order_by(desc(Shout.created_at)).limit(limit)