mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
375235435e
6 changed files with 219 additions and 16 deletions
|
@ -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, \
|
get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, \
|
||||||
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
|
||||||
|
@ -230,6 +230,18 @@ def put_alpha_comment():
|
||||||
return jsonify({"error": str(ex)}), 400
|
return jsonify({"error": str(ex)}), 400
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/api/alpha/comment/delete', methods=['POST'])
|
||||||
|
def post_alpha_comment_delete():
|
||||||
|
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_delete(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():
|
||||||
|
@ -310,7 +322,6 @@ def alpha_post():
|
||||||
|
|
||||||
# Reply - not yet implemented
|
# Reply - not yet implemented
|
||||||
@bp.route('/api/alpha/comment', methods=['GET'])
|
@bp.route('/api/alpha/comment', methods=['GET'])
|
||||||
@bp.route('/api/alpha/comment/delete', methods=['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'])
|
||||||
|
|
|
@ -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
|
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.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
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ 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
|
||||||
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
|
||||||
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
|
||||||
|
@ -11,9 +12,9 @@ from sqlalchemy import desc
|
||||||
@cache.memoize(timeout=3)
|
@cache.memoize(timeout=3)
|
||||||
def cached_reply_list(post_id, person_id, sort, max_depth, user_id):
|
def cached_reply_list(post_id, person_id, sort, max_depth, user_id):
|
||||||
if post_id:
|
if post_id:
|
||||||
replies = PostReply.query.filter(PostReply.deleted == False, PostReply.post_id == post_id, PostReply.depth <= max_depth)
|
replies = PostReply.query.filter(PostReply.post_id == post_id, PostReply.depth <= max_depth)
|
||||||
if person_id:
|
if person_id:
|
||||||
replies = PostReply.query.filter_by(deleted=False, user_id=person_id)
|
replies = PostReply.query.filter_by(user_id=person_id)
|
||||||
|
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
blocked_person_ids = blocked_users(user_id)
|
blocked_person_ids = blocked_users(user_id)
|
||||||
|
@ -163,3 +164,22 @@ def put_reply(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
|
||||||
|
|
||||||
|
|
||||||
|
def post_reply_delete(auth, data):
|
||||||
|
required(['comment_id', 'deleted'], data)
|
||||||
|
integer_expected(['comment_id'], data)
|
||||||
|
boolean_expected(['deleted'], data)
|
||||||
|
|
||||||
|
reply_id = data['comment_id']
|
||||||
|
deleted = data['deleted']
|
||||||
|
|
||||||
|
if deleted == True:
|
||||||
|
user_id, reply = delete_reply(reply_id, SRC_API, auth)
|
||||||
|
else:
|
||||||
|
user_id, reply = restore_reply(reply_id, SRC_API, auth)
|
||||||
|
|
||||||
|
reply_json = reply_view(reply=reply, variant=4, user_id=user_id)
|
||||||
|
return reply_json
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
|
@ -270,25 +270,28 @@ 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.get(reply)
|
||||||
if not reply or reply.deleted:
|
if not reply:
|
||||||
raise Exception('reply_not_found')
|
raise Exception('reply_not_found')
|
||||||
|
|
||||||
|
|
||||||
# Variant 1 - models/comment/comment.dart
|
# Variant 1 - models/comment/comment.dart
|
||||||
if variant == 1:
|
if variant == 1:
|
||||||
include = ['id', 'user_id', 'post_id', 'body', 'deleted']
|
include = ['id', 'user_id', 'post_id', 'body', 'deleted']
|
||||||
v1 = {column.name: getattr(reply, column.name) for column in reply.__table__.columns if column.name in include}
|
v1 = {column.name: getattr(reply, column.name) for column in reply.__table__.columns if column.name in include}
|
||||||
|
|
||||||
v1['path'] = calculate_path(reply)
|
|
||||||
if reply.edited_at:
|
|
||||||
v1['edited_at'] = reply.edited_at.isoformat() + 'Z'
|
|
||||||
|
|
||||||
v1.update({'published': reply.posted_at.isoformat() + 'Z',
|
v1.update({'published': reply.posted_at.isoformat() + 'Z',
|
||||||
'ap_id': reply.profile_id(),
|
'ap_id': reply.profile_id(),
|
||||||
'local': reply.is_local(),
|
'local': reply.is_local(),
|
||||||
'language_id': reply.language_id if reply.language_id else 0,
|
'language_id': reply.language_id if reply.language_id else 0,
|
||||||
'removed': reply.deleted,
|
'distinguished': False,
|
||||||
'distinguished': False})
|
'removed': False})
|
||||||
|
|
||||||
|
v1['path'] = calculate_path(reply)
|
||||||
|
if reply.edited_at:
|
||||||
|
v1['edited_at'] = reply.edited_at.isoformat() + 'Z'
|
||||||
|
if reply.deleted == True:
|
||||||
|
v1['body'] = ''
|
||||||
|
if reply.deleted_by and reply.user_id != reply.deleted_by:
|
||||||
|
v1['removed'] = True
|
||||||
|
|
||||||
return v1
|
return v1
|
||||||
|
|
||||||
|
|
|
@ -499,13 +499,18 @@ def unsubscribe(actor):
|
||||||
if '@' in actor: # this is a remote community, so activitypub is needed
|
if '@' in actor: # this is a remote community, so activitypub is needed
|
||||||
success = True
|
success = True
|
||||||
if not community.instance.gone_forever:
|
if not community.instance.gone_forever:
|
||||||
|
follow_id = f"https://{current_app.config['SERVER_NAME']}/activities/follow/{gibberish(15)}"
|
||||||
|
if community.instance.domain == 'a.gup.pe':
|
||||||
|
join_request = CommunityJoinRequest.query.filter_by(user_id=current_user.id, community_id=community.id).first()
|
||||||
|
if join_request:
|
||||||
|
follow_id = f"https://{current_app.config['SERVER_NAME']}/activities/follow/{join_request.id}"
|
||||||
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/" + gibberish(15)
|
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/" + gibberish(15)
|
||||||
follow = {
|
follow = {
|
||||||
"actor": current_user.public_url(),
|
"actor": current_user.public_url(),
|
||||||
"to": [community.public_url()],
|
"to": [community.public_url()],
|
||||||
"object": community.public_url(),
|
"object": community.public_url(),
|
||||||
"type": "Follow",
|
"type": "Follow",
|
||||||
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{gibberish(15)}"
|
"id": follow_id
|
||||||
}
|
}
|
||||||
undo = {
|
undo = {
|
||||||
'actor': current_user.public_url(),
|
'actor': current_user.public_url(),
|
||||||
|
|
|
@ -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, PostReply, PostReplyBookmark, User, utcnow
|
from app.models import NotificationSubscription, Post, PostReply, PostReplyBookmark, 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
|
||||||
|
|
||||||
|
@ -478,3 +478,167 @@ def edit_reply(input, reply, post, src, auth=None):
|
||||||
return user.id, reply
|
return user.id, reply
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# just for deletes by owner (mod deletes are classed as 'remove')
|
||||||
|
# just for API for now, as WEB version needs attention to ensure that replies can be 'undeleted'
|
||||||
|
def delete_reply(reply_id, src, auth):
|
||||||
|
if src == SRC_API:
|
||||||
|
reply = PostReply.query.filter_by(id=reply_id, deleted=False).one()
|
||||||
|
post = Post.query.filter_by(id=reply.post_id).one()
|
||||||
|
user = authorise_api_user(auth, return_type='model', id_match=reply.user_id)
|
||||||
|
else:
|
||||||
|
reply = PostReply.query.get_or_404(reply_id)
|
||||||
|
post = Post.query.get_or_404(reply.post_id)
|
||||||
|
user = current_user
|
||||||
|
|
||||||
|
reply.deleted = True
|
||||||
|
reply.deleted_by = user.id
|
||||||
|
# everything else (votes, body, reports, bookmarks, subscriptions, etc) only wants deleting when it's properly purged after 7 days
|
||||||
|
# reply_view will return '' in body if reply.deleted == True
|
||||||
|
|
||||||
|
if not reply.author.bot:
|
||||||
|
post.reply_count -= 1
|
||||||
|
reply.author.post_reply_count -= 1
|
||||||
|
db.session.commit()
|
||||||
|
if src == SRC_WEB:
|
||||||
|
flash(_('Comment deleted.'))
|
||||||
|
|
||||||
|
# federate delete
|
||||||
|
if not post.community.local_only:
|
||||||
|
delete_json = {
|
||||||
|
'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}",
|
||||||
|
'type': 'Delete',
|
||||||
|
'actor': user.public_url(),
|
||||||
|
'audience': post.community.public_url(),
|
||||||
|
'to': [
|
||||||
|
'https://www.w3.org/ns/activitystreams#Public'
|
||||||
|
],
|
||||||
|
'published': ap_datetime(utcnow()),
|
||||||
|
'cc': [
|
||||||
|
post.community.public_url(),
|
||||||
|
user.followers_url()
|
||||||
|
],
|
||||||
|
'object': reply.ap_id,
|
||||||
|
'@context': default_context()
|
||||||
|
}
|
||||||
|
|
||||||
|
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
|
||||||
|
success = post_request(post.community.ap_inbox_url, delete_json, user.private_key,
|
||||||
|
user.public_url() + '#main-key')
|
||||||
|
if src == SRC_WEB:
|
||||||
|
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
|
||||||
|
del delete_json['@context']
|
||||||
|
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.public_url() + '/followers'
|
||||||
|
],
|
||||||
|
'@context': default_context(),
|
||||||
|
'object': delete_json
|
||||||
|
}
|
||||||
|
|
||||||
|
for instance in post.community.following_instances():
|
||||||
|
if instance.inbox:
|
||||||
|
send_to_remote_instance(instance.id, post.community.id, announce)
|
||||||
|
|
||||||
|
if src == SRC_API:
|
||||||
|
return user.id, reply
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def restore_reply(reply_id, src, auth):
|
||||||
|
if src == SRC_API:
|
||||||
|
reply = PostReply.query.filter_by(id=reply_id, deleted=True).one()
|
||||||
|
post = Post.query.filter_by(id=reply.post_id).one()
|
||||||
|
user = authorise_api_user(auth, return_type='model', id_match=reply.user_id)
|
||||||
|
if reply.deleted_by and reply.user_id != reply.deleted_by:
|
||||||
|
raise Exception('incorrect_login')
|
||||||
|
else:
|
||||||
|
reply = PostReply.query.get_or_404(reply_id)
|
||||||
|
post = Post.query.get_or_404(reply.post_id)
|
||||||
|
user = current_user
|
||||||
|
|
||||||
|
reply.deleted = False
|
||||||
|
reply.deleted_by = None
|
||||||
|
|
||||||
|
if not reply.author.bot:
|
||||||
|
post.reply_count += 1
|
||||||
|
reply.author.post_reply_count += 1
|
||||||
|
db.session.commit()
|
||||||
|
if src == SRC_WEB:
|
||||||
|
flash(_('Comment restored.'))
|
||||||
|
|
||||||
|
# federate undelete
|
||||||
|
if not post.community.local_only:
|
||||||
|
delete_json = {
|
||||||
|
'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}",
|
||||||
|
'type': 'Delete',
|
||||||
|
'actor': user.public_url(),
|
||||||
|
'audience': post.community.public_url(),
|
||||||
|
'to': [
|
||||||
|
'https://www.w3.org/ns/activitystreams#Public'
|
||||||
|
],
|
||||||
|
'published': ap_datetime(utcnow()),
|
||||||
|
'cc': [
|
||||||
|
post.community.public_url(),
|
||||||
|
user.followers_url()
|
||||||
|
],
|
||||||
|
'object': reply.ap_id
|
||||||
|
}
|
||||||
|
undo_json = {
|
||||||
|
'id': f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}",
|
||||||
|
'type': 'Undo',
|
||||||
|
'actor': user.public_url(),
|
||||||
|
'audience': post.community.public_url(),
|
||||||
|
'to': [
|
||||||
|
'https://www.w3.org/ns/activitystreams#Public'
|
||||||
|
],
|
||||||
|
'cc': [
|
||||||
|
post.community.public_url(),
|
||||||
|
user.followers_url()
|
||||||
|
],
|
||||||
|
'object': delete_json,
|
||||||
|
'@context': default_context()
|
||||||
|
}
|
||||||
|
|
||||||
|
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
|
||||||
|
success = post_request(post.community.ap_inbox_url, undo_json, user.private_key,
|
||||||
|
user.public_url() + '#main-key')
|
||||||
|
if src == SRC_WEB:
|
||||||
|
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
|
||||||
|
del undo_json['@context']
|
||||||
|
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.public_url() + '/followers'
|
||||||
|
],
|
||||||
|
'@context': default_context(),
|
||||||
|
'object': undo_json
|
||||||
|
}
|
||||||
|
|
||||||
|
for instance in post.community.following_instances():
|
||||||
|
if instance.inbox:
|
||||||
|
send_to_remote_instance(instance.id, post.community.id, announce)
|
||||||
|
|
||||||
|
if src == SRC_API:
|
||||||
|
return user.id, reply
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
Loading…
Add table
Reference in a new issue