From f772f41784dc988587c46eaf7633efef4620c02b Mon Sep 17 00:00:00 2001 From: bniwredyc Date: Tue, 17 Jan 2023 22:07:44 +0100 Subject: [PATCH] shout rating and views count fixed --- .gitignore | 3 +- main.py | 9 +++-- resolvers/zine/_common.py | 31 --------------- resolvers/zine/load.py | 79 +++++++++++++++++++++++++++++++------ resolvers/zine/reactions.py | 31 +++++++++++++-- server.py | 6 +-- settings.py | 2 +- 7 files changed, 107 insertions(+), 54 deletions(-) delete mode 100644 resolvers/zine/_common.py diff --git a/.gitignore b/.gitignore index 576a6846..5723e88b 100644 --- a/.gitignore +++ b/.gitignore @@ -148,5 +148,6 @@ dump .vscode *dump.sql *.csv -dev-server-status.txt +dev-server.pid /resetdb.sh +backups/ diff --git a/main.py b/main.py index d324970c..f01bdc11 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import asyncio +import os from importlib import import_module from os.path import exists from ariadne import load_schema_from_path, make_executable_schema @@ -18,7 +19,7 @@ from resolvers.auth import confirm_email_handler from services.main import storages_init from services.stat.viewed import ViewedStorage from services.zine.gittask import GitTask -from settings import DEV_SERVER_STATUS_FILE_NAME, SENTRY_DSN +from settings import DEV_SERVER_PID_FILE_NAME, SENTRY_DSN # from sse.transport import GraphQLSSEHandler # from services.inbox.presence import on_connect, on_disconnect from services.inbox.sse import sse_messages @@ -50,12 +51,12 @@ async def start_up(): async def dev_start_up(): - if exists(DEV_SERVER_STATUS_FILE_NAME): + if exists(DEV_SERVER_PID_FILE_NAME): await redis.connect() return else: - with open(DEV_SERVER_STATUS_FILE_NAME, 'w', encoding='utf-8') as f: - f.write('running') + with open(DEV_SERVER_PID_FILE_NAME, 'w', encoding='utf-8') as f: + f.write(str(os.getpid())) await start_up() diff --git a/resolvers/zine/_common.py b/resolvers/zine/_common.py deleted file mode 100644 index 7046be92..00000000 --- a/resolvers/zine/_common.py +++ /dev/null @@ -1,31 +0,0 @@ -from sqlalchemy import func, case -from sqlalchemy.orm import aliased -from orm.reaction import Reaction, ReactionKind - - -def add_common_stat_columns(q): - aliased_reaction = aliased(Reaction) - - q = q.outerjoin(aliased_reaction).add_columns( - func.sum( - aliased_reaction.id - ).label('reacted_stat'), - func.sum( - case( - (aliased_reaction.body.is_not(None), 1), - else_=0 - ) - ).label('commented_stat'), - func.sum(case( - (aliased_reaction.kind == ReactionKind.AGREE, 1), - (aliased_reaction.kind == ReactionKind.DISAGREE, -1), - (aliased_reaction.kind == ReactionKind.PROOF, 1), - (aliased_reaction.kind == ReactionKind.DISPROOF, -1), - (aliased_reaction.kind == ReactionKind.ACCEPT, 1), - (aliased_reaction.kind == ReactionKind.REJECT, -1), - (aliased_reaction.kind == ReactionKind.LIKE, 1), - (aliased_reaction.kind == ReactionKind.DISLIKE, -1), - else_=0) - ).label('rating_stat')) - - return q diff --git a/resolvers/zine/load.py b/resolvers/zine/load.py index f70e8960..20330493 100644 --- a/resolvers/zine/load.py +++ b/resolvers/zine/load.py @@ -1,25 +1,48 @@ from datetime import datetime, timedelta, timezone + from sqlalchemy.orm import joinedload, aliased -from sqlalchemy.sql.expression import desc, asc, select, func +from sqlalchemy.sql.expression import desc, asc, select, func, case from auth.credentials import AuthCredentials +from base.exceptions import ObjectNotExist from base.orm import local_session from base.resolvers import query -from base.exceptions import ObjectNotExist from orm import ViewedEntry +from orm.reaction import Reaction, ReactionKind from orm.shout import Shout, ShoutAuthor -from orm.reaction import Reaction -from resolvers.zine._common import add_common_stat_columns def add_stat_columns(q): - q = q.outerjoin(ViewedEntry).add_columns(func.sum(ViewedEntry.amount).label('viewed_stat')) + aliased_reaction = aliased(Reaction) - return add_common_stat_columns(q) + q = q.outerjoin(aliased_reaction).add_columns( + func.sum( + aliased_reaction.id + ).label('reacted_stat'), + func.sum( + case( + (aliased_reaction.body.is_not(None), 1), + else_=0 + ) + ).label('commented_stat'), + func.sum(case( + # do not count comments' reactions + (aliased_reaction.replyTo.is_not(None), 0), + (aliased_reaction.kind == ReactionKind.AGREE, 1), + (aliased_reaction.kind == ReactionKind.DISAGREE, -1), + (aliased_reaction.kind == ReactionKind.PROOF, 1), + (aliased_reaction.kind == ReactionKind.DISPROOF, -1), + (aliased_reaction.kind == ReactionKind.ACCEPT, 1), + (aliased_reaction.kind == ReactionKind.REJECT, -1), + (aliased_reaction.kind == ReactionKind.LIKE, 1), + (aliased_reaction.kind == ReactionKind.DISLIKE, -1), + else_=0) + ).label('rating_stat')) + + return q def apply_filters(q, filters, user_id=None): - if filters.get("reacted") and user_id: q.join(Reaction, Reaction.createdBy == user_id) @@ -59,8 +82,25 @@ async def load_shout(_, info, slug): ).filter( Shout.deletedAt.is_(None) ).group_by(Shout.id) + try: - [shout, viewed_stat, reacted_stat, commented_stat, rating_stat] = session.execute(q).first() + [shout, reacted_stat, commented_stat, rating_stat] = session.execute(q).first() + + viewed_stat_query = select().select_from( + Shout + ).where( + Shout.id == shout.id + ).join( + ViewedEntry + ).group_by( + Shout.id + ).add_columns( + func.sum(ViewedEntry.amount).label('viewed_stat') + ) + + # Debug tip: + # print(viewed_stat_query.compile(compile_kwargs={"literal_binds": True})) + viewed_stat = session.execute(viewed_stat_query).scalar() shout.stat = { "viewed": viewed_stat, @@ -125,15 +165,32 @@ async def load_shouts_by(_, info, options): with local_session() as session: shouts = [] + shouts_map = {} - for [shout, viewed_stat, reacted_stat, commented_stat, rating_stat] in session.execute(q).unique(): + for [shout, reacted_stat, commented_stat, rating_stat] in session.execute(q).unique(): shouts.append(shout) - shout.stat = { - "viewed": viewed_stat, + "viewed": 0, "reacted": reacted_stat, "commented": commented_stat, "rating": rating_stat } + shouts_map[shout.id] = shout + + viewed_stat_query = select( + Shout.id + ).where( + Shout.id.in_(shouts_map.keys()) + ).join( + ViewedEntry + ).group_by( + Shout.id + ).add_columns( + func.sum(ViewedEntry.amount).label('viewed_stat') + ) + + for [shout_id, viewed_stat] in session.execute(viewed_stat_query).unique(): + shouts.append(shout) + shouts_map[shout_id].stat['viewed'] = viewed_stat return shouts diff --git a/resolvers/zine/reactions.py b/resolvers/zine/reactions.py index 6f1ffdd0..92b86935 100644 --- a/resolvers/zine/reactions.py +++ b/resolvers/zine/reactions.py @@ -1,5 +1,7 @@ from datetime import datetime, timedelta, timezone -from sqlalchemy import and_, asc, desc, select, text, func +from sqlalchemy import and_, asc, desc, select, text, func, case +from sqlalchemy.orm import aliased + from auth.authenticate import login_required from auth.credentials import AuthCredentials from base.orm import local_session @@ -7,11 +9,34 @@ from base.resolvers import mutation, query from orm.reaction import Reaction, ReactionKind from orm.shout import Shout, ShoutReactionsFollower from orm.user import User -from resolvers.zine._common import add_common_stat_columns def add_reaction_stat_columns(q): - return add_common_stat_columns(q) + aliased_reaction = aliased(Reaction) + + q = q.outerjoin(aliased_reaction).add_columns( + func.sum( + aliased_reaction.id + ).label('reacted_stat'), + func.sum( + case( + (aliased_reaction.body.is_not(None), 1), + else_=0 + ) + ).label('commented_stat'), + func.sum(case( + (aliased_reaction.kind == ReactionKind.AGREE, 1), + (aliased_reaction.kind == ReactionKind.DISAGREE, -1), + (aliased_reaction.kind == ReactionKind.PROOF, 1), + (aliased_reaction.kind == ReactionKind.DISPROOF, -1), + (aliased_reaction.kind == ReactionKind.ACCEPT, 1), + (aliased_reaction.kind == ReactionKind.REJECT, -1), + (aliased_reaction.kind == ReactionKind.LIKE, 1), + (aliased_reaction.kind == ReactionKind.DISLIKE, -1), + else_=0) + ).label('rating_stat')) + + return q def reactions_follow(user_id, shout_id: int, auto=False): diff --git a/server.py b/server.py index 510a0257..9f0f9cc1 100644 --- a/server.py +++ b/server.py @@ -2,7 +2,7 @@ import sys import os import uvicorn -from settings import PORT, DEV_SERVER_STATUS_FILE_NAME +from settings import PORT, DEV_SERVER_PID_FILE_NAME def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook): @@ -69,8 +69,8 @@ if __name__ == "__main__": if len(sys.argv) > 1: x = sys.argv[1] if x == "dev": - if os.path.exists(DEV_SERVER_STATUS_FILE_NAME): - os.remove(DEV_SERVER_STATUS_FILE_NAME) + if os.path.exists(DEV_SERVER_PID_FILE_NAME): + os.remove(DEV_SERVER_PID_FILE_NAME) want_reload = False if "reload" in sys.argv: print("MODE: DEV + RELOAD") diff --git a/settings.py b/settings.py index c712ddd1..0bfa9785 100644 --- a/settings.py +++ b/settings.py @@ -29,4 +29,4 @@ SESSION_TOKEN_HEADER = "Authorization" SENTRY_DSN = environ.get("SENTRY_DSN") # for local development -DEV_SERVER_STATUS_FILE_NAME = 'dev-server-status.txt' +DEV_SERVER_PID_FILE_NAME = 'dev-server.pid'