0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 06:24:48 +08:00
discourse/app/services/post_action_notifier.rb
Régis Hanol d818ef3c73
DEV: route bulk tag actions through PostRevisor (#37848)
Bulk tag operations (change, append, remove) were directly manipulating
tag records via DiscourseTagging and TopicTag, bypassing PostRevisor
entirely. This meant callbacks, plugin hooks, and other side effects
tied to the revision pipeline were not triggered.

This refactors the three bulk tag actions to go through
`first_post.revise` instead, using `silent: true` to avoid sending
notifications and `skip_revision: true` to avoid creating revision
records, preserving the existing quiet behavior.

Also adds eager loading of first_post and tags to avoid N+1 queries,
simplifies tag input handling by normalizing both tag_ids and tag_names
into a single code path, and refactors `notification_is_disabled?` in
PostActionNotifier for readability.
2026-02-24 14:39:01 +01:00

181 lines
4.7 KiB
Ruby
Vendored

# frozen_string_literal: true
class PostActionNotifier
def self.disable
@disabled = true
end
def self.enable
@disabled = false
end
# For testing purposes
def self.reset!
@custom_post_revision_notifier_recipients = nil
end
def self.alerter
@alerter ||= PostAlerter.new
end
def self.refresh_like_notification(post, read)
return unless post && post.user_id && post.topic
usernames =
post
.post_actions
.where(post_action_type_id: PostActionType.types[:like])
.joins(:user)
.order("post_actions.created_at desc")
.where("post_actions.created_at > ?", 1.day.ago)
.pluck(:username)
if usernames.length > 0
data = {
topic_title: post.topic.title,
username: usernames[0],
display_username: usernames[0],
username2: usernames[1],
count: usernames.length,
}
Notification.create(
notification_type: Notification.types[:liked],
topic_id: post.topic_id,
post_number: post.post_number,
user_id: post.user_id,
read: read,
data: data.to_json,
)
end
end
def self.post_action_deleted(post_action)
return if @disabled
# We only care about deleting post actions for now
return if post_action.deleted_at.blank?
if post_action.post_action_type_id == PostActionType.types[:like] && post_action.post
read = true
Notification
.where(
topic_id: post_action.post.topic_id,
user_id: post_action.post.user_id,
post_number: post_action.post.post_number,
notification_type: Notification.types[:liked],
)
.each do |notification|
read = false unless notification.read
notification.destroy
end
refresh_like_notification(post_action.post, read)
else
# not using destroy_all cause we want stuff to trigger
Notification.where(post_action_id: post_action.id).each(&:destroy)
end
end
def self.post_action_created(post_action)
return if @disabled
# We only notify on likes for now
return unless post_action.is_like?
post = post_action.post
return if post_action.user.blank? || post.blank?
alerter.create_notification(
post.user,
Notification.types[:liked],
post,
display_username: post_action.user.username,
post_action_id: post_action.id,
user_id: post_action.user_id,
)
end
def self.after_create_post_revision(post_revision)
return if @disabled
post = post_revision.post
return unless post
return if post_revision.user.blank?
return if post.topic.blank?
return if post.topic.private_message?
return if post_revision.silent
return if notification_is_disabled?(post_revision)
user_ids = []
user_ids << post.user_id if post_revision.user_id != post.user_id
# Notify all users watching the topic when the OP of a wiki topic is edited
# or if the topic category allows unlimited owner edits on the OP.
if post.is_first_post? &&
(post.wiki? || post.topic.category_allows_unlimited_owner_edits_on_first_post?)
user_ids.concat(
TopicUser
.watching(post.topic_id)
.where.not(user_id: post_revision.user_id)
.where(topic: post.topic)
.pluck(:user_id),
)
end
custom_post_revision_notifier_recipients.each do |block|
user_ids.concat(Array(block.call(post_revision)))
end
if user_ids.present?
DB.after_commit do
Jobs.enqueue(:notify_post_revision, user_ids: user_ids, post_revision_id: post_revision.id)
end
end
end
def self.after_post_unhide(post, flaggers)
return if @disabled || post.last_editor.blank? || flaggers.blank?
flaggers.each do |flagger|
alerter.create_notification(
flagger,
Notification.types[:edited],
post,
display_username: post.last_editor.username,
acting_user_id: post.last_editor.id,
)
end
end
def self.custom_post_revision_notifier_recipients
@custom_post_revision_notifier_recipients ||= Set.new
end
def self.add_post_revision_notifier_recipients(&block)
custom_post_revision_notifier_recipients << block
end
private
def self.notification_is_disabled?(post_revision)
if SiteSetting.disable_system_edit_notifications &&
post_revision.user_id == Discourse::SYSTEM_USER_ID
return true
end
modifications = post_revision.modifications
if SiteSetting.disable_category_edit_notifications && modifications&.dig("category_id").present?
return true
end
if SiteSetting.disable_tags_edit_notifications && modifications&.dig("tags").present?
return true
end
false
end
end