mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
946e6fc4bd
10 changed files with 184 additions and 38 deletions
|
@ -503,7 +503,7 @@ def shared_inbox():
|
|||
return ''
|
||||
|
||||
if missing_actor_in_announce_object:
|
||||
if ((request_json['object']['type'] == 'Create' or request_json['object']['type']) and
|
||||
if ((request_json['object']['type'] == 'Create' or request_json['object']['type'] == 'Update') and
|
||||
'attributedTo' in request_json['object']['object'] and isinstance(request_json['object']['object']['attributedTo'], str)):
|
||||
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_MONITOR, request_json, 'nodebb: Actor is missing in the Create')
|
||||
request_json['object']['actor'] = request_json['object']['object']['attributedTo']
|
||||
|
@ -587,7 +587,7 @@ def replay_inbox_request(request_json):
|
|||
return
|
||||
|
||||
if missing_actor_in_announce_object:
|
||||
if ((request_json['object']['type'] == 'Create' or request_json['object']['type']) and
|
||||
if ((request_json['object']['type'] == 'Create' or request_json['object']['type'] == 'Update') and
|
||||
'attributedTo' in request_json['object']['object'] and isinstance(request_json['object']['object']['attributedTo'], str)):
|
||||
request_json['object']['actor'] = request_json['object']['object']['attributedTo']
|
||||
|
||||
|
@ -770,7 +770,7 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
if core_activity['type'] == 'Create' or core_activity['type'] == 'Update':
|
||||
if isinstance(core_activity['object'], str):
|
||||
core_activity = verify_object_from_source(core_activity) # change core_activity['object'] from str to dict, then process normally
|
||||
if not request_json:
|
||||
if not core_activity:
|
||||
log_incoming_ap(id, APLOG_CREATE, APLOG_FAILURE, saved_json, 'Could not verify unsigned request from source')
|
||||
return
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ 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, post_post_remove, \
|
||||
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_mark_as_read, \
|
||||
post_reply_delete, post_reply_report, post_reply_remove, \
|
||||
get_community_list, get_community, post_community_follow, post_community_block, \
|
||||
get_user, post_user_block
|
||||
get_user, post_user_block, get_user_unread_count, get_user_replies
|
||||
from app.shared.auth import log_user_in
|
||||
|
||||
from flask import current_app, jsonify, request
|
||||
|
@ -354,6 +354,18 @@ def post_alpha_comment_remove():
|
|||
return jsonify({"error": str(ex)}), 400
|
||||
|
||||
|
||||
@bp.route('/api/alpha/comment/mark_as_read', methods=['POST'])
|
||||
def post_alpha_comment_mark_as_read():
|
||||
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_reply_mark_as_read(auth, data))
|
||||
except Exception as ex:
|
||||
return jsonify({"error": str(ex)}), 400
|
||||
|
||||
|
||||
# User
|
||||
@bp.route('/api/alpha/user', methods=['GET'])
|
||||
def get_alpha_user():
|
||||
|
@ -379,6 +391,29 @@ def post_alpha_user_login():
|
|||
return jsonify({"error": str(ex)}), 400
|
||||
|
||||
|
||||
@bp.route('/api/alpha/user/unread_count', methods=['GET'])
|
||||
def get_alpha_user_unread_count():
|
||||
if not enable_api():
|
||||
return jsonify({'error': 'alpha api is not enabled'})
|
||||
try:
|
||||
auth = request.headers.get('Authorization')
|
||||
return jsonify(get_user_unread_count(auth))
|
||||
except Exception as ex:
|
||||
return jsonify({"error": str(ex)}), 400
|
||||
|
||||
|
||||
@bp.route('/api/alpha/user/replies', methods=['GET'])
|
||||
def get_alpha_user_replies():
|
||||
if not enable_api():
|
||||
return jsonify({'error': 'alpha api is not enabled'})
|
||||
try:
|
||||
auth = request.headers.get('Authorization')
|
||||
data = request.args.to_dict() or None
|
||||
return jsonify(get_user_replies(auth, data))
|
||||
except Exception as ex:
|
||||
return jsonify({"error": str(ex)}), 400
|
||||
|
||||
|
||||
@bp.route('/api/alpha/user/block', methods=['POST'])
|
||||
def post_alpha_user_block():
|
||||
if not enable_api():
|
||||
|
@ -426,8 +461,7 @@ def alpha_post():
|
|||
return jsonify({"error": "not_yet_implemented"}), 400
|
||||
|
||||
# Reply - not yet implemented
|
||||
@bp.route('/api/alpha/comment', methods=['GET']) # Stage 1 if needed for search
|
||||
@bp.route('/api/alpha/comment/mark_as_read', methods=['POST']) # No DB support
|
||||
@bp.route('/api/alpha/comment', methods=['GET']) # Stage 2
|
||||
@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
|
||||
|
@ -451,7 +485,6 @@ def alpha_chat():
|
|||
@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
|
||||
|
@ -461,7 +494,6 @@ def alpha_chat():
|
|||
@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
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
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, 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, post_reply_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, post_reply_remove, post_reply_mark_as_read
|
||||
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, get_user_unread_count, get_user_replies
|
||||
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ def get_community_list(auth, data):
|
|||
|
||||
def get_community(auth, data):
|
||||
if not data or ('id' not in data and 'name' not in data):
|
||||
raise Exception('missing_parameters')
|
||||
raise Exception('missing parameters for community')
|
||||
if 'id' in data:
|
||||
community = int(data['id'])
|
||||
elif 'name' in data:
|
||||
|
|
|
@ -6,7 +6,7 @@ from app.api.alpha.views import search_view
|
|||
|
||||
def get_search(auth, data):
|
||||
if not data or ('q' not in data and 'type_' not in data):
|
||||
raise Exception('missing_parameters')
|
||||
raise Exception('missing parameters for search')
|
||||
|
||||
type = data['type_']
|
||||
listing_type = data['listing_type'] if 'listing_type' in data else 'Local'
|
||||
|
|
|
@ -35,7 +35,7 @@ def cached_post_list(type, sort, user_id, community_id, community_name, person_i
|
|||
# change when polls are supported
|
||||
posts = posts.filter(Post.type != POST_TYPE_POLL)
|
||||
|
||||
if user_id is not None:
|
||||
if user_id and user_id != person_id:
|
||||
blocked_person_ids = blocked_users(user_id)
|
||||
if blocked_person_ids:
|
||||
posts = posts.filter(Post.user_id.not_in(blocked_person_ids))
|
||||
|
@ -104,7 +104,7 @@ def get_post_list(auth, data, user_id=None, search_type='Posts'):
|
|||
|
||||
def get_post(auth, data):
|
||||
if not data or 'id' not in data:
|
||||
raise Exception('missing_parameters')
|
||||
raise Exception('missing parameters for post')
|
||||
|
||||
id = int(data['id'])
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from app import cache
|
||||
from app import cache, db
|
||||
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected, string_expected
|
||||
from app.api.alpha.views import reply_view, reply_report_view
|
||||
from app.models import PostReply, Post
|
||||
from app.models import Notification, 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, \
|
||||
delete_reply, restore_reply, report_reply, mod_remove_reply, mod_restore_reply
|
||||
from app.utils import authorise_api_user, blocked_users, blocked_instances
|
||||
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import desc, or_
|
||||
|
||||
# person_id param: the author of the reply; user_id param: the current logged-in user
|
||||
@cache.memoize(timeout=3)
|
||||
|
@ -16,7 +16,7 @@ def cached_reply_list(post_id, person_id, sort, max_depth, user_id):
|
|||
if person_id:
|
||||
replies = PostReply.query.filter_by(user_id=person_id)
|
||||
|
||||
if user_id is not None:
|
||||
if user_id and user_id != person_id:
|
||||
blocked_person_ids = blocked_users(user_id)
|
||||
if blocked_person_ids:
|
||||
replies = replies.filter(PostReply.user_id.not_in(blocked_person_ids))
|
||||
|
@ -43,7 +43,7 @@ def get_reply_list(auth, data, user_id=None):
|
|||
person_id = data['person_id'] if data and 'person_id' in data else None
|
||||
|
||||
if data and not post_id and not person_id:
|
||||
raise Exception('missing_parameters')
|
||||
raise Exception('missing parameters for reply')
|
||||
else:
|
||||
if auth:
|
||||
user_id = authorise_api_user(auth)
|
||||
|
@ -62,6 +62,7 @@ def get_reply_list(auth, data, user_id=None):
|
|||
replylist.append(reply_view(reply=reply, variant=2, user_id=user_id))
|
||||
except:
|
||||
continue
|
||||
break
|
||||
list_json = {
|
||||
"comments": replylist
|
||||
}
|
||||
|
@ -210,3 +211,33 @@ def post_reply_remove(auth, data):
|
|||
|
||||
reply_json = reply_view(reply=reply, variant=4, user_id=user_id)
|
||||
return reply_json
|
||||
|
||||
|
||||
def post_reply_mark_as_read(auth, data):
|
||||
required(['comment_reply_id', 'read'], data)
|
||||
integer_expected(['comment_reply_id'], data)
|
||||
boolean_expected(['read'], data)
|
||||
|
||||
reply_id = data['comment_reply_id']
|
||||
read = data['read']
|
||||
|
||||
user_id = authorise_api_user(auth)
|
||||
|
||||
# no real support for this. Just marking the Notification for the reply really
|
||||
# notification has its own id, which would be handy, but reply_view is currently just returning the reply.id for that
|
||||
reply = PostReply.query.filter_by(id=reply_id).one()
|
||||
|
||||
reply_url = '#comment_' + str(reply.id)
|
||||
mention_url = '/comment/' + str(reply.id)
|
||||
notification = Notification.query.filter(Notification.user_id == user_id, or_(Notification.url.ilike(f"%{reply_url}%"), Notification.url.ilike(f"%{mention_url}%"))).first()
|
||||
if notification:
|
||||
notification.read = read
|
||||
db.session.commit()
|
||||
|
||||
reply_json = {'comment_reply_view': reply_view(reply=reply, variant=5, user_id=user_id, read=True)}
|
||||
return reply_json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
from app.api.alpha.views import user_view
|
||||
from app import db
|
||||
from app.api.alpha.views import user_view, reply_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.models import User
|
||||
from app.models import PostReply, User
|
||||
from app.shared.user import block_another_user, unblock_another_user
|
||||
|
||||
from sqlalchemy import text, desc
|
||||
|
||||
|
||||
def get_user(auth, data):
|
||||
if not data or ('person_id' not in data and 'username' not in data):
|
||||
|
@ -27,8 +30,9 @@ def get_user(auth, data):
|
|||
auth = None # avoid authenticating user again in get_post_list and get_reply_list
|
||||
|
||||
# bit unusual. have to help construct the json here rather than in views, to avoid circular dependencies
|
||||
post_list = get_post_list(auth, data, user_id)
|
||||
reply_list = get_reply_list(auth, data, user_id)
|
||||
# lists are empty when viewing own account, to deal with a bug I've yet to identify
|
||||
post_list = get_post_list(auth, data, user_id) if not user_id == person_id else {'posts': []}
|
||||
reply_list = get_reply_list(auth, data, user_id) if not user_id == person_id else {'comments': []}
|
||||
|
||||
user_json = user_view(user=person_id, variant=3)
|
||||
user_json['posts'] = post_list['posts']
|
||||
|
@ -81,3 +85,50 @@ def post_user_block(auth, data):
|
|||
user_id = block_another_user(person_id, SRC_API, auth) if block else unblock_another_user(person_id, SRC_API, auth)
|
||||
user_json = user_view(user=person_id, variant=4, user_id=user_id)
|
||||
return user_json
|
||||
|
||||
|
||||
def get_user_unread_count(auth):
|
||||
user_id = authorise_api_user(auth)
|
||||
|
||||
# Mentions are just included in replies
|
||||
|
||||
unread_notifications = db.session.execute(text("SELECT COUNT(id) as c FROM notification WHERE user_id = :user_id AND read = false"), {'user_id': user_id}).scalar()
|
||||
unread_messages = db.session.execute(text("SELECT * from chat_message AS cm INNER JOIN conversation c ON cm.conversation_id =c.id WHERE c.read = false AND cm.recipient_id = :user_id"), {'user_id': user_id}).scalar()
|
||||
if not unread_messages:
|
||||
unread_messages = 0
|
||||
|
||||
unread_count = {
|
||||
"replies": unread_notifications - unread_messages,
|
||||
"mentions": 0,
|
||||
"private_messages": unread_messages
|
||||
}
|
||||
|
||||
return unread_count
|
||||
|
||||
|
||||
def get_user_replies(auth, data):
|
||||
page = int(data['page']) if data and 'page' in data else 1
|
||||
limit = int(data['limit']) if data and 'limit' in data else 10
|
||||
|
||||
user_id = authorise_api_user(auth)
|
||||
|
||||
unread_urls = db.session.execute(text("select url from notification where user_id = :user_id and read = false and url ilike '%comment%'"), {'user_id': user_id}).scalars()
|
||||
unread_ids = []
|
||||
for url in unread_urls:
|
||||
if '#comment_' in url: # reply format
|
||||
unread_ids.append(url.rpartition('_')[-1])
|
||||
elif '/comment/' in url: # mention format
|
||||
unread_ids.append(url.rpartition('/')[-1])
|
||||
|
||||
replies = PostReply.query.filter(PostReply.id.in_(unread_ids)).order_by(desc(PostReply.posted_at)).paginate(page=page, per_page=limit, error_out=False)
|
||||
|
||||
reply_list = []
|
||||
for reply in replies:
|
||||
reply_list.append(reply_view(reply=reply, variant=5, user_id=user_id))
|
||||
list_json = {
|
||||
"replies": reply_list
|
||||
}
|
||||
|
||||
return list_json
|
||||
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ def calculate_if_has_children(reply): # result used as True / False
|
|||
return db.session.execute(text('SELECT COUNT(id) AS c FROM "post_reply" WHERE parent_id = :id'), {'id': reply.id}).scalar()
|
||||
|
||||
|
||||
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, read=False):
|
||||
if isinstance(reply, int):
|
||||
reply = PostReply.query.filter_by(id=reply).one()
|
||||
|
||||
|
@ -330,6 +330,38 @@ def reply_view(reply: PostReply | int, variant, user_id=None, my_vote=0):
|
|||
|
||||
return v4
|
||||
|
||||
# Variant 5 - views/comment_reply_view.dart - /user/replies api endpoint
|
||||
if variant == 5:
|
||||
bookmarked = db.session.execute(text('SELECT user_id FROM "post_reply_bookmark" WHERE post_reply_id = :post_reply_id and user_id = :user_id'), {'post_reply_id': reply.id, 'user_id': user_id}).scalar()
|
||||
reply_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_REPLY, 'entity_id': reply.id, 'user_id': user_id}).scalar()
|
||||
banned = db.session.execute(text('SELECT user_id FROM "community_ban" WHERE user_id = :user_id and community_id = :community_id'), {'user_id': reply.user_id, 'community_id': reply.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': reply.user_id, 'community_id': reply.community_id}).scalar()
|
||||
admin = db.session.execute(text('SELECT user_id FROM "user_role" WHERE user_id = :user_id and role_id = 4'), {'user_id': reply.user_id}).scalar()
|
||||
|
||||
saved = True if bookmarked else False
|
||||
activity_alert = True if reply_sub else False
|
||||
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
|
||||
|
||||
v5 = {'comment_reply': {'id': reply.id, 'recipient_id': user_id, 'comment_id': reply.id, 'read': read, 'published': reply.posted_at.isoformat() + 'Z'},
|
||||
'comment': reply_view(reply=reply, variant=1),
|
||||
'creator': user_view(user=reply.author, variant=1),
|
||||
'post': post_view(post=reply.post, variant=1),
|
||||
'community': community_view(community=reply.community, variant=1),
|
||||
'recipient': user_view(user=user_id, variant=1),
|
||||
'counts': {'comment_id': reply.id, 'score': reply.score, 'upvotes': reply.up_votes, 'downvotes': reply.down_votes, 'published': reply.posted_at.isoformat() + 'Z', 'child_count': 0},
|
||||
'activity_alert': activity_alert,
|
||||
'creator_banned_from_community': creator_banned_from_community,
|
||||
'creator_is_moderator': creator_is_moderator,
|
||||
'creator_is_admin': creator_is_admin,
|
||||
'subscribed': 'NotSubscribed',
|
||||
'saved': saved,
|
||||
'creator_blocked': False
|
||||
}
|
||||
|
||||
return v5
|
||||
|
||||
|
||||
def reply_report_view(report, reply_id, user_id):
|
||||
# views/comment_report_view.dart - /comment/report api endpoint
|
||||
|
|
|
@ -107,19 +107,19 @@ def retrieve_mods_and_backfill(community_id: int, server, name, community_json=N
|
|||
else:
|
||||
new_membership = CommunityMember(community_id=community.id, user_id=mod.id, is_moderator=True)
|
||||
db.session.add(new_membership)
|
||||
elif community.ap_moderators_url:
|
||||
mods_data = remote_object_to_json(community.ap_moderators_url)
|
||||
if mods_data and mods_data['type'] == 'OrderedCollection' and 'orderedItems' in mods_data:
|
||||
for actor in mods_data['orderedItems']:
|
||||
sleep(0.5)
|
||||
mod = find_actor_or_create(actor)
|
||||
if mod:
|
||||
existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=mod.id).first()
|
||||
if existing_membership:
|
||||
existing_membership.is_moderator = True
|
||||
else:
|
||||
new_membership = CommunityMember(community_id=community.id, user_id=mod.id, is_moderator=True)
|
||||
db.session.add(new_membership)
|
||||
elif community.ap_moderators_url:
|
||||
mods_data = remote_object_to_json(community.ap_moderators_url)
|
||||
if mods_data and mods_data['type'] == 'OrderedCollection' and 'orderedItems' in mods_data:
|
||||
for actor in mods_data['orderedItems']:
|
||||
sleep(0.5)
|
||||
mod = find_actor_or_create(actor)
|
||||
if mod:
|
||||
existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=mod.id).first()
|
||||
if existing_membership:
|
||||
existing_membership.is_moderator = True
|
||||
else:
|
||||
new_membership = CommunityMember(community_id=community.id, user_id=mod.id, is_moderator=True)
|
||||
db.session.add(new_membership)
|
||||
if is_peertube:
|
||||
community.restricted_to_mods = True
|
||||
db.session.commit()
|
||||
|
|
Loading…
Reference in a new issue