shout rating and views count fixed

This commit is contained in:
bniwredyc 2023-01-17 22:07:44 +01:00
parent 50a76e8534
commit f772f41784
7 changed files with 107 additions and 54 deletions

3
.gitignore vendored
View File

@ -148,5 +148,6 @@ dump
.vscode .vscode
*dump.sql *dump.sql
*.csv *.csv
dev-server-status.txt dev-server.pid
/resetdb.sh /resetdb.sh
backups/

View File

@ -1,4 +1,5 @@
import asyncio import asyncio
import os
from importlib import import_module from importlib import import_module
from os.path import exists from os.path import exists
from ariadne import load_schema_from_path, make_executable_schema 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.main import storages_init
from services.stat.viewed import ViewedStorage from services.stat.viewed import ViewedStorage
from services.zine.gittask import GitTask 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 sse.transport import GraphQLSSEHandler
# from services.inbox.presence import on_connect, on_disconnect # from services.inbox.presence import on_connect, on_disconnect
from services.inbox.sse import sse_messages from services.inbox.sse import sse_messages
@ -50,12 +51,12 @@ async def start_up():
async def dev_start_up(): async def dev_start_up():
if exists(DEV_SERVER_STATUS_FILE_NAME): if exists(DEV_SERVER_PID_FILE_NAME):
await redis.connect() await redis.connect()
return return
else: else:
with open(DEV_SERVER_STATUS_FILE_NAME, 'w', encoding='utf-8') as f: with open(DEV_SERVER_PID_FILE_NAME, 'w', encoding='utf-8') as f:
f.write('running') f.write(str(os.getpid()))
await start_up() await start_up()

View File

@ -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

View File

@ -1,25 +1,48 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from sqlalchemy.orm import joinedload, aliased 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 auth.credentials import AuthCredentials
from base.exceptions import ObjectNotExist
from base.orm import local_session from base.orm import local_session
from base.resolvers import query from base.resolvers import query
from base.exceptions import ObjectNotExist
from orm import ViewedEntry from orm import ViewedEntry
from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutAuthor 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): 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): def apply_filters(q, filters, user_id=None):
if filters.get("reacted") and user_id: if filters.get("reacted") and user_id:
q.join(Reaction, Reaction.createdBy == user_id) q.join(Reaction, Reaction.createdBy == user_id)
@ -59,8 +82,25 @@ async def load_shout(_, info, slug):
).filter( ).filter(
Shout.deletedAt.is_(None) Shout.deletedAt.is_(None)
).group_by(Shout.id) ).group_by(Shout.id)
try: 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 = { shout.stat = {
"viewed": viewed_stat, "viewed": viewed_stat,
@ -125,15 +165,32 @@ async def load_shouts_by(_, info, options):
with local_session() as session: with local_session() as session:
shouts = [] 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) shouts.append(shout)
shout.stat = { shout.stat = {
"viewed": viewed_stat, "viewed": 0,
"reacted": reacted_stat, "reacted": reacted_stat,
"commented": commented_stat, "commented": commented_stat,
"rating": rating_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 return shouts

View File

@ -1,5 +1,7 @@
from datetime import datetime, timedelta, timezone 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.authenticate import login_required
from auth.credentials import AuthCredentials from auth.credentials import AuthCredentials
from base.orm import local_session from base.orm import local_session
@ -7,11 +9,34 @@ from base.resolvers import mutation, query
from orm.reaction import Reaction, ReactionKind from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutReactionsFollower from orm.shout import Shout, ShoutReactionsFollower
from orm.user import User from orm.user import User
from resolvers.zine._common import add_common_stat_columns
def add_reaction_stat_columns(q): 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): def reactions_follow(user_id, shout_id: int, auto=False):

View File

@ -2,7 +2,7 @@ import sys
import os import os
import uvicorn 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): def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook):
@ -69,8 +69,8 @@ if __name__ == "__main__":
if len(sys.argv) > 1: if len(sys.argv) > 1:
x = sys.argv[1] x = sys.argv[1]
if x == "dev": if x == "dev":
if os.path.exists(DEV_SERVER_STATUS_FILE_NAME): if os.path.exists(DEV_SERVER_PID_FILE_NAME):
os.remove(DEV_SERVER_STATUS_FILE_NAME) os.remove(DEV_SERVER_PID_FILE_NAME)
want_reload = False want_reload = False
if "reload" in sys.argv: if "reload" in sys.argv:
print("MODE: DEV + RELOAD") print("MODE: DEV + RELOAD")

View File

@ -29,4 +29,4 @@ SESSION_TOKEN_HEADER = "Authorization"
SENTRY_DSN = environ.get("SENTRY_DSN") SENTRY_DSN = environ.get("SENTRY_DSN")
# for local development # for local development
DEV_SERVER_STATUS_FILE_NAME = 'dev-server-status.txt' DEV_SERVER_PID_FILE_NAME = 'dev-server.pid'