API: post remove and restore by mod

This commit is contained in:
freamon 2025-01-20 05:01:31 +00:00
parent ac53c2635b
commit 113a64a95d
5 changed files with 188 additions and 80 deletions

View file

@ -1,8 +1,10 @@
from app.api.alpha import bp
from app.api.alpha.utils import get_site, post_site_block, \
get_search, \
get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, post_post, put_post, post_post_delete, post_post_report, post_post_lock, post_post_feature, \
get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report, \
get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, post_post, \
put_post, post_post_delete, post_post_report, post_post_lock, post_post_feature, post_post_remove, \
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_user, post_user_block
from app.shared.auth import log_user_in
@ -231,6 +233,18 @@ def post_alpha_post_feature():
return jsonify({"error": str(ex)}), 400
@bp.route('/api/alpha/post/remove', methods=['POST'])
def post_alpha_post_remove():
if not enable_api():
return jsonify({'error': 'alpha api is not enabled'})
#try:
auth = request.headers.get('Authorization')
data = request.get_json(force=True) or {}
return jsonify(post_post_remove(auth, data))
#except Exception as ex:
# return jsonify({"error": str(ex)}), 400
# Reply
@bp.route('/api/alpha/comment/list', methods=['GET'])
def get_alpha_comment_list():
@ -368,108 +382,105 @@ def post_alpha_user_block():
# Not yet implemented. Copied from lemmy's V3 api, so some aren't needed, and some need changing
# Site - not yet implemented
@bp.route('/api/alpha/site', methods=['POST'])
@bp.route('/api/alpha/site', methods=['PUT'])
@bp.route('/api/alpha/site', methods=['POST']) # Create New Site. No plans to implement
@bp.route('/api/alpha/site', methods=['PUT']) # Edit Site. Not available in app
def alpha_site():
return jsonify({"error": "not_yet_implemented"}), 400
# Miscellaneous - not yet implemented
@bp.route('/api/alpha/modlog', methods=['GET'])
@bp.route('/api/alpha/resolve_object', methods=['GET'])
@bp.route('/api/alpha/federated_instances', methods=['GET'])
@bp.route('/api/alpha/modlog', methods=['GET']) # Get Modlog. Not usually public
@bp.route('/api/alpha/resolve_object', methods=['GET']) # Stage 1: Needed for search
@bp.route('/api/alpha/federated_instances', methods=['GET']) # No plans to implement - only V3 version needed
def alpha_miscellaneous():
return jsonify({"error": "not_yet_implemented"}), 400
# Community - not yet implemented
@bp.route('/api/alpha/community', methods=['POST'])
@bp.route('/api/alpha/community', methods=['PUT'])
@bp.route('/api/alpha/community/hide', methods=['PUT'])
@bp.route('/api/alpha/community/delete', methods=['POST'])
@bp.route('/api/alpha/community/remove', methods=['POST'])
@bp.route('/api/alpha/community/transfer', methods=['POST'])
@bp.route('/api/alpha/community/ban_user', methods=['POST'])
@bp.route('/api/alpha/community/mod', methods=['POST'])
@bp.route('/api/alpha/community', methods=['POST']) # (none
@bp.route('/api/alpha/community', methods=['PUT']) # of
@bp.route('/api/alpha/community/hide', methods=['PUT']) # these
@bp.route('/api/alpha/community/delete', methods=['POST']) # are
@bp.route('/api/alpha/community/remove', methods=['POST']) # available
@bp.route('/api/alpha/community/transfer', methods=['POST']) # in
@bp.route('/api/alpha/community/ban_user', methods=['POST']) # the
@bp.route('/api/alpha/community/mod', methods=['POST']) # app)
def alpha_community():
return jsonify({"error": "not_yet_implemented"}), 400
# Post - not yet implemented
@bp.route('/api/alpha/post/remove', methods=['POST'])
@bp.route('/api/alpha/post/feature', methods=['POST'])
@bp.route('/api/alpha/post/report', methods=['POST'])
@bp.route('/api/alpha/post/report/resolve', methods=['PUT'])
@bp.route('/api/alpha/post/report/list', methods=['GET'])
@bp.route('/api/alpha/post/site_metadata', methods=['GET'])
@bp.route('/api/alpha/post/report/resolve', methods=['PUT']) # Stage 2
@bp.route('/api/alpha/post/report/list', methods=['GET']) # Stage 2
@bp.route('/api/alpha/post/site_metadata', methods=['GET']) # Not available in app
def alpha_post():
return jsonify({"error": "not_yet_implemented"}), 400
# Reply - not yet implemented
@bp.route('/api/alpha/comment', methods=['GET'])
@bp.route('/api/alpha/comment/remove', 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/report/resolve', methods=['PUT'])
@bp.route('/api/alpha/comment/report/list', methods=['GET'])
@bp.route('/api/alpha/comment', methods=['GET']) # Stage 1 if needed for search
@bp.route('/api/alpha/comment/remove', methods=['POST']) # Stage 1
@bp.route('/api/alpha/comment/mark_as_read', methods=['POST']) # No DB support
@bp.route('/api/alpha/comment/distinguish', methods=['POST']) # Not really used
@bp.route('/api/alpha/comment/report/resolve', methods=['PUT']) # Stage 2
@bp.route('/api/alpha/comment/report/list', methods=['GET']) # Stage 2
def alpha_reply():
return jsonify({"error": "not_yet_implemented"}), 400
# Chat - not yet implemented
@bp.route('/api/alpha/private_message/list', methods=['GET'])
@bp.route('/api/alpha/private_message', methods=['PUT'])
@bp.route('/api/alpha/private_message', methods=['POST'])
@bp.route('/api/alpha/private_message/delete', methods=['POST'])
@bp.route('/api/alpha/private_message/mark_as_read', methods=['POST'])
@bp.route('/api/alpha/private_message/report', methods=['POST'])
@bp.route('/api/alpha/private_message/report/resolve', methods=['PUT'])
@bp.route('/api/alpha/private_message/report/list', methods=['GET'])
@bp.route('/api/alpha/private_message/list', methods=['GET']) # Stage 1
@bp.route('/api/alpha/private_message', methods=['PUT']) # Stage 1
@bp.route('/api/alpha/private_message', methods=['POST']) # Stage 1
@bp.route('/api/alpha/private_message/delete', methods=['POST']) # Stage 1
@bp.route('/api/alpha/private_message/mark_as_read', methods=['POST']) # Stage 1
@bp.route('/api/alpha/private_message/report', methods=['POST']) # Stage 1
@bp.route('/api/alpha/private_message/report/resolve', methods=['PUT']) # Stage 2
@bp.route('/api/alpha/private_message/report/list', methods=['GET']) # Stage 2
def alpha_chat():
return jsonify({"error": "not_yet_implemented"}), 400
# User - not yet implemented
@bp.route('/api/alpha/user/register', methods=['POST'])
@bp.route('/api/alpha/user/get_captcha', methods=['GET'])
@bp.route('/api/alpha/user/mention', methods=['GET'])
@bp.route('/api/alpha/user/mention/mark_as_read', methods=['POST'])
@bp.route('/api/alpha/user/replies', methods=['GET'])
@bp.route('/api/alpha/user/ban', methods=['POST'])
@bp.route('/api/alpha/user/banned', methods=['GET'])
@bp.route('/api/alpha/user/delete_account', methods=['POST'])
@bp.route('/api/alpha/user/password_reset', methods=['POST'])
@bp.route('/api/alpha/user/password_change', methods=['POST'])
@bp.route('/api/alpha/user/mark_all_as_read', methods=['POST'])
@bp.route('/api/alpha/user/save_user_settings', methods=['PUT'])
@bp.route('/api/alpha/user/change_password', methods=['PUT'])
@bp.route('/api/alpha/user/repost_count', methods=['GET'])
@bp.route('/api/alpha/user/unread_count', methods=['GET'])
@bp.route('/api/alpha/user/verify_email', methods=['POST'])
@bp.route('/api/alpha/user/leave_admin', methods=['POST'])
@bp.route('/api/alpha/user/totp/generate', methods=['POST'])
@bp.route('/api/alpha/user/totp/update', methods=['POST'])
@bp.route('/api/alpha/user/export_settings', methods=['GET'])
@bp.route('/api/alpha/user/import_settings', methods=['POST'])
@bp.route('/api/alpha/user/list_logins', methods=['GET'])
@bp.route('/api/alpha/user/validate_auth', methods=['GET'])
@bp.route('/api/alpha/user/logout', methods=['POST'])
@bp.route('/api/alpha/user/register', methods=['POST']) # Not available in app
@bp.route('/api/alpha/user/get_captcha', methods=['GET']) # Not available in app
@bp.route('/api/alpha/user/mention', methods=['GET']) # No DB support
@bp.route('/api/alpha/user/mention/mark_as_read', methods=['POST']) # No DB support
@bp.route('/api/alpha/user/replies', methods=['GET']) # Stage 1
@bp.route('/api/alpha/user/ban', methods=['POST']) # Admin function. No plans to implement
@bp.route('/api/alpha/user/banned', methods=['GET']) # Admin function. No plans to implement
@bp.route('/api/alpha/user/delete_account', methods=['POST']) # Not available in app
@bp.route('/api/alpha/user/password_reset', methods=['POST']) # Not available in app
@bp.route('/api/alpha/user/password_change', methods=['POST']) # Not available in app
@bp.route('/api/alpha/user/mark_all_as_read', methods=['POST']) # Stage 1
@bp.route('/api/alpha/user/save_user_settings', methods=['PUT']) # Not available in app
@bp.route('/api/alpha/user/change_password', methods=['PUT']) # Not available in app
@bp.route('/api/alpha/user/report_count', methods=['GET']) # Stage 2
@bp.route('/api/alpha/user/unread_count', methods=['GET']) # Stage 1
@bp.route('/api/alpha/user/verify_email', methods=['POST']) # Admin function. No plans to implement
@bp.route('/api/alpha/user/leave_admin', methods=['POST']) # Admin function. No plans to implement
@bp.route('/api/alpha/user/totp/generate', methods=['POST']) # Not available in app
@bp.route('/api/alpha/user/totp/update', methods=['POST']) # Not available in app
@bp.route('/api/alpha/user/export_settings', methods=['GET']) # Not available in app
@bp.route('/api/alpha/user/import_settings', methods=['POST']) # Not available in app
@bp.route('/api/alpha/user/list_logins', methods=['GET']) # Not available in app
@bp.route('/api/alpha/user/validate_auth', methods=['GET']) # Not available in app
@bp.route('/api/alpha/user/logout', methods=['POST']) # Stage 1
def alpha_user():
return jsonify({"error": "not_yet_implemented"}), 400
# Admin - not yet implemented
@bp.route('/api/alpha/admin/add', methods=['POST'])
@bp.route('/api/alpha/admin/registration_application/count', methods=['GET'])
@bp.route('/api/alpha/admin/registration_application/list', methods=['GET'])
@bp.route('/api/alpha/admin/registration_application/approve', methods=['PUT'])
@bp.route('/api/alpha/admin/purge/person', methods=['POST'])
@bp.route('/api/alpha/admin/purge/community', methods=['POST'])
@bp.route('/api/alpha/admin/purge/post', methods=['POST'])
@bp.route('/api/alpha/admin/purge/comment', methods=['POST'])
@bp.route('/api/alpha/post/like/list', methods=['GET'])
@bp.route('/api/alpha/comment/like/list', methods=['GET'])
@bp.route('/api/alpha/admin/registration_application/count', methods=['GET']) # (no
@bp.route('/api/alpha/admin/registration_application/list', methods=['GET']) # plans
@bp.route('/api/alpha/admin/registration_application/approve', methods=['PUT']) # to
@bp.route('/api/alpha/admin/purge/person', methods=['POST']) # implement
@bp.route('/api/alpha/admin/purge/community', methods=['POST']) # any
@bp.route('/api/alpha/admin/purge/post', methods=['POST']) # endpoints
@bp.route('/api/alpha/admin/purge/comment', methods=['POST']) # for
@bp.route('/api/alpha/post/like/list', methods=['GET']) # admin
@bp.route('/api/alpha/comment/like/list', methods=['GET']) # use)
def alpha_admin():
return jsonify({"error": "not_yet_implemented"}), 400
# CustomEmoji - not yet implemented
@bp.route('/api/alpha/custom_emoji', methods=['PUT'])
@bp.route('/api/alpha/custom_emoji', methods=['POST'])
@bp.route('/api/alpha/custom_emoji/delete', methods=['POST'])
@bp.route('/api/alpha/custom_emoji', methods=['PUT']) # (doesn't
@bp.route('/api/alpha/custom_emoji', methods=['POST']) # seem
@bp.route('/api/alpha/custom_emoji/delete', methods=['POST']) # important)
def alpha_emoji():
return jsonify({"error": "not_yet_implemented"}), 400

View file

@ -1,6 +1,6 @@
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.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, post_post, put_post, post_post_delete, post_post_report, post_post_lock, post_post_feature
from app.api.alpha.utils.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, post_post, put_post, post_post_delete, post_post_report, post_post_lock, post_post_feature, post_post_remove
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.user import get_user, post_user_block

View file

@ -3,7 +3,8 @@ from app.api.alpha.views import post_view, post_report_view
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected, string_expected
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_VIDEO
from app.models import Post, Community, CommunityMember, utcnow
from app.shared.post import vote_for_post, bookmark_the_post, remove_the_bookmark_from_post, toggle_post_notification, make_post, edit_post, delete_post, restore_post, report_post, lock_post, sticky_post
from app.shared.post import vote_for_post, bookmark_the_post, remove_the_bookmark_from_post, toggle_post_notification, make_post, edit_post, \
delete_post, restore_post, report_post, lock_post, sticky_post, mod_remove_post, mod_restore_post
from app.utils import authorise_api_user, blocked_users, blocked_communities, blocked_instances, community_ids_from_instances, is_image_url, is_video_url
from datetime import timedelta
@ -267,3 +268,23 @@ def post_post_feature(auth, data):
post_json = post_view(post=post, variant=4, user_id=user_id)
return post_json
def post_post_remove(auth, data):
required(['post_id', 'removed'], data)
integer_expected(['post_id'], data)
boolean_expected(['removed'], data)
string_expected(['reason'], data)
post_id = data['post_id']
removed = data['removed']
if removed == True:
reason = data['reason'] if 'reason' in data else 'Removed by mod'
user_id, post = mod_remove_post(post_id, reason, SRC_API, auth)
else:
reason = data['reason'] if 'reason' in data else 'Restored by mod'
user_id, post = mod_restore_post(post_id, reason, SRC_API, auth)
post_json = post_view(post=post, variant=4, user_id=user_id)
return post_json

View file

@ -572,9 +572,15 @@ def sticky_post(post_id, featured, src, auth=None):
if post.community.is_moderator(user) or post.community.is_instance_admin(user):
post.sticky = featured
if featured:
modlog_type = 'featured_post'
else:
modlog_type = 'unfeatured_post'
if not community.ap_featured_url:
community.ap_featured_url = community.ap_profile_id + '/featured'
db.session.commit()
add_to_modlog_activitypub(modlog_type, user, community_id=post.community_id,
link_text=shorten_string(post.title), link=f'post/{post.id}', reason='')
if featured:
task_selector('sticky_post', user_id=user.id, post_id=post_id)
@ -584,4 +590,68 @@ def sticky_post(post_id, featured, src, auth=None):
return user.id, post
# mod deletes
def mod_remove_post(post_id, reason, src, auth):
if src == SRC_API:
user = authorise_api_user(auth, return_type='model')
else:
user = current_user
post = Post.query.filter_by(id=post_id, user_id=user.id, deleted=False).one()
if not post.community.is_moderator(user) and not post.community.is_instance_admin(user):
raise Exception('Does not have permission')
if post.url:
post.calculate_cross_posts(delete_only=True)
post.deleted = True
post.deleted_by = user.id
post.author.post_count -= 1
post.community.post_count -= 1
db.session.commit()
if src == SRC_WEB:
flash(_('Post deleted.'))
add_to_modlog_activitypub('delete_post', user, community_id=post.community_id,
link_text=shorten_string(post.title), link=f'post/{post.id}', reason=reason)
task_selector('delete_post', user_id=user.id, post_id=post.id, reason=reason)
if src == SRC_API:
return user.id, post
else:
return
def mod_restore_post(post_id, reason, src, auth):
if src == SRC_API:
user = authorise_api_user(auth, return_type='model')
else:
user = current_user
post = Post.query.filter_by(id=post_id, user_id=user.id, deleted=True).one()
if not post.community.is_moderator(user) and not post.community.is_instance_admin(user):
raise Exception('Does not have permission')
if post.url:
post.calculate_cross_posts()
post.deleted = False
post.deleted_by = None
post.author.post_count -= 1
post.community.post_count -= 1
db.session.commit()
if src == SRC_WEB:
flash(_('Post restored.'))
add_to_modlog_activitypub('restore_post', user, community_id=post.community_id,
link_text=shorten_string(post.title), link=f'post/{post.id}', reason=reason)
task_selector('restore_post', user_id=user.id, post_id=post.id, reason=reason)
if src == SRC_API:
return user.id, post
else:
return

View file

@ -13,6 +13,7 @@ Delete:
'type':
'actor':
'object':
'summary': (if deleted by mod / admin)
'@context':
'audience':
'to': []
@ -23,30 +24,30 @@ For Announce, remove @context from inner object, and use same fields except audi
@celery.task
def delete_reply(send_async, user_id, reply_id):
def delete_reply(send_async, user_id, reply_id, reason=None):
reply = PostReply.query.filter_by(id=reply_id).one()
delete_object(user_id, reply)
@celery.task
def restore_reply(send_async, user_id, reply_id):
def restore_reply(send_async, user_id, reply_id, reason=None):
reply = PostReply.query.filter_by(id=reply_id).one()
delete_object(user_id, reply, is_restore=True)
@celery.task
def delete_post(send_async, user_id, post_id):
def delete_post(send_async, user_id, post_id, reason=None):
post = Post.query.filter_by(id=post_id).one()
delete_object(user_id, post, is_post=True)
delete_object(user_id, post, is_post=True, reason=reason)
@celery.task
def restore_post(send_async, user_id, post_id):
def restore_post(send_async, user_id, post_id, reason=None):
post = Post.query.filter_by(id=post_id).one()
delete_object(user_id, post, is_post=True, is_restore=True)
delete_object(user_id, post, is_post=True, is_restore=True, reason=reason)
def delete_object(user_id, object, is_post=False, is_restore=False):
def delete_object(user_id, object, is_post=False, is_restore=False, reason=None):
user = User.query.filter_by(id=user_id).one()
community = object.community
@ -81,6 +82,8 @@ def delete_object(user_id, object, is_post=False, is_restore=False):
'to': to,
'cc': cc
}
if reason:
delete['summary'] = reason
if is_restore:
del delete['@context']
@ -127,6 +130,9 @@ def delete_object(user_id, object, is_post=False, is_restore=False):
post_request(community.ap_inbox_url, payload, user.private_key, user.public_url() + '#main-key')
domains_sent_to.append(community.instance.domain)
if reason:
return
if is_post and followers:
payload = undo if is_restore else delete
for follower in followers: