mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
API: process /community/follow (for joining and leaving communities)
This commit is contained in:
parent
ab6d66e7e2
commit
5f42de3893
5 changed files with 251 additions and 7 deletions
|
@ -2,7 +2,7 @@ from app.api.alpha import bp
|
|||
from app.api.alpha.utils import get_site, \
|
||||
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, \
|
||||
get_community_list, get_community, \
|
||||
get_community_list, get_community, post_community_follow, \
|
||||
get_user, post_user_block
|
||||
from app.shared.auth import log_user_in
|
||||
|
||||
|
@ -46,6 +46,18 @@ def get_alpha_community_list():
|
|||
return jsonify({"error": str(ex)}), 400
|
||||
|
||||
|
||||
@bp.route('/api/alpha/community/follow', methods=['POST'])
|
||||
def post_alpha_community_follow():
|
||||
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_community_follow(auth, data))
|
||||
except Exception as ex:
|
||||
return jsonify({"error": str(ex)}), 400
|
||||
|
||||
|
||||
# Post
|
||||
@bp.route('/api/alpha/post/list', methods=['GET'])
|
||||
def get_alpha_post_list():
|
||||
|
@ -214,7 +226,6 @@ def alpha_miscellaneous():
|
|||
@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/follow', methods=['POST'])
|
||||
@bp.route('/api/alpha/community/block', methods=['POST'])
|
||||
@bp.route('/api/alpha/community/delete', methods=['POST'])
|
||||
@bp.route('/api/alpha/community/remove', methods=['POST'])
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from app.api.alpha.utils.site import get_site
|
||||
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
|
||||
from app.api.alpha.utils.community import get_community, get_community_list
|
||||
from app.api.alpha.utils.community import get_community, get_community_list, post_community_follow
|
||||
from app.api.alpha.utils.user import get_user, post_user_block
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from app import cache
|
||||
from app.api.alpha.views import community_view
|
||||
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected
|
||||
from app.utils import authorise_api_user
|
||||
from app.models import Community, CommunityMember
|
||||
from app.shared.community import join_community, leave_community
|
||||
from app.utils import communities_banned_from
|
||||
|
||||
|
||||
|
@ -26,8 +28,8 @@ def get_community_list(auth, data):
|
|||
if auth:
|
||||
try:
|
||||
user_id = authorise_api_user(auth)
|
||||
except Exception as e:
|
||||
raise e
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
|
@ -58,8 +60,8 @@ def get_community(auth, data):
|
|||
if auth:
|
||||
try:
|
||||
user_id = authorise_api_user(auth)
|
||||
except Exception as e:
|
||||
raise e
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
|
@ -68,3 +70,36 @@ def get_community(auth, data):
|
|||
return community_json
|
||||
except:
|
||||
raise
|
||||
|
||||
|
||||
# would be in app/constants.py
|
||||
SRC_API = 3
|
||||
|
||||
def post_community_follow(auth, data):
|
||||
try:
|
||||
required(['community_id', 'follow'], data)
|
||||
integer_expected(['community_id'], data)
|
||||
boolean_expected(['follow'], data)
|
||||
except:
|
||||
raise
|
||||
|
||||
community_id = data['community_id']
|
||||
follow = data['follow']
|
||||
|
||||
if auth:
|
||||
try:
|
||||
user_id = authorise_api_user(auth)
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
try:
|
||||
if follow == True:
|
||||
user_id = join_community(community_id, SRC_API, auth)
|
||||
else:
|
||||
user_id = leave_community(community_id, SRC_API, auth)
|
||||
community_json = community_view(community=community_id, variant=4, stub=False, user_id=user_id)
|
||||
return community_json
|
||||
except:
|
||||
raise
|
||||
|
|
|
@ -226,6 +226,11 @@ def community_view(community: Community | int | str, variant, stub=False, user_i
|
|||
'discussion_languages': []}
|
||||
return v3
|
||||
|
||||
# Variant 4 - models/community/community_response.dart - /community/follow api endpoint
|
||||
if variant == 4:
|
||||
v4 = {'community_view': community_view(community=community, variant=2, stub=False, user_id=user_id),
|
||||
'discussion_languages': []}
|
||||
return v4
|
||||
|
||||
|
||||
# would be better to incrementally add to a post_reply.path field
|
||||
|
|
193
app/shared/community.py
Normal file
193
app/shared/community.py
Normal file
|
@ -0,0 +1,193 @@
|
|||
from app import db, cache
|
||||
from app.activitypub.signature import post_request
|
||||
from app.constants import *
|
||||
from app.models import Community, CommunityBan, CommunityJoinRequest, CommunityMember
|
||||
from app.utils import authorise_api_user, community_membership, joined_communities, gibberish
|
||||
|
||||
from flask import abort, current_app, flash
|
||||
from flask_babel import _
|
||||
from flask_login import current_user
|
||||
|
||||
# would be in app/constants.py
|
||||
SRC_WEB = 1
|
||||
SRC_PUB = 2
|
||||
SRC_API = 3
|
||||
|
||||
# function can be shared between WEB and API (only API calls it for now)
|
||||
# call from admin.federation not tested
|
||||
def join_community(community_id: int, src, auth=None, user_id=None, main_user_name=True):
|
||||
if src == SRC_API:
|
||||
community = Community.query.get(community_id)
|
||||
if not community:
|
||||
raise Exception('community_not_found')
|
||||
try:
|
||||
user = authorise_api_user(auth, return_type='model')
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
community = Community.query.get_or_404(community_id)
|
||||
if not user_id:
|
||||
user = current_user
|
||||
else:
|
||||
user = User.query.get(user_id)
|
||||
|
||||
pre_load_message = {}
|
||||
if community_membership(user, community) != SUBSCRIPTION_MEMBER and community_membership(user, community) != SUBSCRIPTION_PENDING:
|
||||
banned = CommunityBan.query.filter_by(user_id=user.id, community_id=community.id).first()
|
||||
if banned:
|
||||
if src == SRC_API:
|
||||
raise Exception('banned_from_community')
|
||||
else:
|
||||
if main_user_name:
|
||||
flash(_('You cannot join this community'))
|
||||
return
|
||||
else:
|
||||
pre_load_message['user_banned'] = True
|
||||
return pre_load_message
|
||||
else:
|
||||
if src == SRC_API:
|
||||
return user.id
|
||||
else:
|
||||
if not main_user_name:
|
||||
pre_load_message['status'] = 'already subscribed, or subsciption pending'
|
||||
return pre_load_message
|
||||
|
||||
success = True
|
||||
remote = not community.is_local()
|
||||
if remote:
|
||||
# send ActivityPub message to remote community, asking to follow. Accept message will be sent to our shared inbox
|
||||
join_request = CommunityJoinRequest(user_id=user.id, community_id=community.id)
|
||||
db.session.add(join_request)
|
||||
db.session.commit()
|
||||
if community.instance.online():
|
||||
follow = {
|
||||
"actor": user.public_url(main_user_name=main_user_name),
|
||||
"to": [community.public_url()],
|
||||
"object": community.public_url(),
|
||||
"type": "Follow",
|
||||
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{join_request.id}"
|
||||
}
|
||||
success = post_request(community.ap_inbox_url, follow, user.private_key,
|
||||
user.public_url(main_user_name=main_user_name) + '#main-key', timeout=10)
|
||||
if success is False or isinstance(success, str):
|
||||
if 'is not in allowlist' in success:
|
||||
if src == SRC_API:
|
||||
raise Exception('not_in_remote_instance_allowlist')
|
||||
else:
|
||||
msg_to_user = f'{community.instance.domain} does not allow us to join their communities.'
|
||||
if main_user_name:
|
||||
flash(_(msg_to_user), 'error')
|
||||
return
|
||||
else:
|
||||
pre_load_message['status'] = msg_to_user
|
||||
return pre_load_message
|
||||
else:
|
||||
if src != SRC_API:
|
||||
msg_to_user = "There was a problem while trying to communicate with remote server. If other people have already joined this community it won't matter."
|
||||
if main_user_name:
|
||||
flash(_(msg_to_user), 'error')
|
||||
return
|
||||
else:
|
||||
pre_load_message['status'] = msg_to_user
|
||||
return pre_load_message
|
||||
|
||||
# for local communities, joining is instant
|
||||
member = CommunityMember(user_id=user.id, community_id=community.id)
|
||||
db.session.add(member)
|
||||
db.session.commit()
|
||||
if success is True:
|
||||
cache.delete_memoized(community_membership, user, community)
|
||||
cache.delete_memoized(joined_communities, user.id)
|
||||
if src == SRC_API:
|
||||
return user.id
|
||||
else:
|
||||
if main_user_name:
|
||||
flash('You joined ' + community.title)
|
||||
else:
|
||||
pre_load_message['status'] = 'joined'
|
||||
|
||||
if not main_user_name:
|
||||
return pre_load_message
|
||||
|
||||
# for SRC_WEB, calling function should handle if the community isn't found
|
||||
|
||||
|
||||
# function can be shared between WEB and API (only API calls it for now)
|
||||
def leave_community(community_id: int, src, auth=None):
|
||||
if src == SRC_API:
|
||||
community = Community.query.get(community_id)
|
||||
if not community:
|
||||
raise Exception('community_not_found')
|
||||
try:
|
||||
user = authorise_api_user(auth, return_type='model')
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
community = Community.query.get_or_404(community_id)
|
||||
user = current_user
|
||||
|
||||
subscription = community_membership(user, community)
|
||||
if subscription:
|
||||
if subscription != SUBSCRIPTION_OWNER:
|
||||
proceed = True
|
||||
# Undo the Follow
|
||||
if not community.is_local():
|
||||
success = True
|
||||
if not community.instance.gone_forever:
|
||||
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/" + gibberish(15)
|
||||
follow = {
|
||||
"actor": user.public_url(),
|
||||
"to": [community.public_url()],
|
||||
"object": community.public_url(),
|
||||
"type": "Follow",
|
||||
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{gibberish(15)}"
|
||||
}
|
||||
undo = {
|
||||
'actor': user.public_url(),
|
||||
'to': [community.public_url()],
|
||||
'type': 'Undo',
|
||||
'id': undo_id,
|
||||
'object': follow
|
||||
}
|
||||
success = post_request(community.ap_inbox_url, undo, user.private_key,
|
||||
user.public_url() + '#main-key', timeout=10)
|
||||
if success is False or isinstance(success, str):
|
||||
if src != SRC_API:
|
||||
flash('There was a problem while trying to unsubscribe', 'error')
|
||||
return
|
||||
|
||||
if proceed:
|
||||
db.session.query(CommunityMember).filter_by(user_id=user.id, community_id=community.id).delete()
|
||||
db.session.query(CommunityJoinRequest).filter_by(user_id=user.id, community_id=community.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
if src != SRC_API:
|
||||
flash('You have left ' + community.title)
|
||||
|
||||
cache.delete_memoized(community_membership, user, community)
|
||||
cache.delete_memoized(joined_communities, user.id)
|
||||
else:
|
||||
# todo: community deletion
|
||||
if src == SRC_API:
|
||||
raise Exception('need_to_make_someone_else_owner')
|
||||
else:
|
||||
flash('You need to make someone else the owner before unsubscribing.', 'warning')
|
||||
return
|
||||
|
||||
if src == SRC_API:
|
||||
return user.id
|
||||
else:
|
||||
# let calling function handle redirect
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in a new issue