diff --git a/resolvers/reaction.py b/resolvers/reaction.py index 958d8b6c..a2a3af3e 100644 --- a/resolvers/reaction.py +++ b/resolvers/reaction.py @@ -307,6 +307,7 @@ async def delete_reaction(_, info, reaction_id: int): session.delete(r) session.commit() await notify_reaction(reaction_dict, 'delete') + return {'error': None, 'reaction': reaction_dict} except Exception as exc: return {'error': f'cannot delete reaction: {exc}'} diff --git a/resolvers/stat.py b/resolvers/stat.py index 95e73349..32efda6d 100644 --- a/resolvers/stat.py +++ b/resolvers/stat.py @@ -17,9 +17,10 @@ def add_topic_stat_columns(q): aliased_shout_author = aliased(ShoutAuthor) aliased_topic_follower = aliased(TopicFollower) aliased_shout_topic = aliased(ShoutTopic) + aliased_shout = aliased(Shout) q = ( - q.outerjoin(aliased_shout_topic, aliased_shout_topic.topic == Topic.id) + q.outerjoin(aliased_shout_topic, and_(aliased_shout_topic.topic == Topic.id, aliased_shout.published_at.is_not(None))) .add_columns( func.count(distinct(aliased_shout_topic.shout)).label('shouts_stat') ) diff --git a/services/cache.py b/services/cache.py index d91f787c..41c7b83c 100644 --- a/services/cache.py +++ b/services/cache.py @@ -24,20 +24,67 @@ async def set_author_cache(author: dict): await redis.execute('SET', f'user:{author.get("user")}', payload) await redis.execute('SET', f'author:{author.get("id")}', payload) + # update stat all field for followers' caches in list + followers_str = await redis.execute('GET', f'author:{author.get("id")}:followers') + followers = [] + if followers_str: + followers = json.loads(followers_str) + if isinstance(followers, list): + for follower in followers: + follower_follows_authors = [] + follower_follows_authors_str = await redis.execute('GET', f'author:{author.get("id")}:follows-authors') + if follower_follows_authors_str: + follower_follows_authors = json.loads(follower_follows_authors_str) + c = 0 + for old_author in follower_follows_authors: + if int(old_author.get('id')) == int(author.get('id', 0)): + follower_follows_authors[c] = author + break # exit the loop since we found and updated the author + c += 1 + else: + # author not found in the list, so add the new author with the updated stat field + follower_follows_authors.append(author) + + # update stat field for all authors' caches in list + follows_str = await redis.execute('GET', f'author:{author.get("id")}:follows-authors') + follows_authors = [] + if follows_str: + follows_authors = json.loads(follows_str) + if isinstance(follows_authors, list): + for followed_author in follows_authors: + followed_author_followers = [] + followed_author_followers_str = await redis.execute('GET', f'author:{author.get("id")}:followers') + if followed_author_followers_str: + followed_author_followers = json.loads(followed_author_followers_str) + c = 0 + for old_follower in followed_author_followers: + if int(old_follower.get('id')) == int(author.get('id', 0)): + followed_author_followers[c] = author + break # exit the loop since we found and updated the author + c += 1 + else: + # author not found in the list, so add the new author with the updated stat field + followed_author_followers.append(author) + +async def update_author_followers_cache(author_id: int, followers): + updated_followers = [f.dict() if isinstance(f, Author) else f for f in followers] + payload = json.dumps( + updated_followers, + cls=CustomJSONEncoder, + ) + await redis.execute('SET', f'author:{author_id}:followers', payload) + author_str = await redis.execute('GET', f'author:{author_id}') + if author_str: + author = json.loads(author_str) + author['stat']['followers'] = len(updated_followers) + await set_author_cache(author) + async def set_topic_cache(topic: dict): payload = json.dumps(topic, cls=CustomJSONEncoder) await redis.execute('SET', f'topic:{topic.get("id")}', payload) -async def update_author_followers_cache(author_id: int, followers): - payload = json.dumps( - [f.dict() if isinstance(f, Author) else f for f in followers], - cls=CustomJSONEncoder, - ) - await redis.execute('SET', f'author:{author_id}:followers', payload) - - async def set_follows_topics_cache(follows, author_id: int): try: payload = json.dumps( @@ -54,12 +101,19 @@ async def set_follows_topics_cache(follows, author_id: int): async def set_follows_authors_cache(follows, author_id: int): + updated_follows = [a.dict() if isinstance(a, Author) else a for a in follows] try: payload = json.dumps( - [a.dict() if isinstance(a, Author) else a for a in follows], + updated_follows, cls=CustomJSONEncoder, ) await redis.execute('SET', f'author:{author_id}:follows-authors', payload) + # update author everywhere + author_str = await redis.execute('GET', f'author:{author_id}') + if author_str: + author = json.loads(author_str) + author['stat']['authors'] = len(updated_follows) + await set_author_cache(author) except Exception as exc: import traceback @@ -110,12 +164,13 @@ async def update_followers_for_author( def after_shout_update(_mapper, _connection, shout: Shout): + logger.info('after shout update') # Main query to get authors associated with the shout through ShoutAuthor authors_query = ( select(Author) .select_from(ShoutAuthor) # Select from ShoutAuthor .join(Author, Author.id == ShoutAuthor.author) # Join with Author - .where(ShoutAuthor.shout == shout.id) # Filter by shout.id + .filter(ShoutAuthor.shout == shout.id) # Filter by shout.id ) for author_with_stat in get_with_stat(authors_query): @@ -123,6 +178,7 @@ def after_shout_update(_mapper, _connection, shout: Shout): def after_reaction_update(mapper, connection, reaction: Reaction): + logger.info('after reaction update') try: author_subquery = select(Author).where(Author.id == reaction.created_by) replied_author_subquery = ( @@ -157,6 +213,7 @@ def after_reaction_update(mapper, connection, reaction: Reaction): def after_author_update(_mapper, _connection, author: Author): + logger.info('after author update') q = select(Author).where(Author.id == author.id) result = get_with_stat(q) if result: @@ -165,24 +222,28 @@ def after_author_update(_mapper, _connection, author: Author): def after_topic_follower_insert(_mapper, _connection, target: TopicFollower): + logger.info(target) asyncio.create_task( handle_topic_follower_change(target.topic, target.follower, True) ) def after_topic_follower_delete(_mapper, _connection, target: TopicFollower): + logger.info(target) asyncio.create_task( handle_topic_follower_change(target.topic, target.follower, False) ) def after_author_follower_insert(_mapper, _connection, target: AuthorFollower): + logger.info(target) asyncio.create_task( handle_author_follower_change(target.author, target.follower, True) ) def after_author_follower_delete(_mapper, _connection, target: AuthorFollower): + logger.info(target) asyncio.create_task( handle_author_follower_change(target.author, target.follower, False) ) @@ -191,6 +252,7 @@ def after_author_follower_delete(_mapper, _connection, target: AuthorFollower): async def handle_author_follower_change( author_id: int, follower_id: int, is_insert: bool ): + logger.info(author_id) author_query = select(Author).select_from(Author).filter(Author.id == author_id) [author] = get_with_stat(author_query) follower_query = select(Author).select_from(Author).filter(Author.id == follower_id) @@ -224,6 +286,7 @@ async def handle_author_follower_change( async def handle_topic_follower_change( topic_id: int, follower_id: int, is_insert: bool ): + logger.info(topic_id) topic_query = select(Topic).filter(Topic.id == topic_id) [topic] = get_with_stat(topic_query) follower_query = select(Author).filter(Author.id == follower_id)