Merge remote-tracking branch 'origin/main'

This commit is contained in:
rimu 2024-10-20 11:54:31 +13:00
commit 2d6d9b960c
12 changed files with 192 additions and 50 deletions

View file

@ -835,8 +835,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
for ocp in old_cross_posts: for ocp in old_cross_posts:
if ocp.cross_posts is not None and post.id in ocp.cross_posts: if ocp.cross_posts is not None and post.id in ocp.cross_posts:
ocp.cross_posts.remove(post.id) ocp.cross_posts.remove(post.id)
delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id) delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id, activity_log.id)
activity_log.result = 'success'
elif request_json['object']['type'] == 'Page': # Sent for Mastodon's benefit elif request_json['object']['type'] == 'Page': # Sent for Mastodon's benefit
activity_log.result = 'ignored' activity_log.result = 'ignored'
activity_log.exception_message = 'Intended for Mastodon' activity_log.exception_message = 'Intended for Mastodon'

View file

@ -220,6 +220,13 @@ def comment_model_to_json(reply: PostReply) -> dict:
} }
if reply.edited_at: if reply.edited_at:
reply_data['updated'] = ap_datetime(reply.edited_at) reply_data['updated'] = ap_datetime(reply.edited_at)
if reply.deleted:
if reply.deleted_by == reply.user_id:
reply_data['content'] = '<p>Deleted by author</p>'
reply_data['source']['content'] = 'Deleted by author'
else:
reply_data['content'] = '<p>Deleted by moderator</p>'
reply_data['source']['content'] = 'Deleted by moderator'
return reply_data return reply_data
@ -1295,18 +1302,24 @@ def is_activitypub_request():
return 'application/ld+json' in request.headers.get('Accept', '') or 'application/activity+json' in request.headers.get('Accept', '') return 'application/ld+json' in request.headers.get('Accept', '') or 'application/activity+json' in request.headers.get('Accept', '')
def delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id): def delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id):
if current_app.debug: if current_app.debug:
delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id) delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id)
else: else:
delete_post_or_comment_task.delay(user_ap_id, community_ap_id, to_be_deleted_ap_id) delete_post_or_comment_task.delay(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id)
@celery.task @celery.task
def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id): def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id):
deletor = find_actor_or_create(user_ap_id) deletor = find_actor_or_create(user_ap_id)
community = find_actor_or_create(community_ap_id, community_only=True) community = find_actor_or_create(community_ap_id, community_only=True)
to_delete = find_liked_object(to_be_deleted_ap_id) to_delete = find_liked_object(to_be_deleted_ap_id)
if to_delete.deleted:
aplog = ActivityPubLog.query.get(aplog_id)
if aplog:
aplog.result = 'ignored'
aplog.exception_message = 'Activity about local content which is already present'
return
if deletor and community and to_delete: if deletor and community and to_delete:
if deletor.is_admin() or community.is_moderator(deletor) or community.is_instance_admin(deletor) or to_delete.author.id == deletor.id: if deletor.is_admin() or community.is_moderator(deletor) or community.is_instance_admin(deletor) or to_delete.author.id == deletor.id:
@ -1323,12 +1336,8 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id
elif isinstance(to_delete, PostReply): elif isinstance(to_delete, PostReply):
if not to_delete.author.bot: if not to_delete.author.bot:
to_delete.post.reply_count -= 1 to_delete.post.reply_count -= 1
if to_delete.has_replies(): to_delete.deleted = True
to_delete.body = 'Deleted by author' if to_delete.author.id == deletor.id else 'Deleted by moderator' to_delete.deleted_by = deletor.id
to_delete.body_html = markdown_to_html(to_delete.body)
else:
to_delete.delete_dependencies()
to_delete.deleted = True
to_delete.author.post_reply_count -= 1 to_delete.author.post_reply_count -= 1
to_delete.deleted_by = deletor.id to_delete.deleted_by = deletor.id
db.session.commit() db.session.commit()
@ -1398,12 +1407,8 @@ def remove_data_from_banned_user_task(deletor_ap_id, user_ap_id, target):
for post_reply in post_replies: for post_reply in post_replies:
if not user.bot: if not user.bot:
post_reply.post.reply_count -= 1 post_reply.post.reply_count -= 1
if post_reply.has_replies(): post_reply.deleted = True
post_reply.body = 'Banned' post_reply.deleted_by = deletor.id
post_reply.body_html = markdown_to_html(post_reply.body)
else:
post_reply.delete_dependencies()
post_reply.deleted = True
db.session.commit() db.session.commit()
for post in posts: for post in posts:

View file

