API: inbox private messages

This commit is contained in:
freamon 2025-01-23 23:21:24 +00:00
parent 7e45e4e939
commit 0f79b8075f
5 changed files with 120 additions and 16 deletions

View file

@ -6,7 +6,8 @@ from app.api.alpha.utils import get_site, post_site_block, \
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_unread_count, get_user_replies
get_user, post_user_block, get_user_unread_count, get_user_replies, post_user_mark_all_as_read, \
get_private_message_list
from app.shared.auth import log_user_in
from flask import current_app, jsonify, request
@ -366,6 +367,19 @@ def post_alpha_comment_mark_as_read():
return jsonify({"error": str(ex)}), 400
# Private Message
@bp.route('/api/alpha/private_message/list', methods=['GET'])
def get_alpha_private_message_list():
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_private_message_list(auth, data))
except Exception as ex:
return jsonify({"error": str(ex)}), 400
# User
@bp.route('/api/alpha/user', methods=['GET'])
def get_alpha_user():
@ -426,6 +440,17 @@ def post_alpha_user_block():
return jsonify({"error": str(ex)}), 400
@bp.route('/api/alpha/user/mark_all_as_read', methods=['POST'])
def post_alpha_user_mark_all_as_read():
if not enable_api():
return jsonify({'error': 'alpha api is not enabled'})
try:
auth = request.headers.get('Authorization')
return jsonify(post_user_mark_all_as_read(auth))
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
@ -436,7 +461,7 @@ def alpha_site():
# Miscellaneous - not yet implemented
@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/resolve_object', methods=['GET']) # Stage 2
@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
@ -469,12 +494,11 @@ def alpha_reply():
return jsonify({"error": "not_yet_implemented"}), 400
# Chat - not yet implemented
@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', methods=['PUT']) # Not available in app
@bp.route('/api/alpha/private_message', methods=['POST']) # Not available in app
@bp.route('/api/alpha/private_message/delete', methods=['POST']) # Not available in app
@bp.route('/api/alpha/private_message/mark_as_read', methods=['POST']) # Not available in app
@bp.route('/api/alpha/private_message/report', methods=['POST']) # Not available in app
@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():
@ -484,15 +508,14 @@ def alpha_chat():
@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/mention/mark_as_read', methods=['POST']) # No DB support / Not available in app (using mark_all instead)
@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/save_user_settings', methods=['PUT']) # Stage 2
@bp.route('/api/alpha/user/change_password', methods=['PUT']) # Stage 2
@bp.route('/api/alpha/user/report_count', methods=['GET']) # Stage 2
@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
@ -502,7 +525,7 @@ def alpha_chat():
@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
@bp.route('/api/alpha/user/logout', methods=['POST']) # Stage 2
def alpha_user():
return jsonify({"error": "not_yet_implemented"}), 400

View file

@ -3,6 +3,7 @@ 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, 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, get_user_unread_count, get_user_replies
from app.api.alpha.utils.user import get_user, post_user_block, get_user_unread_count, get_user_replies, post_user_mark_all_as_read
from app.api.alpha.utils.private_message import get_private_message_list

View file

@ -0,0 +1,38 @@
from app import db
from app.api.alpha.views import private_message_view
from app.models import ChatMessage, Conversation
from app.utils import authorise_api_user
from flask import current_app
from sqlalchemy import text
import re
def get_private_message_list(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
unread_only = data['unread_only'] if data and 'unread_only' in data else True
user_id = authorise_api_user(auth)
read = not unread_only
unread_urls = db.session.execute(text("select url from notification where user_id = :user_id and read = false and url ilike '%#message_<ChatMessage%'"), {'user_id': user_id}).scalars()
unread_ids = []
pattern = r"/chat/.+?#message_<ChatMessage (.+?)>"
for url in unread_urls:
match = re.search(pattern, url)
if match:
unread_ids.append(match.group(1))
private_messages = ChatMessage.query.filter(ChatMessage.recipient_id == user_id, ChatMessage.id.in_(unread_ids)).join(Conversation, Conversation.id == ChatMessage.conversation_id).filter_by(read=read)
pm_list = []
for private_message in private_messages:
ap_id = 'https://' + current_app.config['SERVER_NAME'] + '/chat/' + str(private_message.conversation_id) + '#message_<ChatMessage ' + str(private_message.id) + '>'
pm_list.append(private_message_view(private_message, user_id, ap_id))
pm_json = {
"private_messages": pm_list
}
return pm_json

View file

@ -4,7 +4,7 @@ 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 PostReply, User
from app.models import Conversation, ChatMessage, Notification, PostReply, User
from app.shared.user import block_another_user, unblock_another_user
from sqlalchemy import text, desc
@ -96,6 +96,8 @@ def get_user_unread_count(auth):
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
if unread_notifications == 0:
unread_messages = 0
unread_count = {
"replies": unread_notifications - unread_messages,
@ -132,3 +134,19 @@ def get_user_replies(auth, data):
return list_json
def post_user_mark_all_as_read(auth):
user_id = authorise_api_user(auth)
notifications = Notification.query.filter_by(user_id=user_id, read=False)
for notification in notifications:
notification.read = True
conversations = Conversation.query.filter_by(read=False).join(ChatMessage, ChatMessage.conversation_id == Conversation.id).filter_by(recipient_id=user_id)
for conversation in conversations:
conversation.read = True
db.session.commit()
return {'replies': []}

View file

@ -2,7 +2,7 @@ from __future__ import annotations
from app import cache, db
from app.constants import *
from app.models import Community, CommunityMember, Instance, Post, PostReply, PostVote, User
from app.models import ChatMessage, Community, CommunityMember, Instance, Post, PostReply, PostVote, User
from app.utils import blocked_communities
from sqlalchemy import text
@ -471,6 +471,30 @@ def instance_view(instance: Instance | int, variant):
return v1
def private_message_view(cm: ChatMessage, user_id, ap_id):
creator = user_view(cm.sender_id, variant=1)
recipient = user_view(cm.recipient_id, variant=1)
is_local = creator['instance_id'] == 1
v1 = {
'private_message': {
'id': cm.id,
'creator_id': cm.sender_id,
'recipient_id': user_id,
'content': cm.body,
'deleted': False,
'read': cm.read,
'published': cm.created_at.isoformat() + 'Z',
'ap_id': ap_id,
'local': is_local
},
'creator': creator,
'recipient': recipient
}
return v1
@cache.memoize(timeout=86400)
def cached_modlist_for_community(community_id):
moderator_ids = db.session.execute(text('SELECT user_id FROM "community_member" WHERE community_id = :community_id and is_moderator = True'), {'community_id': community_id}).scalars()