refactor community subscription to use new subscription system #20

This commit is contained in:
rimu 2024-04-22 20:53:03 +12:00
parent 95172af37c
commit 708edd51b6
6 changed files with 64 additions and 25 deletions

View file

@ -167,7 +167,7 @@ def domain_blocks():
@bp.route('/api/v3/site')
#@cache.cached(timeout=600)
@cache.cached(timeout=600)
def lemmy_site():
return jsonify(lemmy_site_data())

View file

@ -1535,25 +1535,27 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
def notify_about_post(post: Post):
# todo: eventually this function could trigger a lot of DB activity. This function will need to be a celery task.
# Send notifications based on subscriptions to the author
notifications_sent_to = set()
for notify_id in post.author.notification_subscribers():
new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}",
user_id=notify_id, author_id=post.user_id)
db.session.add(new_notification)
user = User.query.get(notify_id)
user.unread_notifications += 1
db.session.commit()
notifications_sent_to.add(notify_id)
if notify_id != post.user_id:
new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}",
user_id=notify_id, author_id=post.user_id)
db.session.add(new_notification)
user = User.query.get(notify_id)
user.unread_notifications += 1
db.session.commit()
notifications_sent_to.add(notify_id)
# Send notifications based on subscriptions to the community
people_to_notify = CommunityMember.query.filter_by(community_id=post.community_id, notify_new_posts=True, is_banned=False)
for person in people_to_notify:
if person.user_id != post.user_id and person.user_id not in notifications_sent_to:
new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}", user_id=person.user_id, author_id=post.user_id)
for notify_id in post.community.notification_subscribers():
if notify_id != post.user_id and notify_id not in notifications_sent_to:
new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}",
user_id=notify_id, author_id=post.user_id)
db.session.add(new_notification)
user = User.query.get(person.user_id) # todo: make this more efficient by doing a join with CommunityMember at the start of the function
user = User.query.get(notify_id)
user.unread_notifications += 1
db.session.commit()

View file

@ -16,9 +16,10 @@ import os
from app.activitypub.signature import RsaKeys
from app.auth.util import random_token
from app.constants import NOTIF_COMMUNITY
from app.email import send_verification_email, send_email
from app.models import Settings, BannedInstances, Interest, Role, User, RolePermission, Domain, ActivityPubLog, \
utcnow, Site, Instance, File, Notification, Post, CommunityMember
utcnow, Site, Instance, File, Notification, Post, CommunityMember, NotificationSubscription
from app.utils import file_get_contents, retrieve_block_list, blocked_domains, retrieve_peertube_block_list
@ -305,6 +306,18 @@ def register(app):
db.session.query(ActivityPubLog).filter(ActivityPubLog.created_at < utcnow() - timedelta(days=3)).delete()
db.session.commit()
@app.cli.command("migrate_community_notifs")
def migrate_community_notifs():
with app.app_context():
member_infos = CommunityMember.query.filter(CommunityMember.notify_new_posts == True,
CommunityMember.is_banned == False).all()
for member_info in member_infos:
new_notification = NotificationSubscription(user_id=member_info.user_id, entity_id=member_info.community_id,
type=NOTIF_COMMUNITY)
db.session.add(new_notification)
db.session.commit()
print('Done')
def parse_communities(interests_source, segment):
lines = interests_source.split("\n")

View file