@ -195,7 +195,8 @@ def register(app):
for post_reply in PostReply.query.filter(PostReply.deleted == True, for post_reply in PostReply.query.filter(PostReply.deleted == True,
PostReply.posted_at < utcnow() - timedelta(days=7)).all(): PostReply.posted_at < utcnow() - timedelta(days=7)).all():
post_reply.delete_dependencies() post_reply.delete_dependencies()
db.session.delete(post_reply) if not post_reply.has_replies():
db.session.delete(post_reply)
db.session.commit() db.session.commit()
for post in Post.query.filter(Post.deleted == True, for post in Post.query.filter(Post.deleted == True,

View file

@ -566,12 +566,8 @@ def delete_post_reply_from_community_task(post_reply_id):
post = post_reply.post post = post_reply.post
community = post.community community = post.community
if post_reply.user_id == current_user.id or community.is_moderator(): if post_reply.user_id == current_user.id or community.is_moderator():
if post_reply.has_replies(): post_reply.deleted = True
post_reply.body = 'Deleted by author' if post_reply.author.id == current_user.id else 'Deleted by moderator' post_reply.deleted_by = current_user.id
post_reply.body_html = markdown_to_html(post_reply.body)
else:
post_reply.delete_dependencies()
post_reply.deleted = True
db.session.commit() db.session.commit()
# federate delete # federate delete

View file

@ -1513,9 +1513,18 @@ class PostReply(db.Model):
return parent.author.public_url() return parent.author.public_url()
def delete_dependencies(self): def delete_dependencies(self):
"""
The first loop doesn't seem to ever be invoked with the current behaviour.
For replies with their own replies: functions which deal with removal don't set reply.deleted and don't call this, and
because reply.deleted isn't set, the cli task 7 days later doesn't call this either.
The plan is to set reply.deleted whether there's child replies or not (as happens with the API call), so I've commented
it out so the current behaviour isn't changed.
for child_reply in self.child_replies(): for child_reply in self.child_replies():
child_reply.delete_dependencies() child_reply.delete_dependencies()
db.session.delete(child_reply) db.session.delete(child_reply)
"""
db.session.query(PostReplyBookmark).filter(PostReplyBookmark.post_reply_id == self.id).delete() db.session.query(PostReplyBookmark).filter(PostReplyBookmark.post_reply_id == self.id).delete()
db.session.query(Report).filter(Report.suspect_post_reply_id == self.id).delete() db.session.query(Report).filter(Report.suspect_post_reply_id == self.id).delete()

View file

@ -681,9 +681,13 @@ def post_options(post_id: int):
def post_reply_options(post_id: int, comment_id: int): def post_reply_options(post_id: int, comment_id: int):
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
post_reply = PostReply.query.get_or_404(comment_id) post_reply = PostReply.query.get_or_404(comment_id)
if current_user.is_anonymous or not current_user.is_admin(): if post.deleted or post_reply.deleted:
if post.deleted or post_reply.deleted: if current_user.is_anonymous:
abort(404) abort(404)
if (not post.community.is_moderator() and
not current_user.is_admin() and
(post_reply.deleted_by is not None and post_reply.deleted_by != current_user.id)):
abort(401)
existing_bookmark = [] existing_bookmark = []
if current_user.is_authenticated: if current_user.is_authenticated:
@ -1597,17 +1601,12 @@ def post_reply_delete(post_id: int, comment_id: int):
post_reply = PostReply.query.get_or_404(comment_id) post_reply = PostReply.query.get_or_404(comment_id)
community = post.community community = post.community
if post_reply.user_id == current_user.id or community.is_moderator() or current_user.is_admin(): if post_reply.user_id == current_user.id or community.is_moderator() or current_user.is_admin():
if post_reply.has_replies(): post_reply.deleted = True
post_reply.body = 'Deleted by author' if post_reply.author.id == current_user.id else 'Deleted by moderator' post_reply.deleted_by = current_user.id
post_reply.body_html = markdown_to_html(post_reply.body)
else:
post_reply.delete_dependencies()
post_reply.deleted = True
g.site.last_active = community.last_active = utcnow() g.site.last_active = community.last_active = utcnow()
if not post_reply.author.bot: if not post_reply.author.bot:
post.reply_count -= 1 post.reply_count -= 1
post_reply.author.post_reply_count -= 1 post_reply.author.post_reply_count -= 1
post_reply.deleted_by = current_user.id
db.session.commit() db.session.commit()
flash(_('Comment deleted.')) flash(_('Comment deleted.'))
# federate delete # federate delete
@ -1627,11 +1626,12 @@ def post_reply_delete(post_id: int, comment_id: int):
if post_reply.user_id != current_user.id: if post_reply.user_id != current_user.id:
delete_json['summary'] = 'Deleted by mod' delete_json['summary'] = 'Deleted by mod'
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it if not post.community.is_local():
success = post_request(post.community.ap_inbox_url, delete_json, current_user.private_key, if post_reply.user_id == current_user.id or post.community.is_moderator(current_user):
current_user.public_url() + '#main-key') success = post_request(post.community.ap_inbox_url, delete_json, current_user.private_key,
if success is False or isinstance(success, str): current_user.public_url() + '#main-key')
flash('Failed to send delete to remote server', 'error') if success is False or isinstance(success, str):
flash('Failed to send delete to remote server', 'error')
else: # local community - send it to followers on remote instances else: # local community - send it to followers on remote instances
announce = { announce = {
"id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}",
@ -1658,6 +1658,107 @@ def post_reply_delete(post_id: int, comment_id: int):
return redirect(url_for('activitypub.post_ap', post_id=post.id)) return redirect(url_for('activitypub.post_ap', post_id=post.id))
@bp.route('/post/<int:post_id>/comment/<int:comment_id>/restore', methods=['GET', 'POST'])
@login_required
def post_reply_restore(post_id: int, comment_id: int):
post = Post.query.get_or_404(post_id)
post_reply = PostReply.query.get_or_404(comment_id)
community = post.community
if post_reply.user_id == current_user.id or post.community.is_moderator() or current_user.is_admin():
if post_reply.deleted_by == post_reply.user_id:
was_mod_deletion = False
else:
was_mod_deletion = True
post_reply.deleted = False
post_reply.deleted_by = None
if not post_reply.author.bot:
post.reply_count += 1
post_reply.author.post_reply_count += 1
db.session.commit()
flash(_('Comment restored.'))
# Federate un-delete
if not post.community.local_only:
delete_json = {
"actor": current_user.public_url(),
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"object": {
'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}",
'type': 'Delete',
'actor': current_user.public_url(),
'audience': post.community.public_url(),
'to': [post.community.public_url(), 'https://www.w3.org/ns/activitystreams#Public'],
'published': ap_datetime(utcnow()),
'cc': [
current_user.followers_url()
],
'object': post_reply.ap_id,
'uri': post_reply.ap_id,
},
"cc": [post.community.public_url()],
"audience": post.community.public_url(),
"type": "Undo",
"id": f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}"
}
if was_mod_deletion:
delete_json['object']['summary'] = "Deleted by mod"
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
if not was_mod_deletion or (was_mod_deletion and post.community.is_moderator(current_user)):
success = post_request(post.community.ap_inbox_url, delete_json, current_user.private_key,
current_user.public_url() + '#main-key')
if success is False or isinstance(success, str):
flash('Failed to send delete to remote server', 'error')
else: # local community - send it to followers on remote instances
announce = {
"id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}",
"type": 'Announce',
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"actor": post.community.public_url(),
"cc": [
post.community.ap_followers_url
],
'@context': default_context(),
'object': delete_json
}
for instance in post.community.following_instances():
if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain):
send_to_remote_instance(instance.id, post.community.id, announce)
if post_reply.user_id != current_user.id:
add_to_modlog('restore_post_reply', community_id=post.community.id, link_text=f'comment on {shorten_string(post.title)}',
link=f'post/{post.id}#comment_{post_reply.id}')
return redirect(url_for('activitypub.post_ap', post_id=post.id))
@bp.route('/post/<int:post_id>/comment/<int:comment_id>/purge', methods=['GET', 'POST'])
@login_required
def post_reply_purge(post_id: int, comment_id: int):
post = Post.query.get_or_404(post_id)
post_reply = PostReply.query.get_or_404(comment_id)
if not post_reply.deleted:
abort(404)
if post_reply.user_id == current_user.id and (post_reply.deleted_by is None or post_reply.deleted_by != post_reply.user_id):
abort(401)
if post_reply.user_id == current_user.id or post.community.is_moderator() or current_user.is_admin():
if not post_reply.has_replies():
post_reply.delete_dependencies()
db.session.delete(post_reply)
db.session.commit()
flash(_('Comment purged.'))
else:
flash(_('Comments that have been replied to cannot be purged.'))
else:
abort(401)
return redirect(url_for('activitypub.post_ap', post_id=post.id))
@bp.route('/post/<int:post_id>/notification', methods=['GET', 'POST']) @bp.route('/post/<int:post_id>/notification', methods=['GET', 'POST'])
@login_required @login_required
def post_notification(post_id: int): def post_notification(post_id: int):

View file

@ -11,7 +11,7 @@ from app.utils import blocked_instances, blocked_users
# replies to a post, in a tree, sorted by a variety of methods # replies to a post, in a tree, sorted by a variety of methods
def post_replies(post_id: int, sort_by: str, show_first: int = 0) -> List[PostReply]: def post_replies(post_id: int, sort_by: str, show_first: int = 0) -> List[PostReply]:
comments = PostReply.query.filter_by(post_id=post_id).filter(PostReply.deleted == False) comments = PostReply.query.filter_by(post_id=post_id)
if current_user.is_authenticated: if current_user.is_authenticated:
instance_ids = blocked_instances(current_user.id) instance_ids = blocked_instances(current_user.id)
if instance_ids: if instance_ids:
@ -52,7 +52,7 @@ def get_comment_branch(post_id: int, comment_id: int, sort_by: str) -> List[Post
if parent_comment is None: if parent_comment is None:
return [] return []
comments = PostReply.query.filter(PostReply.post_id == post_id, PostReply.deleted == False) comments = PostReply.query.filter(PostReply.post_id == post_id)
if current_user.is_authenticated: if current_user.is_authenticated:
instance_ids = blocked_instances(current_user.id) instance_ids = blocked_instances(current_user.id)
if instance_ids: if instance_ids:

View file

@ -4,7 +4,7 @@
no_collapse: Don't collapse for admin and moderator views no_collapse: Don't collapse for admin and moderator views
#} #}
{% if current_user.is_authenticated -%} {% if current_user.is_authenticated -%}
{% set collapsed = (post_reply.score <= current_user.reply_collapse_threshold) {% set collapsed = ((post_reply.score <= current_user.reply_collapse_threshold) or post_reply.deleted)
and not no_collapse -%} and not no_collapse -%}
{% else -%} {% else -%}
{% set collapsed = (post_reply.score <= -10) and not no_collapse -%} {% set collapsed = (post_reply.score <= -10) and not no_collapse -%}
@ -36,6 +36,11 @@
<span class="red fe fe-report" title="{{ _('Reported. Check comment for issues.') }}"></span> <span class="red fe fe-report" title="{{ _('Reported. Check comment for issues.') }}"></span>
{% endif -%} {% endif -%}
</div> </div>
<div class="col-auto">
{% if post_reply.deleted -%}
<span class="red fe fe-delete" title="{{ _('Comment deleted') }}"></span>
{% endif -%}
</div>
<div class="col-auto"> <div class="col-auto">
<a class="unhide" href="#"><span class="fe fe-expand"></span></a> <a class="unhide" href="#"><span class="fe fe-expand"></span></a>
</div> </div>

View file

@ -28,7 +28,15 @@
{% endif %} {% endif %}
</div> </div>
<div class="comment_body hidable {% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}reported{% endif %}"> <div class="comment_body hidable {% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}reported{% endif %}">
{{ comment['comment'].body_html | safe }} {% if comment['comment'].deleted -%}
{% if comment['comment'].deleted_by is none or comment['comment'].deleted_by != comment['comment'].user_id -%}
<p>Deleted by moderator</p>
{% else -%}
<p>Deleted by author</p>
{% endif -%}
{% else -%}
{{ comment['comment'].body_html | community_links | safe }}
{% endif -%}
</div> </div>
</div> </div>
<div class="comment_actions hidable"> <div class="comment_actions hidable">

View file

@ -80,7 +80,7 @@
aria-level="{{ comment['comment'].depth + 1 }}" role="treeitem" aria-expanded="true" tabindex="0"> aria-level="{{ comment['comment'].depth + 1 }}" role="treeitem" aria-expanded="true" tabindex="0">
<div class="limit_height">{% if not comment['comment'].author.indexable -%}<!--googleoff: all-->{% endif -%} <div class="limit_height">{% if not comment['comment'].author.indexable -%}<!--googleoff: all-->{% endif -%}
<div class="comment_author"> <div class="comment_author">
{% with collapsed = comment['comment'].score < reply_collapse_threshold -%} {% with collapsed = (comment['comment'].score < reply_collapse_threshold or comment['comment'].deleted) -%}
{{ render_username(comment['comment'].author) }} {{ render_username(comment['comment'].author) }}
{% endwith -%} {% endwith -%}
{% if comment['comment'].author.id == post.author.id -%}<span title="Submitter of original post" aria-label="{{ _('Post creator') }}" class="small">[OP] </span>{% endif -%} {% if comment['comment'].author.id == post.author.id -%}<span title="Submitter of original post" aria-label="{{ _('Post creator') }}" class="small">[OP] </span>{% endif -%}
@ -91,7 +91,15 @@
{% endif -%} {% endif -%}
</div> </div>
<div class="comment_body hidable {% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user) -%}reported{% endif -%}"> <div class="comment_body hidable {% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user) -%}reported{% endif -%}">
{{ comment['comment'].body_html | community_links | safe }} {% if comment['comment'].deleted -%}
{% if comment['comment'].deleted_by is none or comment['comment'].deleted_by != comment['comment'].user_id -%}
<p>Deleted by moderator</p>
{% else -%}
<p>Deleted by author</p>
{% endif -%}
{% else -%}
{{ comment['comment'].body_html | community_links | safe }}
{% endif -%}
</div>{% if not comment['comment'].author.indexable -%}<!--googleon: all-->{% endif -%} </div>{% if not comment['comment'].author.indexable -%}<!--googleon: all-->{% endif -%}
</div> </div>
<div class="comment_actions hidable"> <div class="comment_actions hidable">
@ -104,7 +112,7 @@
{% endwith -%} {% endwith -%}
</div> </div>
<div class="hide_button"> <div class="hide_button">
{% if comment['comment'].score <= reply_collapse_threshold -%} {% if comment['comment'].score <= reply_collapse_threshold or comment['comment'].deleted -%}
<a href='#'><span class="fe fe-expand"></span></a> <a href='#'><span class="fe fe-expand"></span></a>
{% else -%} {% else -%}
<a href='#'><span class="fe fe-collapse"></span></a> <a href='#'><span class="fe fe-collapse"></span></a>
@ -132,7 +140,7 @@
{% endif -%} {% endif -%}
{% endif -%} {% endif -%}
</div> </div>
{% if comment['comment'].score <= reply_collapse_threshold -%} {% if comment['comment'].score <= reply_collapse_threshold or comment['comment'].deleted -%}
<script nonce="{{ session['nonce'] }}" type="text/javascript"> <script nonce="{{ session['nonce'] }}" type="text/javascript">
toBeHidden.push({{ comment['comment'].id }}); toBeHidden.push({{ comment['comment'].id }});
</script> </script>

View file

@ -19,8 +19,14 @@
{% endif -%} {% endif -%}
{% if post_reply.user_id == current_user.id or post.community.is_moderator() or post.community.is_owner() or current_user.is_admin() -%} {% if post_reply.user_id == current_user.id or post.community.is_moderator() or post.community.is_owner() or current_user.is_admin() -%}
{% if post_reply.deleted -%} {% if post_reply.deleted -%}
<li><a href="{{ url_for('post.post_reply_restore', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span> <li><a href="{{ url_for('post.post_reply_restore', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-arrow-up"></span>
{{ _('Restore') }}</a></li> {{ _('Restore') }}</a></li>
{% if not post_reply.has_replies() -%}
{% if post.community.is_moderator() or current_user.is_admin() or (post_reply.user_id == current_user.id and post_reply.deleted_by == post_reply.user_id) -%}
<li><a href="{{ url_for('post.post_reply_purge', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete red"></span>
{{ _('Purge') }}</a></li>
{% endif -%}
{% endif -%}
{% else -%} {% else -%}
<li><a href="{{ url_for('post.post_reply_delete', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span> <li><a href="{{ url_for('post.post_reply_delete', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span>
{{ _('Delete') }}</a></li> {{ _('Delete') }}</a></li>

View file

@ -63,9 +63,13 @@ def show_profile(user):
else: else:
upvoted = [] upvoted = []
subscribed = Community.query.filter_by(banned=False).join(CommunityMember).filter(CommunityMember.user_id == user.id).all() subscribed = Community.query.filter_by(banned=False).join(CommunityMember).filter(CommunityMember.user_id == user.id).all()
if current_user.is_anonymous or user.id != current_user.id: if current_user.is_anonymous or (user.id != current_user.id and not current_user.is_admin()):
moderates = moderates.filter(Community.private_mods == False) moderates = moderates.filter(Community.private_mods == False)
post_replies = PostReply.query.filter_by(user_id=user.id).filter(PostReply.deleted == False).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False) post_replies = PostReply.query.filter_by(user_id=user.id, deleted=False).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False)
elif current_user.is_admin():
post_replies = PostReply.query.filter_by(user_id=user.id).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False)
elif current_user.id == user.id:
post_replies = PostReply.query.filter_by(user_id=user.id).filter(or_(PostReply.deleted == False, PostReply.deleted_by == user.id)).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False)
# profile info # profile info
canonical = user.ap_public_url if user.ap_public_url else None canonical = user.ap_public_url if user.ap_public_url else None