docbrown/app/services/mentions/create_all.rb
Lewis Sparlin ca6719b9b1
Issue #9077 has already been resolved, added specs (#15342)
* Prove #9077 has been resolved

* Do no create mentions for any @mention-like syntax from embedded liquid, add spec
2021-11-17 11:27:50 +02:00

96 lines
3.2 KiB
Ruby

module Mentions
# This class creates mentions + associated notifications for Articles and Comments.
# This class will check to see if there are any @-mentions in the post, and will
# create the associated mentions inline if necessary.
class CreateAll
def initialize(notifiable)
@notifiable = notifiable
end
def self.call(...)
new(...).call
end
def call
mentioned_users = users_mentioned_in_text_excluding_author
delete_mentions_removed_from_notifiable_text(mentioned_users)
create_mentions_for(mentioned_users)
end
private
def users_mentioned_in_text_excluding_author
mentioned_usernames = extract_usernames_from_mentions_in_text
collect_existing_users(mentioned_usernames)
.yield_self do |existing_mentioned_users|
reject_notifiable_author(existing_mentioned_users)
end
end
def collect_existing_users(usernames)
User.registered.where(username: usernames)
end
def create_mentions_for(users)
users.each { |user| create_mention_for(user) }
end
def extract_usernames_from_mentions_in_text
# The "mentioned-user" css is added by Html::Parser#user_link_if_exists
doc = Nokogiri::HTML(notifiable.processed_html)
# Remove any mentions that are embedded within any liquid tags
non_liquid_tag_mentions = doc.css(".mentioned-user").reject do |tag|
tag.ancestors(".liquid-comment").any? ||
tag.ancestors("[class^=ltag]").any? ||
tag.ancestors("[class*=' ltag']").any?
end
non_liquid_tag_mentions.map { |link| link.text.delete("@").downcase }
end
def reject_notifiable_author(users)
users.reject { |user| authored_by?(user, notifiable) }
end
def authored_by?(user, notifiable)
user.id == notifiable.user_id
end
def delete_mentions_removed_from_notifiable_text(users)
mentions = notifiable.mentions.where.not(user_id: users).destroy_all
Notification.remove_all(notifiable_ids: mentions.map(&:id), notifiable_type: "Mention") if mentions.present?
end
def user_has_comment_notifications?(user)
user.notifications.exists?(notifiable_id: notifiable.id, notifiable_type: "Comment")
end
def create_mention_for(user)
# Do not create additional notifications for being mentioned in a comment.
if notifiable.is_a?(Comment) && user_has_comment_notifications?(user)
return
end
# The mentionable_type is the model that created the mention, the user is the user to be mentioned.
mention = Mention.create(user_id: user.id, mentionable_id: notifiable.id,
mentionable_type: notifiable.class.name)
# If notifiable is an Article, we need to create the notification for the mention immediately so
# that the notification exists in the database before we attempt to create other Article-related notifications.
# However, if notifiable is a Comment, we can create the notification for the mention in the background.
case notifiable
when Article
Notification.send_mention_notification_without_delay(mention)
when Comment
Notification.send_mention_notification(mention)
end
mention
end
attr_reader :notifiable
end
end