API: support /user/block endpoint

This commit is contained in:
freamon 2024-10-05 20:55:04 +00:00
parent eff0edf817
commit 7c8dfe6bd3
8 changed files with 181 additions and 30 deletions

View file

@ -3,7 +3,7 @@ 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_user
get_user, post_user_block
from app.shared.auth import log_user_in
from flask import current_app, jsonify, request
@ -170,7 +170,7 @@ def get_alpha_user():
@bp.route('/api/alpha/user/login', methods=['POST'])
def post_alpha_login():
def post_alpha_user_login():
if not current_app.debug:
return jsonify({'error': 'alpha api routes only available in debug mode'})
try:
@ -181,6 +181,18 @@ def post_alpha_login():
return jsonify({"error": str(ex)}), 400
@bp.route('/api/alpha/user/block', methods=['POST'])
def post_alpha_user_block():
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_user_block(auth, data))
except Exception as ex:
return jsonify({"error": str(ex)}), 400
# Not yet implemented. Copied from lemmy's V3 api, so some aren't needed, and some need changing
# Site - not yet implemented
@ -219,7 +231,6 @@ def alpha_community():
@bp.route('/api/alpha/post/remove', methods=['POST'])
@bp.route('/api/alpha/post/lock', methods=['POST'])
@bp.route('/api/alpha/post/feature', methods=['POST'])
@bp.route('/api/alpha/post/save', methods=['PUT'])
@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'])
@ -261,7 +272,6 @@ def alpha_chat():
@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/block', methods=['POST'])
@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'])

View file

@ -2,6 +2,6 @@ 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.user import get_user
from app.api.alpha.utils.user import get_user, post_user_block

View file

