Merge remote-tracking branch 'origin/main'

This commit is contained in:
rimu 2024-10-30 09:19:44 +13:00
commit bcbb9f83e9
24 changed files with 348 additions and 188 deletions

View file

@ -206,17 +206,18 @@ def lemmy_federated_instances():
linked = [] linked = []
allowed = [] allowed = []
blocked = [] blocked = []
for instance in AllowedInstances.query.all():
allowed.append({"id": instance.id, "domain": instance.domain, "published": utcnow(), "updated": utcnow()})
for instance in BannedInstances.query.all():
blocked.append({"id": instance.id, "domain": instance.domain, "published": utcnow(), "updated": utcnow()})
for instance in instances: for instance in instances:
instance_data = {"id": instance.id, "domain": instance.domain, "published": instance.created_at.isoformat(), "updated": instance.updated_at.isoformat()} instance_data = {"id": instance.id, "domain": instance.domain, "published": instance.created_at.isoformat(), "updated": instance.updated_at.isoformat()}
if instance.software: if instance.software:
instance_data['software'] = instance.software instance_data['software'] = instance.software
if instance.version: if instance.version:
instance_data['version'] = instance.version instance_data['version'] = instance.version
linked.append(instance_data) if not any(blocked_instance.get('domain') == instance.domain for blocked_instance in blocked):
for instance in AllowedInstances.query.all(): linked.append(instance_data)
allowed.append({"id": instance.id, "domain": instance.domain, "published": utcnow(), "updated": utcnow()})
for instance in BannedInstances.query.all():
blocked.append({"id": instance.id, "domain": instance.domain, "published": utcnow(), "updated": utcnow()})
return jsonify({ return jsonify({
"federated_instances": { "federated_instances": {
"linked": linked, "linked": linked,
@ -619,6 +620,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
post = create_post(activity_log, community, request_json, user) post = create_post(activity_log, community, request_json, user)
if post: if post:
announce_activity_to_followers(community, user, request_json) announce_activity_to_followers(community, user, request_json)
activity_log.result = 'success'
except TypeError as e: except TypeError as e:
activity_log.exception_message = 'TypeError. See log file.' activity_log.exception_message = 'TypeError. See log file.'
current_app.logger.error('TypeError: ' + str(request_json)) current_app.logger.error('TypeError: ' + str(request_json))
@ -1081,17 +1083,16 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
activity_log.result = 'success' activity_log.result = 'success'
elif request_json['object']['type'] == 'Delete': # undoing a delete elif request_json['object']['type'] == 'Delete': # undoing a delete
activity_log.activity_type = 'Restore' activity_log.activity_type = 'Restore'
reply = PostReply.query.filter_by(ap_id=request_json['object']['object']).first() post = Post.query.filter_by(ap_id=request_json['object']['object']).first()
if reply: if post:
deletor = find_actor_or_create(request_json['object']['actor'], create_if_not_found=False) deletor = find_actor_or_create(request_json['object']['actor'], create_if_not_found=False)
if deletor: if deletor:
if reply.author.id == deletor.id or reply.community.is_moderator(deletor) or reply.community.is_instance_admin(deletor): if post.author.id == deletor.id or post.community.is_moderator(deletor) or post.community.is_instance_admin(deletor):
reply.deleted = False post.deleted = False
reply.deleted_by = None post.deleted_by = None
if not reply.author.bot: post.author.post_count += 1
reply.post.reply_count += 1 post.community.post_count += 1
reply.author.post_reply_count += 1 announce_activity_to_followers(post.community, post.author, request_json)
announce_activity_to_followers(reply.community, reply.author, request_json)
db.session.commit() db.session.commit()
activity_log.result = 'success' activity_log.result = 'success'
else: else:
@ -1099,7 +1100,25 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
else: else:
activity_log.exception_message = 'Restorer did not already exist' activity_log.exception_message = 'Restorer did not already exist'
else: else:
activity_log.exception_message = 'Reply not found, or object was not a reply' reply = PostReply.query.filter_by(ap_id=request_json['object']['object']).first()
if reply:
deletor = find_actor_or_create(request_json['object']['actor'], create_if_not_found=False)
if deletor:
if reply.author.id == deletor.id or reply.community.is_moderator(deletor) or reply.community.is_instance_admin(deletor):
reply.deleted = False
reply.deleted_by = None
if not reply.author.bot:
reply.post.reply_count += 1
reply.author.post_reply_count += 1
announce_activity_to_followers(reply.community, reply.author, request_json)
db.session.commit()
activity_log.result = 'success'
else:
activity_log.exception_message = 'Restore attempt denied'
else:
activity_log.exception_message = 'Restorer did not already exist'
else:
activity_log.exception_message = 'Object not found, or object was not a post or a reply'
elif request_json['type'] == 'Delete': elif request_json['type'] == 'Delete':
if isinstance(request_json['object'], str): if isinstance(request_json['object'], str):
ap_id = request_json['object'] # lemmy ap_id = request_json['object'] # lemmy
@ -1108,21 +1127,26 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
post = Post.query.filter_by(ap_id=ap_id).first() post = Post.query.filter_by(ap_id=ap_id).first()
# Delete post # Delete post
if post: if post:
if can_delete(request_json['actor'], post): deletor = find_actor_or_create(request_json['actor'], create_if_not_found=False)
if post.url and post.cross_posts is not None: if deletor:
old_cross_posts = Post.query.filter(Post.id.in_(post.cross_posts)).all() if post.author.id == deletor.id or post.community.is_moderator(deletor) or post.community.is_instance_admin(deletor):
post.cross_posts.clear() post.deleted = True
for ocp in old_cross_posts: post.delted_by = deletor.id
if ocp.cross_posts is not None: post.author.post_count -= 1
ocp.cross_posts.remove(post.id) post.community.post_count -= 1
post.delete_dependencies() if post.url and post.cross_posts is not None:
announce_activity_to_followers(post.community, post.author, request_json) old_cross_posts = Post.query.filter(Post.id.in_(post.cross_posts)).all()
post.deleted = True post.cross_posts.clear()
post.author.post_count -= 1 for ocp in old_cross_posts:
db.session.commit() if ocp.cross_posts is not None:
activity_log.result = 'success' ocp.cross_posts.remove(post.id)
announce_activity_to_followers(post.community, post.author, request_json)
db.session.commit()
activity_log.result = 'success'
else:
activity_log.exception_message = 'Delete attempt denied'
else: else:
activity_log.exception_message = 'Delete attempt denied' activity_log.exception_message = 'Deletor did not already exist'
else: else:
# Delete PostReply # Delete PostReply
reply = PostReply.query.filter_by(ap_id=ap_id).first() reply = PostReply.query.filter_by(ap_id=ap_id).first()

View file

@ -1323,24 +1323,22 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id
return 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 to_delete.author.id == deletor.id or deletor.is_admin() or community.is_moderator(deletor) or community.is_instance_admin(deletor):
if isinstance(to_delete, Post): if isinstance(to_delete, Post):
to_delete.delete_dependencies()
to_delete.deleted = True to_delete.deleted = True
to_delete.deleted_by = deletor.id
community.post_count -= 1 community.post_count -= 1
to_delete.author.post_count -= 1 to_delete.author.post_count -= 1
to_delete.deleted_by = deletor.id
db.session.commit() db.session.commit()
if to_delete.author.id != deletor.id: if to_delete.author.id != deletor.id:
add_to_modlog_activitypub('delete_post', deletor, community_id=community.id, add_to_modlog_activitypub('delete_post', deletor, community_id=community.id,
link_text=shorten_string(to_delete.title), link=f'post/{to_delete.id}') link_text=shorten_string(to_delete.title), link=f'post/{to_delete.id}')
elif isinstance(to_delete, PostReply): elif isinstance(to_delete, PostReply):
if not to_delete.author.bot:
to_delete.post.reply_count -= 1
to_delete.deleted = True to_delete.deleted = True
to_delete.deleted_by = deletor.id to_delete.deleted_by = deletor.id
to_delete.author.post_reply_count -= 1 to_delete.author.post_reply_count -= 1
to_delete.deleted_by = deletor.id if not to_delete.author.bot:
to_delete.post.reply_count -= 1
db.session.commit() db.session.commit()
if to_delete.author.id != deletor.id: if to_delete.author.id != deletor.id:
add_to_modlog_activitypub('delete_post_reply', deletor, community_id=community.id, add_to_modlog_activitypub('delete_post_reply', deletor, community_id=community.id,
@ -1383,7 +1381,6 @@ def restore_post_or_comment_task(object_json, aplog_id):
if restorer and community and to_restore: if restorer and community and to_restore:
if to_restore.author.id == restorer.id or restorer.is_admin() or community.is_moderator(restorer) or community.is_instance_admin(restorer): if to_restore.author.id == restorer.id or restorer.is_admin() or community.is_moderator(restorer) or community.is_instance_admin(restorer):
if isinstance(to_restore, Post): if isinstance(to_restore, Post):
# TODO: restore_dependencies()
to_restore.deleted = False to_restore.deleted = False
to_restore.deleted_by = None to_restore.deleted_by = None
community.post_count += 1 community.post_count += 1

View file

@ -2,7 +2,7 @@ from app.api.alpha import bp
from app.api.alpha.utils import get_site, post_site_block, \ from app.api.alpha.utils import get_site, post_site_block, \
get_search, \ get_search, \
get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, \ get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, \
get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, \ get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report, \
get_community_list, get_community, post_community_follow, post_community_block, \ get_community_list, get_community, post_community_follow, post_community_block, \
get_user, post_user_block get_user, post_user_block
from app.shared.auth import log_user_in from app.shared.auth import log_user_in
@ -242,6 +242,18 @@ def post_alpha_comment_delete():
return jsonify({"error": str(ex)}), 400 return jsonify({"error": str(ex)}), 400
@bp.route('/api/alpha/comment/report', methods=['POST'])
def post_alpha_comment_report():
if not current_app.debug:
return jsonify({'error': 'alpha api routes only available in debug mode'})
try:
auth = request.headers.get('Authorization')
data = request.get_json(force=True) or {}
return jsonify(post_reply_report(auth, data))
except Exception as ex:
return jsonify({"error": str(ex)}), 400
# User # User
@bp.route('/api/alpha/user', methods=['GET']) @bp.route('/api/alpha/user', methods=['GET'])
def get_alpha_user(): def get_alpha_user():
@ -325,7 +337,6 @@ def alpha_post():
@bp.route('/api/alpha/comment/remove', methods=['POST']) @bp.route('/api/alpha/comment/remove', methods=['POST'])
@bp.route('/api/alpha/comment/mark_as_read', methods=['POST']) @bp.route('/api/alpha/comment/mark_as_read', methods=['POST'])
@bp.route('/api/alpha/comment/distinguish', methods=['POST']) @bp.route('/api/alpha/comment/distinguish', methods=['POST'])
@bp.route('/api/alpha/comment/report', methods=['POST'])
@bp.route('/api/alpha/comment/report/resolve', methods=['PUT']) @bp.route('/api/alpha/comment/report/resolve', methods=['PUT'])
@bp.route('/api/alpha/comment/report/list', methods=['GET']) @bp.route('/api/alpha/comment/report/list', methods=['GET'])
def alpha_reply(): def alpha_reply():

View file

@ -1,7 +1,7 @@
from app.api.alpha.utils.site import get_site, post_site_block from app.api.alpha.utils.site import get_site, post_site_block
from app.api.alpha.utils.misc import get_search from app.api.alpha.utils.misc import get_search
from app.api.alpha.utils.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe from app.api.alpha.utils.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe
from app.api.alpha.utils.reply import get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete from app.api.alpha.utils.reply import get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report
from app.api.alpha.utils.community import get_community, get_community_list, post_community_follow, post_community_block from app.api.alpha.utils.community import get_community, get_community_list, post_community_follow, post_community_block
from app.api.alpha.utils.user import get_user, post_user_block from app.api.alpha.utils.user import get_user, post_user_block

View file

@ -17,15 +17,11 @@ def cached_community_list(type, user_id):
else: else:
communities = Community.query.filter_by(banned=False) communities = Community.query.filter_by(banned=False)
print(len(communities.all()))
if user_id is not None: if user_id is not None:
blocked_instance_ids = blocked_instances(user_id) blocked_instance_ids = blocked_instances(user_id)
if blocked_instance_ids: if blocked_instance_ids:
communities = communities.filter(Community.instance_id.not_in(blocked_instance_ids)) communities = communities.filter(Community.instance_id.not_in(blocked_instance_ids))
print(len(communities.all()))
return communities.all() return communities.all()

View file

@ -98,10 +98,7 @@ def get_post(auth, data):
user_id = authorise_api_user(auth) if auth else None user_id = authorise_api_user(auth) if auth else None
post_json = post_view(post=id, variant=3, user_id=user_id) post_json = post_view(post=id, variant=3, user_id=user_id)
if post_json: return post_json
return post_json
else:
raise Exception('post_not_found')
# would be in app/constants.py # would be in app/constants.py

View file

@ -1,9 +1,9 @@
from app import cache from app import cache
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected, string_expected from app.api.alpha.utils.validators import required, integer_expected, boolean_expected, string_expected
from app.api.alpha.views import reply_view from app.api.alpha.views import reply_view, reply_report_view
from app.models import PostReply, Post from app.models import PostReply, Post
from app.shared.reply import vote_for_reply, bookmark_the_post_reply, remove_the_bookmark_from_post_reply, toggle_post_reply_notification, make_reply, edit_reply, \ from app.shared.reply import vote_for_reply, bookmark_the_post_reply, remove_the_bookmark_from_post_reply, toggle_post_reply_notification, make_reply, edit_reply, \
delete_reply, restore_reply delete_reply, restore_reply, report_reply
from app.utils import authorise_api_user, blocked_users, blocked_instances from app.utils import authorise_api_user, blocked_users, blocked_instances
from sqlalchemy import desc from sqlalchemy import desc
@ -131,9 +131,7 @@ def post_reply(auth, data):
language_id = 2 # FIXME: use site language language_id = 2 # FIXME: use site language
input = {'body': body, 'notify_author': True, 'language_id': language_id} input = {'body': body, 'notify_author': True, 'language_id': language_id}
post = Post.query.get(post_id) post = Post.query.filter_by(id=post_id).one()
if not post:
raise Exception('parent_not_found')
user_id, reply = make_reply(input, post, parent_id, SRC_API, auth) user_id, reply = make_reply(input, post, parent_id, SRC_API, auth)
@ -153,12 +151,8 @@ def put_reply(auth, data):
language_id = 2 # FIXME: use site language language_id = 2 # FIXME: use site language
input = {'body': body, 'notify_author': True, 'language_id': language_id} input = {'body': body, 'notify_author': True, 'language_id': language_id}
reply = PostReply.query.get(reply_id) reply = PostReply.query.filter_by(id=reply_id).one()
if not reply: post = Post.query.filter_by(id=reply.post_id).one()
raise Exception('reply_not_found')
post = Post.query.get(reply.post_id)
if not post:
raise Exception('post_not_found')
user_id, reply = edit_reply(input, reply, post, SRC_API, auth) user_id, reply = edit_reply(input, reply, post, SRC_API, auth)
@ -182,4 +176,18 @@ def post_reply_delete(auth, data):
reply_json = reply_view(reply=reply, variant=4, user_id=user_id) reply_json = reply_view(reply=reply, variant=4, user_id=user_id)
return reply_json return reply_json
return {}
def post_reply_report(auth, data):
required(['comment_id', 'reason'], data)
integer_expected(['comment_id'], data)
string_expected(['reason'], data)
reply_id = data['comment_id']
reason = data['reason']
input = {'reason': reason, 'description': '', 'report_remote': True}
user_id, report = report_reply(reply_id, input, SRC_API, auth)
reply_json = reply_report_view(report=report, reply_id=reply_id, user_id=user_id)
return reply_json

View file

@ -12,9 +12,7 @@ from sqlalchemy import text
def post_view(post: Post | int, variant, stub=False, user_id=None, my_vote=0): def post_view(post: Post | int, variant, stub=False, user_id=None, my_vote=0):
if isinstance(post, int): if isinstance(post, int):
post = Post.query.get(post) post = Post.query.filter_by(id=post, deleted=False).one()
if not post or post.deleted:
raise Exception('post_not_found')
# Variant 1 - models/post/post.dart # Variant 1 - models/post/post.dart
if variant == 1: if variant == 1:
@ -133,9 +131,7 @@ def cached_user_view_variant_1(user: User, stub=False):
# 'user' param can be anyone (including the logged in user), 'user_id' param belongs to the user making the request # 'user' param can be anyone (including the logged in user), 'user_id' param belongs to the user making the request
def user_view(user: User | int, variant, stub=False, user_id=None): def user_view(user: User | int, variant, stub=False, user_id=None):
if isinstance(user, int): if isinstance(user, int):
user = User.query.get(user) user = User.query.filter_by(id=user).one()
if not user:
raise Exception('user_not_found')
# Variant 1 - models/person/person.dart # Variant 1 - models/person/person.dart
if variant == 1: if variant == 1:
@ -190,12 +186,10 @@ def cached_community_view_variant_1(community: Community, stub=False):
def community_view(community: Community | int | str, variant, stub=False, user_id=None): def community_view(community: Community | int | str, variant, stub=False, user_id=None):
if isinstance(community, int): if isinstance(community, int):
community = Community.query.get(community) community = Community.query.filter_by(id=community).one()
elif isinstance(community, str): elif isinstance(community, str):
name, ap_domain = community.split('@') name, ap_domain = community.split('@')
community = Community.query.filter_by(name=name, ap_domain=ap_domain).first() community = Community.query.filter_by(name=name, ap_domain=ap_domain).one()
if not community:
raise Exception('community_not_found')
# Variant 1 - models/community/community.dart # Variant 1 - models/community/community.dart
if variant == 1: if variant == 1:
@ -269,9 +263,7 @@ def calculate_if_has_children(reply): # result used as True / False
def reply_view(reply: PostReply | int, variant, user_id=None, my_vote=0): def reply_view(reply: PostReply | int, variant, user_id=None, my_vote=0):
if isinstance(reply, int): if isinstance(reply, int):
reply = PostReply.query.get(reply) reply = PostReply.query.filter_by(id=reply).one()
if not reply:
raise Exception('reply_not_found')
# Variant 1 - models/comment/comment.dart # Variant 1 - models/comment/comment.dart
if variant == 1: if variant == 1:
@ -338,6 +330,48 @@ def reply_view(reply: PostReply | int, variant, user_id=None, my_vote=0):
return v4 return v4
def reply_report_view(report, reply_id, user_id):
# views/comment_report_view.dart - /comment/report api endpoint
reply_json = reply_view(reply=reply_id, variant=2, user_id=user_id)
post_json = post_view(post=reply_json['comment']['post_id'], variant=1, stub=True)
community_json = community_view(community=post_json['community_id'], variant=1, stub=True)
banned = db.session.execute(text('SELECT user_id FROM "community_ban" WHERE user_id = :user_id and community_id = :community_id'), {'user_id': report.reporter_id, 'community_id': community_json['id']}).scalar()
moderator = db.session.execute(text('SELECT is_moderator FROM "community_member" WHERE user_id = :user_id and community_id = :community_id'), {'user_id': report.reporter_id, 'community_id': community_json['id']}).scalar()
admin = db.session.execute(text('SELECT user_id FROM "user_role" WHERE user_id = :user_id and role_id = 4'), {'user_id': report.reporter_id}).scalar()
creator_banned_from_community = True if banned else False
creator_is_moderator = True if moderator else False
creator_is_admin = True if admin else False
v1 = {
'comment_report_view': {
'comment_report': {
'id': report.id,
'creator_id': report.reporter_id,
'comment_id': report.suspect_post_reply_id,
'original_comment_text': reply_json['comment']['body'],
'reason': report.reasons,
'resolved': report.status == 3,
'published': report.created_at.isoformat() + 'Z'
},
'comment': reply_json['comment'],
'post': post_json,
'community': community_json,
'creator': user_view(user=user_id, variant=1, stub=True),
'comment_creator': user_view(user=report.suspect_user_id, variant=1, stub=True),
'counts': reply_json['counts'],
'creator_banned_from_community': creator_banned_from_community,
'creator_is_moderator': creator_is_moderator,
'creator_is_admin': creator_is_admin,
'creator_blocked': False,
'subscribed': reply_json['subscribed'],
'saved': reply_json['saved']
}
}
return v1
def search_view(type): def search_view(type):
v1 = { v1 = {
'type_': type, 'type_': type,
@ -351,9 +385,7 @@ def search_view(type):
def instance_view(instance: Instance | int, variant): def instance_view(instance: Instance | int, variant):
if isinstance(instance, int): if isinstance(instance, int):
instance = Instance.query.get(instance) instance = Instance.query.filter_by(id=instance).one()
if not instance:
raise Exception('instance_not_found')
if variant == 1: if variant == 1:
include = ['id', 'domain', 'software', 'version'] include = ['id', 'domain', 'software', 'version']

View file

@ -527,8 +527,8 @@ def delete_post_from_community(post_id):
def delete_post_from_community_task(post_id): def delete_post_from_community_task(post_id):
post = Post.query.get(post_id) post = Post.query.get(post_id)
community = post.community community = post.community
post.delete_dependencies()
post.deleted = True post.deleted = True
post.deleted_by = current_user.id
db.session.commit() db.session.commit()
if not community.local_only: if not community.local_only:

View file

@ -1041,11 +1041,8 @@ class User(UserMixin, db.Model):
{'user_id': self.id, 'type': NOTIF_USER}).scalars()) {'user_id': self.id, 'type': NOTIF_USER}).scalars())
def encode_jwt_token(self): def encode_jwt_token(self):
try: payload = {'sub': str(self.id), 'iss': current_app.config['SERVER_NAME'], 'iat': int(time())}
payload = {'sub': str(self.id), 'iss': current_app.config['SERVER_NAME'], 'iat': int(time())} return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
except Exception as e:
return str(e)
# mark a post as 'read' for this user # mark a post as 'read' for this user
def mark_post_as_read(self, post): def mark_post_as_read(self, post):
@ -1377,10 +1374,19 @@ class Post(db.Model):
db.session.query(PollChoice).filter(PollChoice.post_id == self.id).delete() db.session.query(PollChoice).filter(PollChoice.post_id == self.id).delete()
db.session.query(Poll).filter(Poll.post_id == self.id).delete() db.session.query(Poll).filter(Poll.post_id == self.id).delete()
db.session.query(Report).filter(Report.suspect_post_id == self.id).delete() db.session.query(Report).filter(Report.suspect_post_id == self.id).delete()
db.session.execute(text('DELETE FROM "post_reply_vote" WHERE post_reply_id IN (SELECT id FROM post_reply WHERE post_id = :post_id)'),
{'post_id': self.id})
db.session.execute(text('DELETE FROM "post_reply" WHERE post_id = :post_id'), {'post_id': self.id})
db.session.execute(text('DELETE FROM "post_vote" WHERE post_id = :post_id'), {'post_id': self.id}) db.session.execute(text('DELETE FROM "post_vote" WHERE post_id = :post_id'), {'post_id': self.id})
reply_ids = db.session.execute(text('SELECT id FROM "post_reply" WHERE post_id = :post_id'), {'post_id': self.id}).scalars()
reply_ids = tuple(reply_ids)
if reply_ids:
db.session.execute(text('DELETE FROM "post_reply_vote" WHERE post_reply_id IN :reply_ids'), {'reply_ids': reply_ids})
db.session.execute(text('DELETE FROM "post_reply_bookmark" WHERE post_reply_id IN :reply_ids'), {'reply_ids': reply_ids})
db.session.execute(text('DELETE FROM "report" WHERE suspect_post_reply_id IN :reply_ids'), {'reply_ids': reply_ids})
db.session.execute(text('DELETE FROM "post_reply" WHERE post_id = :post_id'), {'post_id': self.id})
self.community.post_reply_count = db.session.execute(text('SELECT COUNT(id) as c FROM "post_reply" WHERE community_id = :community_id AND deleted = false'),
{'community_id': self.community_id}).scalar()
if self.image_id: if self.image_id:
file = File.query.get(self.image_id) file = File.query.get(self.image_id)
file.delete_from_disk() file.delete_from_disk()

View file

@ -670,9 +670,13 @@ def add_reply(post_id: int, comment_id: int):
@bp.route('/post/<int:post_id>/options', methods=['GET']) @bp.route('/post/<int:post_id>/options', methods=['GET'])
def post_options(post_id: int): def post_options(post_id: int):
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
if current_user.is_anonymous or not current_user.is_admin(): if post.deleted:
if post.deleted: if current_user.is_anonymous:
abort(404) abort(404)
if (not post.community.is_moderator() and
not current_user.is_admin() and
(post.deleted_by is not None and post.deleted_by != current_user.id)):
abort(401)
existing_bookmark = [] existing_bookmark = []
if current_user.is_authenticated: if current_user.is_authenticated:
@ -1025,7 +1029,6 @@ def post_delete_post(community: Community, post: Post, user_id: int, federate_al
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)
post.delete_dependencies()
post.deleted = True post.deleted = True
post.deleted_by = user_id post.deleted_by = user_id
post.author.post_count -= 1 post.author.post_count -= 1
@ -1093,14 +1096,19 @@ def post_delete_post(community: Community, post: Post, user_id: int, federate_al
@login_required @login_required
def post_restore(post_id: int): def post_restore(post_id: int):
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
if post.community.is_moderator() or post.community.is_owner() or current_user.is_admin(): if post.user_id == current_user.id or post.community.is_moderator() or post.community.is_owner() or current_user.is_admin():
if post.deleted_by == post.user_id:
was_mod_deletion = False
else:
was_mod_deletion = True
post.deleted = False post.deleted = False
post.deleted_by = None
post.author.post_count += 1 post.author.post_count += 1
post.community.post_count += 1 post.community.post_count += 1
db.session.commit() db.session.commit()
# Federate un-delete # Federate un-delete
if post.is_local(): if not post.community.local_only:
delete_json = { delete_json = {
"actor": current_user.public_url(), "actor": current_user.public_url(),
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": ["https://www.w3.org/ns/activitystreams#Public"],
@ -1116,31 +1124,40 @@ def post_restore(post_id: int):
], ],
'object': post.ap_id, 'object': post.ap_id,
'uri': post.ap_id, 'uri': post.ap_id,
"summary": "bad post",
}, },
"cc": [post.community.public_url()], "cc": [post.community.public_url()],
"audience": post.author.public_url(), "audience": post.community.public_url(),
"type": "Undo", "type": "Undo",
"id": f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}" "id": f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}"
} }
if was_mod_deletion:
delete_json['object']['summary'] = "Deleted by mod"
announce = { if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
"id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", if not was_mod_deletion or (was_mod_deletion and post.community.is_moderator(current_user)):
"type": 'Announce', success = post_request(post.community.ap_inbox_url, delete_json, current_user.private_key,
"to": [ current_user.public_url() + '#main-key')
"https://www.w3.org/ns/activitystreams#Public" if success is False or isinstance(success, str):
], flash('Failed to send delete to remote server', 'error')
"actor": post.community.public_url(),
"cc": [
post.community.ap_followers_url
],
'@context': default_context(),
'object': delete_json
}
for instance in post.community.following_instances(): else: # local community - send it to followers on remote instances
if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): announce = {
send_to_remote_instance(instance.id, post.community.id, 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.user_id != current_user.id: if post.user_id != current_user.id:
add_to_modlog('restore_post', community_id=post.community.id, link_text=shorten_string(post.title), add_to_modlog('restore_post', community_id=post.community.id, link_text=shorten_string(post.title),
@ -1150,6 +1167,23 @@ def post_restore(post_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>/purge', methods=['GET', 'POST'])
@login_required
def post_purge(post_id: int):
post = Post.query.get_or_404(post_id)
if not post.deleted:
abort(404)
if post.deleted_by == current_user.id or post.community.is_moderator() or current_user.is_admin():
post.delete_dependencies()
db.session.delete(post)
db.session.commit()
flash(_('Post purged.'))
else:
abort(401)
return redirect(url_for('user.show_profile_by_id', user_id=post.user_id))
@bp.route('/post/<int:post_id>/bookmark', methods=['GET', 'POST']) @bp.route('/post/<int:post_id>/bookmark', methods=['GET', 'POST'])
@login_required @login_required
def post_bookmark(post_id: int): def post_bookmark(post_id: int):
@ -1750,9 +1784,7 @@ def post_reply_purge(post_id: int, comment_id: int):
post_reply = PostReply.query.get_or_404(comment_id) post_reply = PostReply.query.get_or_404(comment_id)
if not post_reply.deleted: if not post_reply.deleted:
abort(404) 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): if post_reply.deleted_by == current_user.id or post.community.is_moderator() or current_user.is_admin():
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(): if not post_reply.has_replies():
post_reply.delete_dependencies() post_reply.delete_dependencies()
db.session.delete(post_reply) db.session.delete(post_reply)

View file

@ -21,23 +21,21 @@ def log_user_in(input, src):
if src == SRC_WEB: if src == SRC_WEB:
username = input.user_name.data username = input.user_name.data
password = input.password.data password = input.password.data
user = User.query.filter_by(user_name=username, ap_id=None).first()
elif src == SRC_API: elif src == SRC_API:
required(["username_or_email", "password"], input) required(["username_or_email", "password"], input)
string_expected(["username_or_email", "password"], input) string_expected(["username_or_email", "password"], input)
username = input['username_or_email'] username = input['username_or_email']
password = input['password'] password = input['password']
user = User.query.filter_by(user_name=username, ap_id=None, deleted=False).one()
else: else:
return None return None
user = User.query.filter_by(user_name=username, ap_id=None).first() if src == SRC_WEB:
if user is None or user.deleted:
if user is None or user.deleted:
if src == SRC_WEB:
flash(_('No account exists with that user name.'), 'error') flash(_('No account exists with that user name.'), 'error')
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
elif src == SRC_API:
raise Exception('incorrect_login')
if not user.check_password(password): if not user.check_password(password):
if src == SRC_WEB: if src == SRC_WEB:
@ -96,13 +94,9 @@ def log_user_in(input, src):
response.set_cookie('low_bandwidth', '0', expires=datetime(year=2099, month=12, day=30)) response.set_cookie('low_bandwidth', '0', expires=datetime(year=2099, month=12, day=30))
return response return response
elif src == SRC_API: elif src == SRC_API:
token = user.encode_jwt_token() login_json = {
if token: 'jwt': user.encode_jwt_token(),
login_json = { 'registration_created': user.verified,
"jwt": token, 'verify_email_sent': True
"registration_created": user.verified, }
"verify_email_sent": True return login_json
}
return login_json
else:
raise Exception('could_not_generate_token')

View file

@ -17,9 +17,7 @@ SRC_API = 3
# call from admin.federation not tested # call from admin.federation not tested
def join_community(community_id: int, src, auth=None, user_id=None, main_user_name=True): def join_community(community_id: int, src, auth=None, user_id=None, main_user_name=True):
if src == SRC_API: if src == SRC_API:
community = Community.query.get(community_id) community = Community.query.filter_by(id=community_id).one()
if not community:
raise Exception('community_not_found')
user = authorise_api_user(auth, return_type='model') user = authorise_api_user(auth, return_type='model')
else: else:
community = Community.query.get_or_404(community_id) community = Community.query.get_or_404(community_id)
@ -112,9 +110,7 @@ def join_community(community_id: int, src, auth=None, user_id=None, main_user_na
# function can be shared between WEB and API (only API calls it for now) # function can be shared between WEB and API (only API calls it for now)
def leave_community(community_id: int, src, auth=None): def leave_community(community_id: int, src, auth=None):
if src == SRC_API: if src == SRC_API:
community = Community.query.get(community_id) community = Community.query.filter_by(id=community_id).one()
if not community:
raise Exception('community_not_found')
user = authorise_api_user(auth, return_type='model') user = authorise_api_user(auth, return_type='model')
else: else:
community = Community.query.get_or_404(community_id) community = Community.query.get_or_404(community_id)

View file

@ -20,9 +20,7 @@ SRC_API = 3
def vote_for_post(post_id: int, vote_direction, src, auth=None): def vote_for_post(post_id: int, vote_direction, src, auth=None):
if src == SRC_API: if src == SRC_API:
post = Post.query.get(post_id) post = Post.query.filter_by(id=post_id).one()
if not post:
raise Exception('post_not_found')
user = authorise_api_user(auth, return_type='model') user = authorise_api_user(auth, return_type='model')
else: else:
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
@ -97,9 +95,7 @@ def vote_for_post(post_id: int, vote_direction, src, auth=None):
# post_bookmark in app/post/routes would just need to do 'return bookmark_the_post(post_id, SRC_WEB)' # post_bookmark in app/post/routes would just need to do 'return bookmark_the_post(post_id, SRC_WEB)'
def bookmark_the_post(post_id: int, src, auth=None): def bookmark_the_post(post_id: int, src, auth=None):
if src == SRC_API: if src == SRC_API:
post = Post.query.get(post_id) post = Post.query.filter_by(id=post_id, deleted=False).one()
if not post or post.deleted:
raise Exception('post_not_found')
user_id = authorise_api_user(auth) user_id = authorise_api_user(auth)
else: else:
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
@ -127,9 +123,7 @@ def bookmark_the_post(post_id: int, src, auth=None):
# post_remove_bookmark in app/post/routes would just need to do 'return remove_the_bookmark_from_post(post_id, SRC_WEB)' # post_remove_bookmark in app/post/routes would just need to do 'return remove_the_bookmark_from_post(post_id, SRC_WEB)'
def remove_the_bookmark_from_post(post_id: int, src, auth=None): def remove_the_bookmark_from_post(post_id: int, src, auth=None):
if src == SRC_API: if src == SRC_API:
post = Post.query.get(post_id) post = Post.query.filter_by(id=post_id, deleted=False).one()
if not post or post.deleted:
raise Exception('post_not_found')
user_id = authorise_api_user(auth) user_id = authorise_api_user(auth)
else: else:
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
@ -156,9 +150,7 @@ def remove_the_bookmark_from_post(post_id: int, src, auth=None):
def toggle_post_notification(post_id: int, src, auth=None): def toggle_post_notification(post_id: int, src, auth=None):
# Toggle whether the current user is subscribed to notifications about top-level replies to this post or not # Toggle whether the current user is subscribed to notifications about top-level replies to this post or not
if src == SRC_API: if src == SRC_API:
post = Post.query.get(post_id) post = Post.query.filter_by(id=post_id, deleted=False).one()
if not post or post.deleted:
raise Exception('post_not_found')
user_id = authorise_api_user(auth) user_id = authorise_api_user(auth)
else: else:
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
@ -173,10 +165,8 @@ def toggle_post_notification(post_id: int, src, auth=None):
db.session.delete(existing_notification) db.session.delete(existing_notification)
db.session.commit() db.session.commit()
else: # no subscription yet, so make one else: # no subscription yet, so make one
new_notification = NotificationSubscription(name=shorten_string(_('Replies to my post %(post_title)s', new_notification = NotificationSubscription(name=shorten_string(_('Replies to my post %(post_title)s', post_title=post.title)),
post_title=post.title)), user_id=user_id, entity_id=post.id, type=NOTIF_POST)
user_id=user_id, entity_id=post.id,
type=NOTIF_POST)
db.session.add(new_notification) db.session.add(new_notification)
db.session.commit() db.session.commit()

View file

@ -2,7 +2,7 @@ from app import cache, db
from app.activitypub.signature import default_context, post_request_in_background, post_request from app.activitypub.signature import default_context, post_request_in_background, post_request
from app.community.util import send_to_remote_instance from app.community.util import send_to_remote_instance
from app.constants import * from app.constants import *
from app.models import NotificationSubscription, Post, PostReply, PostReplyBookmark, User, utcnow from app.models import Instance, Notification, NotificationSubscription, Post, PostReply, PostReplyBookmark, Report, Site, User, utcnow
from app.utils import gibberish, instance_banned, render_template, authorise_api_user, recently_upvoted_post_replies, recently_downvoted_post_replies, shorten_string, \ from app.utils import gibberish, instance_banned, render_template, authorise_api_user, recently_upvoted_post_replies, recently_downvoted_post_replies, shorten_string, \
piefed_markdown_to_lemmy_markdown, markdown_to_html, ap_datetime piefed_markdown_to_lemmy_markdown, markdown_to_html, ap_datetime
@ -21,9 +21,7 @@ SRC_API = 3
def vote_for_reply(reply_id: int, vote_direction, src, auth=None): def vote_for_reply(reply_id: int, vote_direction, src, auth=None):
if src == SRC_API: if src == SRC_API:
reply = PostReply.query.get(reply_id) reply = PostReply.query.filter_by(id=reply_id).one()
if not reply:
raise Exception('reply_not_found')
user = authorise_api_user(auth, return_type='model') user = authorise_api_user(auth, return_type='model')
else: else:
reply = PostReply.query.get_or_404(reply_id) reply = PostReply.query.get_or_404(reply_id)
@ -98,9 +96,7 @@ def vote_for_reply(reply_id: int, vote_direction, src, auth=None):
# post_reply_bookmark in app/post/routes would just need to do 'return bookmark_the_post_reply(comment_id, SRC_WEB)' # post_reply_bookmark in app/post/routes would just need to do 'return bookmark_the_post_reply(comment_id, SRC_WEB)'
def bookmark_the_post_reply(comment_id: int, src, auth=None): def bookmark_the_post_reply(comment_id: int, src, auth=None):
if src == SRC_API: if src == SRC_API:
post_reply = PostReply.query.get(comment_id) post_reply = PostReply.query.filter_by(id=comment_id, deleted=False).one()
if not post_reply or post_reply.deleted:
raise Exception('comment_not_found')
user_id = authorise_api_user(auth) user_id = authorise_api_user(auth)
else: else:
post_reply = PostReply.query.get_or_404(comment_id) post_reply = PostReply.query.get_or_404(comment_id)
@ -129,9 +125,7 @@ def bookmark_the_post_reply(comment_id: int, src, auth=None):
# post_reply_remove_bookmark in app/post/routes would just need to do 'return remove_the_bookmark_from_post_reply(comment_id, SRC_WEB)' # post_reply_remove_bookmark in app/post/routes would just need to do 'return remove_the_bookmark_from_post_reply(comment_id, SRC_WEB)'
def remove_the_bookmark_from_post_reply(comment_id: int, src, auth=None): def remove_the_bookmark_from_post_reply(comment_id: int, src, auth=None):
if src == SRC_API: if src == SRC_API:
post_reply = PostReply.query.get(comment_id) post_reply = PostReply.query.filter_by(id=comment_id, deleted=False).one()
if not post_reply or post_reply.deleted:
raise Exception('comment_not_found')
user_id = authorise_api_user(auth) user_id = authorise_api_user(auth)
else: else:
post_reply = PostReply.query.get_or_404(comment_id) post_reply = PostReply.query.get_or_404(comment_id)
@ -158,9 +152,7 @@ def remove_the_bookmark_from_post_reply(comment_id: int, src, auth=None):
def toggle_post_reply_notification(post_reply_id: int, src, auth=None): def toggle_post_reply_notification(post_reply_id: int, src, auth=None):
# Toggle whether the current user is subscribed to notifications about replies to this reply or not # Toggle whether the current user is subscribed to notifications about replies to this reply or not
if src == SRC_API: if src == SRC_API:
post_reply = PostReply.query.get(post_reply_id) post_reply = PostReply.query.filter_by(id=post_reply_id, deleted=False).one()
if not post_reply or post_reply.deleted:
raise Exception('comment_not_found')
user_id = authorise_api_user(auth) user_id = authorise_api_user(auth)
else: else:
post_reply = PostReply.query.get_or_404(post_reply_id) post_reply = PostReply.query.get_or_404(post_reply_id)
@ -226,9 +218,7 @@ def make_reply(input, post, parent_id, src, auth=None):
language_id = input.language_id.data language_id = input.language_id.data
if parent_id: if parent_id:
parent_reply = PostReply.query.get(parent_id) parent_reply = PostReply.query.filter_by(id=parent_id).one()
if not parent_reply:
raise Exception('parent_reply_not_found')
else: else:
parent_reply = None parent_reply = None
@ -375,7 +365,7 @@ def edit_reply(input, reply, post, src, auth=None):
flash(_('Your changes have been saved.'), 'success') flash(_('Your changes have been saved.'), 'success')
if reply.parent_id: if reply.parent_id:
in_reply_to = PostReply.query.get(reply.parent_id) in_reply_to = PostReply.query.filter_by(id=reply.parent_id).one()
else: else:
in_reply_to = post in_reply_to = post
@ -642,3 +632,77 @@ def restore_reply(reply_id, src, auth):
return user.id, reply return user.id, reply
else: else:
return return
def report_reply(reply_id, input, src, auth=None):
if src == SRC_API:
reply = PostReply.query.filter_by(id=reply_id).one()
user = authorise_api_user(auth, return_type='model')
reason = input['reason']
description = input['description']
report_remote = input['report_remote']
else:
reply = PostReply.query.get_or_404(reply_id)
user = current_user
reason = input.reasons_to_string(input.reasons.data)
description = input.description.data
report_remote = input.report_remote.data
if reply.reports == -1: # When a mod decides to ignore future reports, reply.reports is set to -1
if src == SRC_API:
raise Exception('already_reported')
else:
flash(_('Comment has already been reported, thank you!'))
return
report = Report(reasons=reason, description=description, type=2, reporter_id=user.id, suspect_post_id=reply.post.id, suspect_community_id=reply.community.id,
suspect_user_id=reply.author.id, suspect_post_reply_id=reply.id, in_community_id=reply.community.id, source_instance_id=1)
db.session.add(report)
# Notify moderators
already_notified = set()
for mod in reply.community.moderators():
moderator = User.query.get(mod.user_id)
if moderator and moderator.is_local():
notification = Notification(user_id=mod.user_id, title=_('A comment has been reported'),
url=f"https://{current_app.config['SERVER_NAME']}/comment/{reply.id}",
author_id=user.id)
db.session.add(notification)
already_notified.add(mod.user_id)
reply.reports += 1
# todo: only notify admins for certain types of report
for admin in Site.admins():
if admin.id not in already_notified:
notify = Notification(title='Suspicious content', url='/admin/reports', user_id=admin.id, author_id=user.id)
db.session.add(notify)
admin.unread_notifications += 1
db.session.commit()
# federate report to originating instance
if not reply.community.is_local() and report_remote:
summary = reason
if description:
summary += ' - ' + description
report_json = {
'actor': user.public_url(),
'audience': reply.community.public_url(),
'content': None,
'id': f"https://{current_app.config['SERVER_NAME']}/activities/flag/{gibberish(15)}",
'object': reply.ap_id,
'summary': summary,
'to': [
reply.community.public_url()
],
'type': 'Flag'
}
instance = Instance.query.get(reply.community.instance_id)
if reply.community.ap_inbox_url and not user.has_blocked_instance(instance.id) and not instance_banned(instance.domain):
success = post_request(reply.community.ap_inbox_url, report_json, user.private_key, user.public_url() + '#main-key')
if success is False or isinstance(success, str):
if src == SRC_WEB:
flash('Failed to send report to remote server', 'error')
if src == SRC_API:
return user.id, report
else:
return

View file

@ -14,7 +14,11 @@
{% if teaser -%} {% if teaser -%}
<div class="row"> <div class="row">
<div class="col-12 hidable"> <div class="col-12 hidable">
reply to: <a href="{{ url_for('activitypub.post_ap', post_id=post_reply.post.id, _anchor='comment_' + str(post_reply.id)) }}">{{ post_reply.post.title | truncate(80, True) }}</a> {% if post_reply.post.deleted: -%}
reply to: [deleted post]
{% else -%}
reply to: <a href="{{ url_for('activitypub.post_ap', post_id=post_reply.post.id, _anchor='comment_' + str(post_reply.id)) }}">{{ post_reply.post.title | truncate(80, True) }}</a>
{% endif -%}
<span class="comment_community">in {{ render_communityname(post_reply.post.community) }}</span> <span class="comment_community">in {{ render_communityname(post_reply.post.community) }}</span>
</div> </div>
</div> </div>
@ -53,7 +57,11 @@
</div> </div>
<div class="comment_actions hidable"> <div class="comment_actions hidable">
{% if post_reply.post.comments_enabled -%} {% if post_reply.post.comments_enabled -%}
<a href="{{ url_for('post.add_reply', post_id=post_reply.post.id, comment_id=post_reply.id) }}" class="" rel="nofollow noindex"><span class="fe fe-reply"></span> reply</a> {% if not post_reply.post.deleted and not post_reply.deleted -%}
<a href="{{ url_for('post.add_reply', post_id=post_reply.post.id, comment_id=post_reply.id) }}" class="" rel="nofollow noindex"><span class="fe fe-reply"></span> reply</a>
{% else -%}
<span class="fe fe-reply"></span> reply
{% endif -%}
{% endif -%} {% endif -%}
<div class="voting_buttons_new"> <div class="voting_buttons_new">
{% with comment=post_reply, community=post_reply.post.community -%} {% with comment=post_reply, community=post_reply.post.community -%}
@ -74,7 +82,9 @@
{% endwith -%} {% endwith -%}
{% endif -%} {% endif -%}
</div> </div>
<a href="{{ url_for('post.post_reply_options', post_id=post_reply.post.id, comment_id=post_reply.id) }}" class="comment_actions_link" rel="nofollow noindex" aria-label="{{ _('Comment options') }}"><span class="fe fe-options" title="Options"> </span></a> {% if not post_reply.post.deleted -%}
<a href="{{ url_for('post.post_reply_options', post_id=post_reply.post.id, comment_id=post_reply.id) }}" class="comment_actions_link" rel="nofollow noindex" aria-label="{{ _('Comment options') }}"><span class="fe fe-options" title="Options"> </span></a>
{% endif -%}
</div> </div>
{% if not post_reply.author.indexable -%}<!--googleon all-->{% endif -%} {% if not post_reply.author.indexable -%}<!--googleon all-->{% endif -%}
{% if collapsed -%} {% if collapsed -%}

View file

@ -19,8 +19,10 @@
{% endif -%} {% endif -%}
{% if post.user_id == current_user.id or post.community.is_moderator() or post.community.is_owner() or current_user.is_admin() -%} {% if post.user_id == current_user.id or post.community.is_moderator() or post.community.is_owner() or current_user.is_admin() -%}
{% if post.deleted -%} {% if post.deleted -%}
<li><a href="{{ url_for('post.post_restore', post_id=post.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span> <li><a href="{{ url_for('post.post_restore', post_id=post.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-arrow-up"></span>
{{ _('Restore') }}</a></li> {{ _('Restore') }}</a></li>
<li><a href="{{ url_for('post.post_purge', post_id=post.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete red"></span>
{{ _('Purge') }}</a></li>
{% else -%} {% else -%}
<li><a href="{{ url_for('post.post_delete', post_id=post.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span> <li><a href="{{ url_for('post.post_delete', post_id=post.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span>
{{ _('Delete') }}</a></li> {{ _('Delete') }}</a></li>

View file

@ -6,6 +6,7 @@
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span> <span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
{% if post.deleted -%}<span class="red fe fe-delete" title="{{ _('Post deleted') }}"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}"> <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
{% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%} {% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%}

View file

@ -9,6 +9,7 @@
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span> <span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
{% if post.deleted -%}<span class="red fe fe-delete" title="{{ _('Post deleted') }}"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}"> <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
{% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%} {% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%}

View file

@ -12,6 +12,7 @@
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span> <span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
{% if post.deleted -%}<span class="red fe fe-delete" title="{{ _('Post deleted') }}"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}"> <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
{% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%} {% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%}

View file

@ -7,6 +7,7 @@
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span> <span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
{% if post.deleted -%}<span class="red fe fe-delete" title="{{ _('Post deleted') }}"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}"> <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
{% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%} {% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%}

View file

@ -14,6 +14,7 @@
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span> <span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
{% if post.deleted -%}<span class="red fe fe-delete" title="{{ _('Post deleted') }}"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}"> <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
{% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%} {% if post.community.icon_id and not low_bandwidth %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image('tiny') }}" alt="Community icon" />{% endif -%}

View file

@ -55,7 +55,6 @@ def show_profile(user):
post_page = request.args.get('post_page', 1, type=int) post_page = request.args.get('post_page', 1, type=int)
replies_page = request.args.get('replies_page', 1, type=int) replies_page = request.args.get('replies_page', 1, type=int)
posts = Post.query.filter_by(user_id=user.id).filter(Post.deleted == False).order_by(desc(Post.posted_at)).paginate(page=post_page, per_page=50, error_out=False)
moderates = Community.query.filter_by(banned=False).join(CommunityMember).filter(CommunityMember.user_id == user.id)\ moderates = Community.query.filter_by(banned=False).join(CommunityMember).filter(CommunityMember.user_id == user.id)\
.filter(or_(CommunityMember.is_moderator, CommunityMember.is_owner)) .filter(or_(CommunityMember.is_moderator, CommunityMember.is_owner))
if current_user.is_authenticated and (user.id == current_user.get_id() or current_user.is_admin()): if current_user.is_authenticated and (user.id == current_user.get_id() or current_user.is_admin()):
@ -65,10 +64,13 @@ def show_profile(user):
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 and not current_user.is_admin()): 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)
posts = Post.query.filter_by(user_id=user.id).filter(Post.deleted == False).order_by(desc(Post.posted_at)).paginate(page=post_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) 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(): elif current_user.is_admin():
posts = Post.query.filter_by(user_id=user.id).order_by(desc(Post.posted_at)).paginate(page=post_page, per_page=50, error_out=False)
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) 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: elif current_user.id == user.id:
posts = Post.query.filter_by(user_id=user.id).filter(or_(Post.deleted == False, Post.deleted_by == user.id)).order_by(desc(Post.posted_at)).paginate(page=post_page, per_page=50, error_out=False)
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) 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

View file

@ -1250,23 +1250,17 @@ def authorise_api_user(auth, return_type=None, id_match=None):
raise Exception('incorrect_login') raise Exception('incorrect_login')
token = auth[7:] # remove 'Bearer ' token = auth[7:] # remove 'Bearer '
try: decoded = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])
decoded = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"]) if decoded:
if decoded: user_id = decoded['sub']
user_id = decoded['sub'] issued_at = decoded['iat'] # use to check against blacklisted JWTs
issued_at = decoded['iat'] # use to check against blacklisted JWTs user = User.query.filter_by(id=user_id, ap_id=None, verified=True, banned=False, deleted=False).one()
user = User.query.filter_by(id=user_id, ap_id=None, verified=True, banned=False, deleted=False).scalar() if id_match and user.id != id_match:
if user: raise Exception('incorrect_login')
if id_match and user.id != id_match: if return_type and return_type == 'model':
raise Exception('incorrect_login') return user
if return_type and return_type == 'model': else:
return user return user.id
else:
return user.id
else:
raise Exception('incorrect_login')
except jwt.InvalidTokenError:
raise Exception('invalid_token')
@cache.memoize(timeout=86400) @cache.memoize(timeout=86400)