@ -21,10 +21,11 @@ from app.community.util import search_for_community, community_url_exists, actor
delete_post_from_community, delete_post_reply_from_community
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \
SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR, REPORT_STATE_NEW, REPORT_STATE_ESCALATED, REPORT_STATE_RESOLVED, \
REPORT_STATE_DISCARDED, POST_TYPE_VIDEO
REPORT_STATE_DISCARDED, POST_TYPE_VIDEO, NOTIF_COMMUNITY
from app.inoculation import inoculation
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \
File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply
File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply, \
NotificationSubscription
from app.community import bp
from app.user.utils import search_for_user
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
@ -1092,7 +1093,7 @@ def community_ban_user(community_id: int, user_id: int):
# todo: federate ban to post author instance
# notify banned person
# Notify banned person
if user.is_local():
cache.delete_memoized(communities_banned_from, user.id)
cache.delete_memoized(joined_communities, user.id)
@ -1107,6 +1108,11 @@ def community_ban_user(community_id: int, user_id: int):
...
# todo: send chatmessage to remote user and federate it
# Remove their notification subscription, if any
db.session.query(NotificationSubscription).filter(NotificationSubscription.entity_id == community.id,
NotificationSubscription.user_id == user.id,
NotificationSubscription.type == NOTIF_COMMUNITY).delete()
return redirect(community.local_url())
else:
return render_template('community/community_ban_user.html', title=_('Ban from community'), form=form, community=community,
@ -1157,7 +1163,20 @@ def community_unban_user(community_id: int, user_id: int):
@bp.route('/<int:community_id>/notification', methods=['GET', 'POST'])
@login_required
def community_notification(community_id: int):
# Toggle whether the current user is subscribed to notifications about this community's posts or not
community = Community.query.get_or_404(community_id)
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == community.id,
NotificationSubscription.user_id == current_user.id,
NotificationSubscription.type == NOTIF_COMMUNITY).first()
if existing_notification:
db.session.delete(existing_notification)
db.session.commit()
else: # no subscription yet, so make one
if not community.user_is_banned(current_user):
new_notification = NotificationSubscription(user_id=current_user.id, entity_id=community.id, type=NOTIF_COMMUNITY)
db.session.add(new_notification)
db.session.commit()
member_info = CommunityMember.query.filter(CommunityMember.community_id == community.id,
CommunityMember.user_id == current_user.id).first()
# existing community members get their notification flag toggled

View file

@ -19,7 +19,7 @@ import jwt
import os
from app.constants import SUBSCRIPTION_NONMEMBER, SUBSCRIPTION_MEMBER, SUBSCRIPTION_MODERATOR, SUBSCRIPTION_OWNER, \
SUBSCRIPTION_BANNED, SUBSCRIPTION_PENDING, NOTIF_USER
SUBSCRIPTION_BANNED, SUBSCRIPTION_PENDING, NOTIF_USER, NOTIF_COMMUNITY
# datetime.utcnow() is depreciated in Python 3.12 so it will need to be swapped out eventually
@ -503,12 +503,15 @@ class Community(db.Model):
return f"https://{current_app.config['SERVER_NAME']}/c/{self.ap_id}"
def notify_new_posts(self, user_id: int) -> bool:
member_info = CommunityMember.query.filter(CommunityMember.community_id == self.id,
CommunityMember.is_banned == False,
CommunityMember.user_id == user_id).first()
if not member_info:
return False
return member_info.notify_new_posts
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == self.id,
NotificationSubscription.user_id == user_id,
NotificationSubscription.type == NOTIF_COMMUNITY).first()
return existing_notification is not None
# ids of all the users who want to be notified when there is a post in this community
def notification_subscribers(self):
return list(db.session.execute(text('SELECT user_id FROM "notification_subscription" WHERE entity_id = :community_id AND type = :type '),
{'community_id': self.id, 'type': NOTIF_COMMUNITY}).scalars())
# instances that have users which are members of this community. (excluding the current instance)
def following_instances(self, include_dormant=False) -> List[Instance]:
@ -836,7 +839,6 @@ class User(UserMixin, db.Model):
return
return User.query.get(id)
def delete_dependencies(self):
if self.cover_id:
file = File.query.get(self.cover_id)
@ -850,6 +852,8 @@ class User(UserMixin, db.Model):
db.session.delete(file)
if self.waiting_for_approval():
db.session.query(UserRegistration).filter(UserRegistration.user_id == self.id).delete()
db.session.query(NotificationSubscription).filter(NotificationSubscription.user_id == self.id).delete()
db.session.query(Notification).filter(Notification.user_id == self.id).delete()
def purge_content(self):
files = File.query.join(Post).filter(Post.user_id == self.id).all()

View file

@ -223,6 +223,7 @@ def change_settings():
@bp.route('/user/<int:user_id>/notification', methods=['GET', 'POST'])
@login_required
def user_notification(user_id: int):
# Toggle whether the current user is subscribed to notifications about this user's posts or not
user = User.query.get_or_404(user_id)
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == user.id,
NotificationSubscription.user_id == current_user.id,