@ -3,7 +3,7 @@ from app.api.alpha.views import post_view
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected
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
from app.utils import authorise_api_user
from app.utils import authorise_api_user, blocked_users
from datetime import timedelta
from sqlalchemy import desc
@ -28,6 +28,11 @@ def cached_post_list(type, sort, user_id, community_id, community_name, person_i
else:
posts = Post.query.filter_by(deleted=False)
if user_id is not None:
blocked_person_ids = blocked_users(user_id)
if blocked_person_ids:
posts = posts.filter(Post.user_id.not_in(blocked_person_ids))
if sort == "Hot":
posts = posts.order_by(desc(Post.ranking)).order_by(desc(Post.posted_at))
elif sort == "TopDay":
@ -49,8 +54,8 @@ def get_post_list(auth, data, user_id=None):
if auth:
try:
user_id = authorise_api_user(auth)
except Exception as e:
raise e
except:
raise
# user_id: the logged in user
# person_id: the author of the posts being requested

View file

@ -1,20 +1,25 @@
from app import cache
from app.utils import authorise_api_user
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected
from app.api.alpha.views import reply_view
from app.models import PostReply
from app.shared.reply import vote_for_reply, bookmark_the_post_reply, remove_the_bookmark_from_post_reply, toggle_post_reply_notification
from app.utils import authorise_api_user, blocked_users
from sqlalchemy import desc
# person_id param: the author of the reply; user_id param: the current logged-in user
@cache.memoize(timeout=3)
def cached_reply_list(post_id, person_id, sort, max_depth):
def cached_reply_list(post_id, person_id, sort, max_depth, user_id):
if post_id:
replies = PostReply.query.filter(PostReply.deleted == False, PostReply.post_id == post_id, PostReply.depth <= max_depth)
if person_id:
replies = PostReply.query.filter_by(deleted=False, user_id=person_id)
if user_id is not None:
blocked_person_ids = blocked_users(user_id)
if blocked_person_ids:
replies = replies.filter(PostReply.user_id.not_in(blocked_person_ids))
if sort == "Hot":
replies = replies.order_by(desc(PostReply.ranking)).order_by(desc(PostReply.posted_at))
elif sort == "Top":
@ -36,13 +41,12 @@ def get_reply_list(auth, data, user_id=None):
if data and not post_id and not person_id:
raise Exception('missing_parameters')
else:
replies = cached_reply_list(post_id, person_id, sort, max_depth)
if auth:
try:
user_id = authorise_api_user(auth)
except Exception as e:
raise e
except:
raise
replies = cached_reply_list(post_id, person_id, sort, max_depth, user_id)
# user_id: the logged in user
# person_id: the author of the posts being requested

View file

@ -1,4 +1,5 @@
from app import db
from app.api.alpha.views import user_view
from app.utils import authorise_api_user
from app.models import Language
@ -71,7 +72,7 @@ def get_site(auth):
#"follows": [],
"community_blocks": [], # TODO
"instance_blocks": [], # TODO
"person_blocks": [], # TODO
"person_blocks": [],
"discussion_languages": [] # TODO
}
"""
@ -83,7 +84,9 @@ def get_site(auth):
for cm in cms:
my_user['follows'].append({'community': Community.api_json(variant=1, id=cm.community_id, stub=True), 'follower': User.api_json(variant=1, id=user_id, stub=True)})
"""
blocked_ids = db.session.execute(text('SELECT blocked_id FROM "user_block" WHERE blocker_id = :blocker_id'), {"blocker_id": user.id}).scalars()
for blocked_id in blocked_ids:
my_user['person_blocks'].append({'person': user_view(user, variant=1, stub=True), 'target': user_view(blocked_id, variant=1, stub=True)})
data = {
"version": "1.0.0",
"site": site

View file

@ -2,6 +2,8 @@ from app.api.alpha.views import user_view
from app.utils import authorise_api_user
from app.api.alpha.utils.post import get_post_list
from app.api.alpha.utils.reply import get_reply_list
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected
from app.shared.user import block_another_user, unblock_another_user
def get_user(auth, data):
@ -39,4 +41,26 @@ def get_user(auth, data):
raise
# would be in app/constants.py
SRC_API = 3
def post_user_block(auth, data):
try:
required(['person_id', 'block'], data)
integer_expected(['post_id'], data)
boolean_expected(['block'], data)
except:
raise
person_id = data['person_id']
block = data['block']
try:
if block == True:
user_id = block_another_user(person_id, SRC_API, auth)
else:
user_id = unblock_another_user(person_id, SRC_API, auth)
user_json = user_view(user=person_id, variant=4, user_id=user_id)
return user_json
except:
raise

View file

@ -50,8 +50,11 @@ def post_view(post: Post | int, variant, stub=False, user_id=None, my_vote=0):
# counts - models/post/post_aggregates.dart
counts = {'post_id': post.id, 'comments': post.reply_count, 'score': post.score, 'upvotes': post.up_votes, 'downvotes': post.down_votes,
'published': post.posted_at.isoformat() + 'Z', 'newest_comment_time': post.last_active.isoformat() + 'Z'}
if user_id:
bookmarked = db.session.execute(text('SELECT user_id FROM "post_bookmark" WHERE post_id = :post_id and user_id = :user_id'), {'post_id': post.id, 'user_id': user_id}).scalar()
post_sub = db.session.execute(text('SELECT user_id FROM "notification_subscription" WHERE type = :type and entity_id = :entity_id and user_id = :user_id'), {'type': NOTIF_POST, 'entity_id': post.id, 'user_id': user_id}).scalar()
else:
bookmarked = post_sub = False
if not stub:
banned = db.session.execute(text('SELECT user_id FROM "community_ban" WHERE user_id = :user_id and community_id = :community_id'), {'user_id': post.user_id, 'community_id': post.community_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': post.user_id, 'community_id': post.community_id}).scalar()
@ -73,7 +76,7 @@ def post_view(post: Post | int, variant, stub=False, user_id=None, my_vote=0):
creator_is_moderator = True if moderator else False
creator_is_admin = True if admin else False
v2 = {'post': post_view(post=post, variant=1, stub=stub), 'counts': counts, 'banned_from_community': False, 'subscribed': 'NotSubscribed',
'saved': saved, 'read': False, 'hidden': False, 'creator_blocked': False, 'unread_comments': post.reply_count, 'my_vote': my_vote, 'activity_alert': activity_alert,
'saved': saved, 'read': False, 'hidden': False, 'unread_comments': post.reply_count, 'my_vote': my_vote, 'activity_alert': activity_alert,
'creator_banned_from_community': creator_banned_from_community, 'creator_is_moderator': creator_is_moderator, 'creator_is_admin': creator_is_admin}
try:
@ -127,7 +130,8 @@ def cached_user_view_variant_1(user: User, stub=False):
return v1
def user_view(user: User | int, variant, stub=False):
# '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):
if isinstance(user, int):
user = User.query.get(user)
if not user:
@ -144,6 +148,7 @@ def user_view(user: User | int, variant, stub=False):
return v2
# Variant 3 - models/user/get_person_details.dart - /user?person_id api endpoint
if variant == 3:
modlist = cached_modlist_for_user(user)
v3 = {'person_view': user_view(user=user, variant=2),
@ -152,6 +157,14 @@ def user_view(user: User | int, variant, stub=False):
'comments': []}
return v3
# Variant 4 - models/user/block_person_response.dart - /user/block api endpoint
if variant == 4:
block = db.session.execute(text('SELECT blocker_id FROM "user_block" WHERE blocker_id = :blocker_id and blocked_id = :blocked_id'), {'blocker_id': user_id, 'blocked_id': user.id}).scalar()
blocked = True if block else False
v4 = {'person_view': user_view(user=user, variant=2),
'blocked': blocked}
return v4
@cache.memoize(timeout=600)
def cached_community_view_variant_1(community: Community, stub=False):

92
app/shared/user.py Normal file
View file

@ -0,0 +1,92 @@
from app import db, cache
from app.constants import ROLE_STAFF, ROLE_ADMIN
from app.models import UserBlock
from app.utils import authorise_api_user, blocked_users
from flask import flash
from flask_babel import _
from flask_login import current_user
from sqlalchemy import text
# would be in app/constants.py
SRC_WEB = 1
SRC_PUB = 2
SRC_API = 3
# only called from API for now, but can be called from web using [un]block_another_user(user.id, SRC_WEB)
# user_id: the local, logged-in user
# person_id: the person they want to block
def block_another_user(person_id, src, auth=None):
if src == SRC_API:
try:
user_id = authorise_api_user(auth)
except:
raise
else:
user_id = current_user.id
if user_id == person_id:
if src == SRC_API:
raise Exception('cannot_block_self')
else:
flash(_('You cannot block yourself.'), 'error')
return
role = db.session.execute(text('SELECT role_id FROM "user_role" WHERE user_id = :person_id'), {'person_id': person_id}).scalar()
if role == ROLE_ADMIN or role == ROLE_STAFF:
if src == SRC_API:
raise Exception('cannot_block_admin_or_staff')
else:
flash(_('You cannot block admin or staff.'), 'error')
return
existing_block = UserBlock.query.filter_by(blocker_id=user_id, blocked_id=person_id).first()
if not existing_block:
block = UserBlock(blocker_id=user_id, blocked_id=person_id)
db.session.add(block)
db.session.execute(text('DELETE FROM "notification_subscription" WHERE entity_id = :current_user AND user_id = :user_id'),
{'current_user': user_id, 'user_id': person_id})
db.session.commit()
cache.delete_memoized(blocked_users, user_id)
# Nothing to fed? (Lemmy doesn't federate anything to the blocked person)
if src == SRC_API:
return user_id
else:
return # let calling function handle confirmation flash message and redirect
def unblock_another_user(person_id, src, auth=None):
if src == SRC_API:
try:
user_id = authorise_api_user(auth)
except:
raise
else:
user_id = current_user.id
if user_id == person_id:
if src == SRC_API:
raise Exception('cannot_unblock_self')
else:
flash(_('You cannot unblock yourself.'), 'error')
return
existing_block = UserBlock.query.filter_by(blocker_id=user_id, blocked_id=person_id).first()
if existing_block:
db.session.delete(existing_block)
db.session.commit()
cache.delete_memoized(blocked_users, user_id)
# Nothing to fed? (Lemmy doesn't federate anything to the unblocked person)
if src == SRC_API:
return user_id
else:
return # let calling function handle confirmation flash message and redirect