rDrama/files/routes/front.py
TLSM 695e6b6dbd Split notification routes, hard wrap, sort order
Change the notifications subpages to be distinct route handlers with
actual paths rather than query parameters. They already were a massive
conditional chain with almost no common logic.

Hard wrap some of the more egregious query lines. Use less duplicated
code for shadowban exclusion.

Only major functionality change which is somewhat related to #476 is
to sort subtrees by Comment.id DESC. Otherwise, upstream is
substantially the same as TheMotte. Given that upstream didn't
experience #476, I think the issue may have been resolved by prior
changes to filtering / comment visibility & moderation.
2023-07-12 00:24:50 -05:00

376 lines
10 KiB
Python

from sqlalchemy.orm import Query
import files.helpers.listing as listing
from files.__main__ import app, limiter
from files.classes.submission import Submission
from files.classes.visstate import StateMod
from files.helpers.comments import comment_filter_moderated
from files.helpers.contentsorting import (apply_time_filter,
sort_comment_results, sort_objects)
from files.helpers.config.environment import DEFAULT_TIME_FILTER
from files.helpers.get import *
from files.helpers.wrappers import *
@app.post("/clear")
@auth_required
def clear(v):
notifs = g.db.query(Notification).join(Comment, Notification.comment_id == Comment.id).filter(Notification.read == False, Notification.user_id == v.id).all()
for n in notifs:
n.read = True
g.db.add(n)
g.db.commit()
return {"message": "Notifications cleared!"}
@app.get("/unread")
@auth_required
def unread(v):
listing = g.db.query(Notification, Comment).join(Comment, Notification.comment_id == Comment.id).filter(
Notification.read == False,
Notification.user_id == v.id,
Comment.state_mod == StateMod.VISIBLE,
Comment.state_user_deleted_utc == None,
Comment.author_id != AUTOJANNY_ID,
).order_by(Notification.created_utc.desc()).all()
for n, c in listing:
n.read = True
g.db.add(n)
g.db.commit()
return {"data":[x[1].json for x in listing]}
@app.get("/notifications")
@auth_required
def notifications_main(v: User):
page: int = max(request.values.get("page", 1, int) or 1, 1)
comments = (g.db.query(Comment, Notification)
.join(Notification.comment)
.filter(
Notification.user_id == v.id,
Comment.state_mod == StateMod.VISIBLE,
Comment.state_user_deleted_utc == None,
Comment.author_id != AUTOJANNY_ID,
).order_by(Notification.created_utc.desc()))
if not v.shadowbanned and v.admin_level < 3:
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 = (c.child_comments
.filter(or_(Comment.author_id == v.id, Comment.id.in_(cids)))
.order_by(Comment.created_utc.desc()).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 = (c.child_comments
.filter(or_(Comment.author_id == v.id, Comment.id.in_(cids)))
.order_by(Comment.created_utc.desc()).all())
for x in c.replies2:
if x.replies2 == None:
x.replies2 = (x.child_comments
.filter(or_(Comment.author_id == v.id, Comment.id.in_(cids)))
.order_by(Comment.created_utc.desc()).all())
else:
while c.parent_comment:
c = c.parent_comment
c.replies2 = c.child_comments.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,
)
@app.get("/notifications/posts")
@auth_required
def notifications_posts(v: User):
page: int = max(request.values.get("page", 1, int) or 1, 1)
notifications = (g.db.query(Notification, Comment)
.join(Comment, Notification.comment_id == Comment.id)
.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))
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,
)
@app.get("/notifications/modmail")
@admin_level_required(2)
def notifications_modmail(v: User):
page: int = max(request.values.get("page", 1, int) or 1, 1)
comments = (g.db.query(Comment)
.filter(Comment.sentto == MODMAIL_ID)
.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: User):
page: int = max(request.values.get("page", 1, int) or 1, 1)
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,
)
if not v.shadowbanned and v.admin_level < 3:
comments = comments.join(Comment.author).filter(User.shadowbanned == None)
comments = comments.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("/")
@app.get("/catalog")
@limiter.limit("3/second;30/minute;1000/hour;5000/day")
@auth_desired
def front_all(v, subdomain=None):
if g.webview and not session.get("session_id"):
session["session_id"] = secrets.token_hex(49)
try: page = max(int(request.values.get("page", 1)), 1)
except: abort(400)
if v:
defaultsorting = v.defaultsorting
defaulttime = 'all'
else:
defaultsorting = "new"
defaulttime = 'all'
sort=request.values.get("sort", defaultsorting)
t=request.values.get('t', defaulttime)
ccmode=request.values.get('ccmode', "false").lower()
if sort == 'bump': t='all'
try: gt=int(request.values.get("after", 0))
except: gt=0
try: lt=int(request.values.get("before", 0))
except: lt=0
ids, next_exists = listing.frontlist(sort=sort,
page=page,
t=t,
v=v,
ccmode=ccmode,
filter_words=v.filter_words if v else [],
gt=gt,
lt=lt,
)
posts = get_posts(ids, v=v, eager=True)
if v:
if v.hidevotedon: posts = [x for x in posts if not hasattr(x, 'voted') or not x.voted]
if v.patron_utc and v.patron_utc < time.time():
v.patron = 0
v.patron_utc = 0
send_repeatable_notification(v.id, "Your paypig status has expired!")
g.db.add(v)
g.db.commit()
if v.unban_utc and v.unban_utc < time.time():
v.is_banned = 0
v.unban_utc = 0
v.ban_evade = 0
send_repeatable_notification(v.id, "You have been unbanned!")
g.db.add(v)
g.db.commit()
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page, ccmode=ccmode, home=True)
@app.get("/changelog")
@auth_required
def changelog(v):
try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1
sort=request.values.get("sort", "new")
t=request.values.get('t', "all")
ids = listing.changeloglist(sort=sort,
page=page,
t=t,
v=v,
)
next_exists = (len(ids) > 25)
ids = ids[:25]
posts = get_posts(ids, v=v)
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
return render_template("changelog.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page)
@app.get("/random_post")
def random_post():
p = g.db.query(Submission.id).filter(Submission.state_user_deleted_utc == None, Submission.state_mod == StateMod.VISIBLE, Submission.private == False).order_by(func.random()).first()
if p: p = p[0]
else: abort(404)
return redirect(f"/post/{p}")
@app.get("/random_user")
def random_user():
u = g.db.query(User.username).order_by(func.random()).first()
if u: u = u[0]
else: abort(404)
return redirect(f"/@{u}")
@app.get("/comments")
@auth_required
def all_comments(v):
page = max(request.values.get("page", 1, int), 1)
sort = request.values.get("sort", "new")
time_filter = request.values.get("t", DEFAULT_TIME_FILTER)
time_gt = request.values.get("after", 0, int)
time_lt = request.values.get("before", 0, int)
idlist = get_comments_idlist(v=v,
page=page, sort=sort, t=time_filter, gt=time_gt, lt=time_lt)
next_exists = len(idlist) > 25
idlist = idlist[:25]
def comment_tree_filter(q: Query) -> Query:
q = q.filter(Comment.id.in_(idlist))
q = comment_filter_moderated(q, v)
q = q.options(selectinload(Comment.post)) # used for post titles
return q
comments, _ = get_comment_trees_eager(comment_tree_filter, sort=sort, v=v)
comments = sort_comment_results(comments, sort=sort, pins=False)
if request.headers.get("Authorization"):
return {"data": [x.json for x in comments]}
return render_template("home_comments.html", v=v,
sort=sort, t=time_filter, page=page, next_exists=next_exists,
comments=comments, standalone=True)
def get_comments_idlist(page=1, v=None, sort="new", t="all", gt=0, lt=0):
comments = g.db.query(Comment.id) \
.join(Comment.post) \
.join(Comment.author) \
.filter(Comment.parent_submission != None)
if v.admin_level < 2:
comments = comments.filter(
Comment.author_id.notin_(v.userblocks),
Comment.state_mod == StateMod.VISIBLE,
Comment.state_user_deleted_utc == None,
Submission.private == False, # comment parent post not private
User.shadowbanned == None, # comment author not shadowbanned
)
if gt: comments = comments.filter(Comment.created_utc > gt)
if lt: comments = comments.filter(Comment.created_utc < lt)
if not gt and not lt:
comments = apply_time_filter(comments, t, Comment)
comments = sort_objects(comments, sort, Comment)
comments = comments.offset(25 * (page - 1)).limit(26).all()
return [x[0] for x in comments]