From ad1273995d9aebe5d2d8f70e0c3cc497ee63bf41 Mon Sep 17 00:00:00 2001 From: Aevann1 Date: Fri, 8 Jul 2022 20:06:54 +0200 Subject: [PATCH] notifications rework --- files/__main__.py | 2 + files/classes/user.py | 12 +- files/helpers/jinja2.py | 2 +- files/routes/__init__.py | 3 +- files/routes/front.py | 169 ---------------- files/routes/notifications.py | 258 ++++++++++++++++++++++++ files/routes/posts.py | 2 +- files/routes/users.py | 4 +- files/templates/header.html | 2 +- files/templates/notifications.html | 51 ++--- files/templates/submission_listing.html | 4 +- sql/20220708-notification-rework.sql | 2 + 12 files changed, 296 insertions(+), 215 deletions(-) create mode 100644 files/routes/notifications.py create mode 100644 sql/20220708-notification-rework.sql diff --git a/files/__main__.py b/files/__main__.py index 295e26755..03ec28833 100644 --- a/files/__main__.py +++ b/files/__main__.py @@ -91,6 +91,8 @@ def before_request(): g.webview = '; wv) ' in ua g.inferior_browser = 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua + if request.path.endswith('/'): request.path = request.path[:-1] + @app.after_request def after_request(response): response.headers.add("Strict-Transport-Security", "max-age=31536000") diff --git a/files/classes/user.py b/files/classes/user.py index cf8027b12..22e3850e7 100644 --- a/files/classes/user.py +++ b/files/classes/user.py @@ -127,6 +127,7 @@ class User(Base): currently_held_lottery_tickets = Column(Integer, default=0) total_held_lottery_tickets = Column(Integer, default=0) total_lottery_winnings = Column(Integer, default=0) + last_viewed_post_notifs = Column(Integer, default=0) badges = relationship("Badge", order_by="Badge.created_utc", back_populates="user") subscriptions = relationship("Subscription", back_populates="user") @@ -447,6 +448,11 @@ class User(Base): if self.admin_level < 2: return 0 return g.db.query(ModAction).filter_by(user_id=self.id).count() + @property + @lazy + def following_ids(self): + return [x[0] for x in g.db.query(Follow.target_id).filter_by(user_id=self.id).all()] + @property @lazy def notifications_count(self): @@ -457,7 +463,7 @@ class User(Base): if not self.shadowbanned and self.admin_level < 3: notifs = notifs.join(Notification.user).filter(User.shadowbanned == None) - return notifs.count() + return notifs.count() + self.post_notifications_count @property @lazy @@ -470,9 +476,7 @@ class User(Base): @property @lazy def post_notifications_count(self): - return g.db.query(Notification).join(Comment).filter( - Notification.user_id == self.id, Notification.read == False, - Comment.author_id == AUTOJANNY_ID).count() + return g.db.query(Submission).filter(Submission.author_id.in_(self.following_ids), Submission.created_utc > self.last_viewed_post_notifs).count() @property @lazy diff --git a/files/helpers/jinja2.py b/files/helpers/jinja2.py index 3bc62b8d4..92d867bbf 100644 --- a/files/helpers/jinja2.py +++ b/files/helpers/jinja2.py @@ -58,4 +58,4 @@ def inject_constants(): "HOLE_NAME": HOLE_NAME, "HOLE_STYLE_FLAIR": HOLE_STYLE_FLAIR, "HOLE_REQUIRED": HOLE_REQUIRED, "LOTTERY_ENABLED": LOTTERY_ENABLED, "GUMROAD_LINK": GUMROAD_LINK, "DEFAULT_THEME": DEFAULT_THEME, "DESCRIPTION": DESCRIPTION, "PERMS": PERMS, - "PROCOINS_ENABLED": PROCOINS_ENABLED, "has_sidebar": has_sidebar, "has_logo": has_logo, "FP": FP} + "PROCOINS_ENABLED": PROCOINS_ENABLED, "has_sidebar": has_sidebar, "has_logo": has_logo, "FP": FP, "NOTIF_MODACTION_JL_MIN": NOTIF_MODACTION_JL_MIN} diff --git a/files/routes/__init__.py b/files/routes/__init__.py index b1bdab958..1699c6466 100644 --- a/files/routes/__init__.py +++ b/files/routes/__init__.py @@ -17,4 +17,5 @@ from .awards import * from .giphy import * from .subs import * from .lottery import * -from .polls import * \ No newline at end of file +from .polls import * +from .notifications import * \ No newline at end of file diff --git a/files/routes/front.py b/files/routes/front.py index e579308f4..4445d716a 100644 --- a/files/routes/front.py +++ b/files/routes/front.py @@ -6,175 +6,6 @@ from files.__main__ import app, cache, limiter from files.classes.submission import Submission from files.helpers.awards import award_timers - -@app.post("/clear") -@auth_required -def clear(v): - notifs = g.db.query(Notification).join(Notification.comment).filter(Notification.read == False, Notification.user_id == v.id).all() - for n in notifs: - n.read = True - g.db.add(n) - return {"message": "Notifications cleared!"} - -@app.get("/unread") -@auth_required -def unread(v): - listing = g.db.query(Notification, Comment).join(Notification.comment).filter( - Notification.read == False, - Notification.user_id == v.id, - Comment.is_banned == False, - Comment.deleted_utc == 0, - Comment.author_id != AUTOJANNY_ID, - ).order_by(Notification.created_utc.desc()).all() - - for n, c in listing: - n.read = True - g.db.add(n) - - return {"data":[x[1].json for x in listing]} - - -@app.get("/notifications") -@auth_required -def notifications(v): - try: page = max(int(request.values.get("page", 1)), 1) - except: page = 1 - - messages = request.values.get('messages') - modmail = request.values.get('modmail') - modactions = request.values.get('modactions') - posts = request.values.get('posts') - reddit = request.values.get('reddit') - if modmail and v.admin_level >= 2: - comments = g.db.query(Comment).filter(Comment.sentto==2).order_by(Comment.id.desc()).offset(25*(page-1)).limit(26).all() - next_exists = (len(comments) > 25) - listing = comments[:25] - elif messages: - if v and (v.shadowbanned or v.admin_level > 2): - comments = g.db.query(Comment).filter(Comment.sentto != None, or_(Comment.author_id==v.id, Comment.sentto==v.id), Comment.parent_submission == None, Comment.level == 1).order_by(Comment.id.desc()).offset(25*(page-1)).limit(26).all() - else: - comments = g.db.query(Comment).join(Comment.author).filter(User.shadowbanned == None, Comment.sentto != None, or_(Comment.author_id==v.id, Comment.sentto==v.id), Comment.parent_submission == None, Comment.level == 1).order_by(Comment.id.desc()).offset(25*(page-1)).limit(26).all() - - next_exists = (len(comments) > 25) - listing = comments[:25] - elif posts: - notifications = g.db.query(Notification, Comment).join(Notification.comment).filter(Notification.user_id == v.id, Comment.author_id == AUTOJANNY_ID).order_by(Notification.created_utc.desc()).offset(25 * (page - 1)).limit(101).all() - - listing = [] - - for index, x in enumerate(notifications[:100]): - n, c = x - if n.read and index > 24: break - elif not n.read: - n.read = True - c.unread = True - g.db.add(n) - if n.created_utc > 1620391248: c.notif_utc = n.created_utc - listing.append(c) - - next_exists = (len(notifications) > len(listing)) - elif modactions: - notifications = g.db.query(Notification, Comment) \ - .join(Notification.comment) \ - .filter(Notification.user_id == v.id, - Comment.body_html.like(f'%

{NOTIF_MODACTION_PREFIX}%'), - Comment.parent_submission == None, Comment.author_id == NOTIFICATIONS_ID) \ - .order_by(Notification.created_utc.desc()).offset(25 * (page - 1)).limit(101).all() - listing = [] - - for index, x in enumerate(notifications[:100]): - n, c = x - if n.read and index > 24: break - elif not n.read: - n.read = True - c.unread = True - g.db.add(n) - if n.created_utc > 1620391248: c.notif_utc = n.created_utc - listing.append(c) - - next_exists = (len(notifications) > len(listing)) - elif reddit: - notifications = g.db.query(Notification, Comment).join(Notification.comment).filter(Notification.user_id == v.id, Comment.body_html.like('%

New site mention: {NOTIF_MODACTION_PREFIX}%') - ).order_by(Notification.created_utc.desc()) - - if not (v and (v.shadowbanned or v.admin_level > 2)): - comments = comments.join(Comment.author).filter(User.shadowbanned == None) - - comments = comments.offset(25 * (page - 1)).limit(26).all() - - next_exists = (len(comments) > 25) - comments = comments[:25] - - cids = [x[0].id for x in comments] - - comms = get_comments(cids, v=v) - - listing = [] - for c, n in comments: - if n.created_utc > 1620391248: c.notif_utc = n.created_utc - if not n.read: - n.read = True - c.unread = True - g.db.add(n) - - if c.parent_submission: - if c.replies2 == None: - c.replies2 = g.db.query(Comment).filter_by(parent_comment_id=c.id).filter(or_(Comment.author_id == v.id, Comment.id.in_(cids))).all() - for x in c.replies2: - if x.replies2 == None: x.replies2 = [] - count = 0 - while count < 50 and c.parent_comment and (c.parent_comment.author_id == v.id or c.parent_comment.id in cids): - count += 1 - c = c.parent_comment - if c.replies2 == None: - c.replies2 = g.db.query(Comment).filter_by(parent_comment_id=c.id).filter(or_(Comment.author_id == v.id, Comment.id.in_(cids))).all() - for x in c.replies2: - if x.replies2 == None: - x.replies2 = g.db.query(Comment).filter_by(parent_comment_id=x.id).filter(or_(Comment.author_id == v.id, Comment.id.in_(cids))).all() - else: - while c.parent_comment: - c = c.parent_comment - c.replies2 = g.db.query(Comment).filter_by(parent_comment_id=c.id).order_by(Comment.id).all() - - if c not in listing: listing.append(c) - - g.db.commit() - - if request.headers.get("Authorization"): return {"data":[x.json for x in listing]} - - return render_template("notifications.html", - v=v, - notifications=listing, - next_exists=next_exists, - page=page, - standalone=True, - render_replies=True, - NOTIF_MODACTION_JL_MIN=NOTIF_MODACTION_JL_MIN, - ) - - @app.get("/") @app.get("/catalog") @app.get("/h/") diff --git a/files/routes/notifications.py b/files/routes/notifications.py new file mode 100644 index 000000000..5c9e0f723 --- /dev/null +++ b/files/routes/notifications.py @@ -0,0 +1,258 @@ +from files.helpers.wrappers import * +from files.helpers.get import * +from files.helpers.const import * +from files.__main__ import app + + +@app.post("/clear") +@auth_required +def clear(v): + notifs = g.db.query(Notification).join(Notification.comment).filter(Notification.read == False, Notification.user_id == v.id).all() + for n in notifs: + n.read = True + g.db.add(n) + return {"message": "Notifications cleared!"} + + +@app.get("/unread") +@auth_required +def unread(v): + listing = g.db.query(Notification, Comment).join(Notification.comment).filter( + Notification.read == False, + Notification.user_id == v.id, + Comment.is_banned == False, + Comment.deleted_utc == 0, + Comment.author_id != AUTOJANNY_ID, + ).order_by(Notification.created_utc.desc()).all() + + for n, c in listing: + n.read = True + g.db.add(n) + + return {"data":[x[1].json for x in listing]} + + + +@app.get("/notifications/modmail") +@admin_level_required(2) +def notifications_modmail(v): + try: page = max(int(request.values.get("page", 1)), 1) + except: page = 1 + + comments = g.db.query(Comment).filter(Comment.sentto==2).order_by(Comment.id.desc()).offset(25*(page-1)).limit(26).all() + next_exists = (len(comments) > 25) + listing = comments[:25] + + if request.headers.get("Authorization"): return {"data":[x.json for x in listing]} + + return render_template("notifications.html", + v=v, + notifications=listing, + next_exists=next_exists, + page=page, + standalone=True, + render_replies=True, + ) + + + +@app.get("/notifications/messages") +@auth_required +def notifications_messages(v): + try: page = max(int(request.values.get("page", 1)), 1) + except: page = 1 + + if v and (v.shadowbanned or v.admin_level > 2): + comments = g.db.query(Comment).filter(Comment.sentto != None, or_(Comment.author_id==v.id, Comment.sentto==v.id), Comment.parent_submission == None, Comment.level == 1).order_by(Comment.id.desc()).offset(25*(page-1)).limit(26).all() + else: + comments = g.db.query(Comment).join(Comment.author).filter(User.shadowbanned == None, Comment.sentto != None, or_(Comment.author_id==v.id, Comment.sentto==v.id), Comment.parent_submission == None, Comment.level == 1).order_by(Comment.id.desc()).offset(25*(page-1)).limit(26).all() + + next_exists = (len(comments) > 25) + listing = comments[:25] + + if request.headers.get("Authorization"): return {"data":[x.json for x in listing]} + + return render_template("notifications.html", + v=v, + notifications=listing, + next_exists=next_exists, + page=page, + standalone=True, + render_replies=True, + ) + + +@app.get("/notifications/posts") +@auth_required +def notifications_posts(v): + try: page = max(int(request.values.get("page", 1)), 1) + except: page = 1 + + listing = g.db.query(Submission).filter(Submission.author_id.in_(v.following_ids)).order_by(Submission.created_utc.desc()).offset(25 * (page - 1)).limit(26).all() + + next_exists = (len(listing) > 25) + listing = listing[:25] + + for p in listing: + p.unread = p.created_utc > v.last_viewed_post_notifs + + v.last_viewed_post_notifs = int(time.time()) + g.db.add(v) + + if request.headers.get("Authorization"): return {"data":[x.json for x in listing]} + + return render_template("notifications.html", + v=v, + notifications=listing, + next_exists=next_exists, + page=page, + standalone=True, + render_replies=True, + ) + + +@app.get("/notifications/modactions") +@admin_level_required(NOTIF_MODACTION_JL_MIN) +def notifications_modactions(v): + try: page = max(int(request.values.get("page", 1)), 1) + except: page = 1 + + notifications = g.db.query(Notification, Comment) \ + .join(Notification.comment) \ + .filter(Notification.user_id == v.id, + Comment.body_html.like(f'%

{NOTIF_MODACTION_PREFIX}%'), + Comment.parent_submission == None, Comment.author_id == NOTIFICATIONS_ID) \ + .order_by(Notification.created_utc.desc()).offset(25 * (page - 1)).limit(101).all() + listing = [] + + for index, x in enumerate(notifications[:100]): + n, c = x + if n.read and index > 24: break + elif not n.read: + n.read = True + c.unread = True + g.db.add(n) + if n.created_utc > 1620391248: c.notif_utc = n.created_utc + listing.append(c) + + next_exists = (len(notifications) > len(listing)) + + if request.headers.get("Authorization"): return {"data":[x.json for x in listing]} + + return render_template("notifications.html", + v=v, + notifications=listing, + next_exists=next_exists, + page=page, + standalone=True, + render_replies=True, + ) + + + +@app.get("/notifications/reddit") +@auth_required +def notifications_reddit(v): + try: page = max(int(request.values.get("page", 1)), 1) + except: page = 1 + + if not v.can_view_offsitementions: abort(403) + + notifications = g.db.query(Notification, Comment).join(Notification.comment).filter(Notification.user_id == v.id, Comment.body_html.like('%

New site mention: New site mention: 500: notifbody = message[:500] + '...' else: notifbody = message - url = f'{SITE_FULL}/notifications?messages=true' + url = f'{SITE_FULL}/notifications/messages' gevent.spawn(pusher_thread, interests, title, notifbody, url) @@ -761,7 +761,7 @@ def messagereply(v): if len(body) > 500: notifbody = body[:500] + '...' else: notifbody = body - url = f'{SITE_FULL}/notifications?messages=true' + url = f'{SITE_FULL}/notifications/messages' gevent.spawn(pusher_thread, interests, title, notifbody, url) diff --git a/files/templates/header.html b/files/templates/header.html index 94dbb0821..ce0ba33aa 100644 --- a/files/templates/header.html +++ b/files/templates/header.html @@ -112,7 +112,7 @@ {% if v.notifications_count %}

{% else %}