mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-01 02:44:59 +08:00
649 lines
23 KiB
Ruby
649 lines
23 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# name: discourse-reactions
|
|
# about: Allows users to react to a post with emojis.
|
|
# meta_topic_id: 183261
|
|
# version: 0.5
|
|
# author: Ahmed Gagan, Rafael dos Santos Silva, Kris Aubuchon, Joffrey Jaffeux, Kris Kotlarek, Jordan Vidrine
|
|
# url: https://github.com/discourse/discourse/tree/main/plugins/discourse-reactions
|
|
|
|
enabled_site_setting :discourse_reactions_enabled
|
|
|
|
register_asset "stylesheets/common/discourse-reactions.scss"
|
|
register_asset "stylesheets/desktop/discourse-reactions.scss", :desktop
|
|
register_asset "stylesheets/mobile/discourse-reactions.scss", :mobile
|
|
|
|
register_svg_icon "star"
|
|
register_svg_icon "far-star"
|
|
|
|
require_relative "lib/reaction_for_like_site_setting_enum"
|
|
require_relative "lib/reactions_excluded_from_like_site_setting_validator"
|
|
|
|
module ::DiscourseReactions
|
|
PLUGIN_NAME = "discourse-reactions"
|
|
end
|
|
|
|
require_relative "lib/discourse_reactions/engine"
|
|
|
|
after_initialize do
|
|
SeedFu.fixture_paths << Rails.root.join("plugins", "discourse-reactions", "db", "fixtures").to_s
|
|
|
|
%w[
|
|
app/controllers/discourse_reactions/custom_reactions_controller.rb
|
|
app/models/discourse_reactions/reaction_user.rb
|
|
app/models/discourse_reactions/reaction.rb
|
|
app/serializers/reaction_serializer.rb
|
|
app/serializers/user_reaction_serializer.rb
|
|
app/services/discourse_reactions/reaction_manager.rb
|
|
app/services/discourse_reactions/reaction_notification.rb
|
|
app/services/discourse_reactions/reaction_like_synchronizer.rb
|
|
lib/discourse_reactions/guardian_extension.rb
|
|
lib/discourse_reactions/notification_extension.rb
|
|
lib/discourse_reactions/post_alerter_extension.rb
|
|
lib/discourse_reactions/post_extension.rb
|
|
lib/discourse_reactions/post_action_extension.rb
|
|
lib/discourse_reactions/posts_reaction_loader.rb
|
|
lib/discourse_reactions/topic_view_serializer_extension.rb
|
|
lib/discourse_reactions/topic_view_posts_serializer_extension.rb
|
|
lib/discourse_reactions/migration_report.rb
|
|
lib/discourse_reactions/post_reactions_query.rb
|
|
app/jobs/regular/discourse_reactions/like_synchronizer.rb
|
|
app/jobs/scheduled/discourse_reactions/scheduled_like_synchronizer.rb
|
|
].each { |path| require_relative path }
|
|
|
|
reloadable_patch do |plugin|
|
|
Post.prepend DiscourseReactions::PostExtension
|
|
PostAction.prepend DiscourseReactions::PostActionExtension
|
|
TopicViewSerializer.prepend DiscourseReactions::TopicViewSerializerExtension
|
|
TopicViewPostsSerializer.prepend DiscourseReactions::TopicViewPostsSerializerExtension
|
|
PostAlerter.prepend DiscourseReactions::PostAlerterExtension
|
|
Guardian.prepend DiscourseReactions::GuardianExtension
|
|
Notification.singleton_class.prepend DiscourseReactions::NotificationExtension
|
|
end
|
|
|
|
Discourse::Application.routes.append { mount DiscourseReactions::Engine, at: "/" }
|
|
|
|
TopicView.on_preload do |topic_view|
|
|
next unless SiteSetting.discourse_reactions_enabled
|
|
|
|
posts = topic_view.posts
|
|
next if posts.blank?
|
|
|
|
ActiveRecord::Associations::Preloader.new(
|
|
records: posts,
|
|
associations: [:post_actions, { reactions: { reaction_users: :user } }],
|
|
).call
|
|
|
|
post_ids = posts.map(&:id).uniq
|
|
|
|
reaction_users_count_map = TopicViewSerializer.posts_reaction_users_count(post_ids)
|
|
post_actions_with_reaction_users =
|
|
DiscourseReactions::TopicViewSerializerExtension.load_post_action_reaction_users_for_posts(
|
|
post_ids,
|
|
)
|
|
|
|
main_reaction = DiscourseReactions::Reaction.main_reaction_id
|
|
excluded = DiscourseReactions::Reaction.reactions_excluded_from_like
|
|
|
|
excluded_filter =
|
|
if excluded.present?
|
|
"AND dr.reaction_value NOT IN (:excluded)"
|
|
else
|
|
""
|
|
end
|
|
|
|
sql_params = {
|
|
post_ids: post_ids,
|
|
like_type: PostActionType::LIKE_POST_ACTION_ID,
|
|
main_reaction: main_reaction,
|
|
}
|
|
sql_params[:excluded] = excluded if excluded.present?
|
|
|
|
likes_rows = DB.query(<<~SQL, **sql_params)
|
|
SELECT pa.post_id, COUNT(*) as likes_count
|
|
FROM post_actions pa
|
|
WHERE pa.deleted_at IS NULL
|
|
AND pa.post_id IN (:post_ids)
|
|
AND pa.post_action_type_id = :like_type
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM discourse_reactions_reaction_users dru
|
|
JOIN discourse_reactions_reactions dr ON dr.id = dru.reaction_id
|
|
WHERE dr.post_id = pa.post_id
|
|
AND dru.user_id = pa.user_id
|
|
AND dr.reaction_value != :main_reaction
|
|
#{excluded_filter}
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM discourse_reactions_reaction_users dru
|
|
JOIN discourse_reactions_reactions dr ON dr.id = dru.reaction_id
|
|
WHERE dr.post_id = pa.post_id
|
|
AND dru.user_id = pa.user_id
|
|
AND dr.reaction_value = :main_reaction
|
|
)
|
|
GROUP BY pa.post_id
|
|
SQL
|
|
|
|
likes_map = likes_rows.each_with_object({}) { |row, h| h[row.post_id] = row.likes_count }
|
|
|
|
precomputed_reactions_map = {}
|
|
|
|
posts.each do |post|
|
|
post.reaction_users_count = reaction_users_count_map[post.id].to_i
|
|
post.post_actions_with_reaction_users = post_actions_with_reaction_users[post.id] || {}
|
|
|
|
emoji_reactions = post.emoji_reactions.select { |r| r.reaction_users_count.to_i > 0 }
|
|
|
|
reactions =
|
|
emoji_reactions.map do |reaction|
|
|
{
|
|
id: reaction.reaction_value,
|
|
type: reaction.reaction_type.to_sym,
|
|
count: reaction.reaction_users_count,
|
|
}
|
|
end
|
|
|
|
likes = likes_map[post.id] || 0
|
|
|
|
if likes > 0
|
|
reaction_likes, reactions = reactions.partition { |r| r[:id] == main_reaction }
|
|
reactions << {
|
|
id: main_reaction,
|
|
type: :emoji,
|
|
count: likes + reaction_likes.sum { |r| r[:count] },
|
|
}
|
|
end
|
|
|
|
precomputed_reactions_map[post.id] = reactions.sort_by { |r| [-r[:count].to_i, r[:id]] }
|
|
end
|
|
|
|
topic_view.set_preloaded_post_data(:reactions, precomputed_reactions_map)
|
|
topic_view.set_preloaded_post_data(:reaction_users_count, reaction_users_count_map)
|
|
end
|
|
|
|
# Helper module for shared reactions serialization logic
|
|
module ReactionsSerializerHelpers
|
|
def self.reactions_for_post(post)
|
|
reactions = []
|
|
reaction_users_counting_as_like = Set.new
|
|
|
|
post
|
|
.emoji_reactions
|
|
.select { |reaction| reaction[:reaction_users_count] }
|
|
.each do |reaction|
|
|
reactions << {
|
|
id: reaction.reaction_value,
|
|
type: reaction.reaction_type.to_sym,
|
|
count: reaction.reaction_users_count,
|
|
}
|
|
|
|
# NOTE: It does not matter if the reaction is currently an enabled one,
|
|
# we need to handle historical data here too so we don't see double-ups in the UI.
|
|
if !DiscourseReactions::Reaction.reactions_excluded_from_like.include?(
|
|
reaction.reaction_value,
|
|
) && reaction.reaction_value != DiscourseReactions::Reaction.main_reaction_id
|
|
reaction_users_counting_as_like.merge(reaction.reaction_users.pluck(:user_id))
|
|
end
|
|
end
|
|
|
|
likes_query =
|
|
post.post_actions.where(post_action_type_id: PostActionType::LIKE_POST_ACTION_ID)
|
|
|
|
# Get rid of any PostAction records that match up to a ReactionUser
|
|
# that is NOT main_reaction_id and is NOT excluded, otherwise we double
|
|
# up on the count/reaction shown in the UI.
|
|
if reaction_users_counting_as_like.any?
|
|
likes_query = likes_query.where.not(user_id: reaction_users_counting_as_like.to_a)
|
|
end
|
|
|
|
# Also get rid of any PostAction records that match up to a ReactionUser
|
|
# that is now the main_reaction_id and has historical data.
|
|
# This subquery checks if there's a matching ReactionUser with main_reaction_id.
|
|
likes_query =
|
|
likes_query.where(
|
|
<<~SQL,
|
|
post_actions.id NOT IN (
|
|
SELECT post_actions.id
|
|
FROM post_actions
|
|
INNER JOIN discourse_reactions_reaction_users
|
|
ON discourse_reactions_reaction_users.post_id = post_actions.post_id
|
|
AND discourse_reactions_reaction_users.user_id = post_actions.user_id
|
|
INNER JOIN discourse_reactions_reactions
|
|
ON discourse_reactions_reactions.id = discourse_reactions_reaction_users.reaction_id
|
|
WHERE post_actions.post_id = :post_id
|
|
AND post_actions.post_action_type_id = :like_type
|
|
AND discourse_reactions_reactions.reaction_value = :main_reaction
|
|
)
|
|
SQL
|
|
post_id: post.id,
|
|
like_type: PostActionType::LIKE_POST_ACTION_ID,
|
|
main_reaction: DiscourseReactions::Reaction.main_reaction_id,
|
|
)
|
|
|
|
likes = likes_query.count
|
|
|
|
# Likes will only be blank if there are only reactions where the reaction is in
|
|
# discourse_reactions_excluded_from_like. All other reactions will have a `PostAction` record.
|
|
return reactions.sort_by { |reaction| [-reaction[:count].to_i, reaction[:id]] } if likes.zero?
|
|
|
|
# Reactions using main_reaction_id only have a `PostAction` record,
|
|
# not any `ReactionUser` records, as long as the main_reaction_id was never
|
|
# changed -- if it was then we could have a ReactionUser as well.
|
|
reaction_likes, reactions =
|
|
reactions.partition { |r| r[:id] == DiscourseReactions::Reaction.main_reaction_id }
|
|
|
|
reactions << {
|
|
id: DiscourseReactions::Reaction.main_reaction_id,
|
|
type: :emoji,
|
|
count: likes + reaction_likes.sum { |r| r[:count] },
|
|
}
|
|
|
|
reactions.sort_by { |reaction| [-reaction[:count].to_i, reaction[:id]] }
|
|
end
|
|
|
|
def self.current_user_reaction_for_post(post, scope)
|
|
return nil if scope.is_anonymous?
|
|
|
|
post.emoji_reactions.each do |reaction|
|
|
reaction_user = reaction.reaction_users.find { |ru| ru.user_id == scope.user.id }
|
|
next if reaction_user.blank?
|
|
|
|
if reaction.reaction_users_count
|
|
return(
|
|
{
|
|
id: reaction.reaction_value,
|
|
type: reaction.reaction_type.to_sym,
|
|
can_undo: reaction_user.can_undo?,
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
# Any PostAction Like that doesn't have a matching ReactionUser record
|
|
# will count as the main_reaction_id.
|
|
like =
|
|
post.post_actions.find do |post_action|
|
|
post_action.post_action_type_id == PostActionType::LIKE_POST_ACTION_ID &&
|
|
!post_action.trashed? && post_action.user_id == scope.user.id
|
|
end
|
|
|
|
return nil if like.blank?
|
|
|
|
{
|
|
id: DiscourseReactions::Reaction.main_reaction_id,
|
|
type: :emoji,
|
|
can_undo: scope.can_delete_post_action?(like),
|
|
}
|
|
end
|
|
|
|
def self.reaction_users_count_for_post(post)
|
|
return post.reaction_users_count unless post.reaction_users_count.nil?
|
|
TopicViewSerializer.posts_reaction_users_count(post.id)[post.id]
|
|
end
|
|
|
|
def self.current_user_used_main_reaction_for_post(post, scope)
|
|
return false if scope.is_anonymous?
|
|
|
|
like_post_action =
|
|
post.post_actions.find do |post_action|
|
|
post_action.post_action_type_id == PostActionType::LIKE_POST_ACTION_ID &&
|
|
post_action.user_id == scope.user.id && !post_action.trashed?
|
|
end
|
|
|
|
has_matching_reaction_user =
|
|
post.emoji_reactions.any? do |reaction|
|
|
reaction.reaction_users.any? { |ru| ru.user_id == scope.user.id } &&
|
|
(
|
|
if SiteSetting.discourse_reactions_allow_any_emoji
|
|
reaction.reaction_value != DiscourseReactions::Reaction.main_reaction_id
|
|
else
|
|
DiscourseReactions::Reaction.reactions_counting_as_like.include?(
|
|
reaction.reaction_value,
|
|
)
|
|
end
|
|
)
|
|
end
|
|
|
|
like_post_action.present? && !has_matching_reaction_user
|
|
end
|
|
|
|
def self.op_reactions_data_for_topic(topic, scope)
|
|
return nil unless topic.first_post
|
|
|
|
post = topic.first_post
|
|
like_action =
|
|
(
|
|
if scope.user
|
|
PostAction.find_by(
|
|
user_id: scope.user.id,
|
|
post_id: post.id,
|
|
post_action_type_id: PostActionType.types[:like],
|
|
)
|
|
else
|
|
nil
|
|
end
|
|
)
|
|
|
|
{
|
|
id: post.id,
|
|
user_id: post.user_id,
|
|
yours: post.user_id == scope.current_user&.id,
|
|
reactions: reactions_for_post(post),
|
|
current_user_reaction: current_user_reaction_for_post(post, scope),
|
|
current_user_used_main_reaction: current_user_used_main_reaction_for_post(post, scope),
|
|
reaction_users_count: reaction_users_count_for_post(post) || 0,
|
|
likeAction: {
|
|
canToggle: like_action ? scope.can_delete_post_action?(like_action) : true,
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
add_to_serializer(:post, :reactions) do
|
|
map = topic_view&.preloaded_post_data(:reactions)
|
|
if map && map.key?(object.id)
|
|
map[object.id]
|
|
else
|
|
ReactionsSerializerHelpers.reactions_for_post(object)
|
|
end
|
|
end
|
|
|
|
add_to_serializer(:post, :current_user_reaction) do
|
|
ReactionsSerializerHelpers.current_user_reaction_for_post(object, scope)
|
|
end
|
|
|
|
add_to_serializer(:post, :reaction_users_count) do
|
|
map = topic_view&.preloaded_post_data(:reaction_users_count)
|
|
if map && map.key?(object.id)
|
|
map[object.id].to_i
|
|
else
|
|
ReactionsSerializerHelpers.reaction_users_count_for_post(object)
|
|
end
|
|
end
|
|
|
|
add_to_serializer(:post, :current_user_used_main_reaction) do
|
|
ReactionsSerializerHelpers.current_user_used_main_reaction_for_post(object, scope)
|
|
end
|
|
|
|
add_to_serializer(
|
|
:topic_list_item,
|
|
:op_reactions_data,
|
|
include_condition: -> do
|
|
object.association(:first_post).loaded? &&
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
:include_discourse_reactions_data_on_topic_list,
|
|
false,
|
|
scope.user,
|
|
)
|
|
end,
|
|
) { ReactionsSerializerHelpers.op_reactions_data_for_topic(object, scope) }
|
|
|
|
add_to_serializer(
|
|
:suggested_topic,
|
|
:op_reactions_data,
|
|
include_condition: -> do
|
|
object.association(:first_post).loaded? &&
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
:include_discourse_reactions_data_on_suggested_topics,
|
|
false,
|
|
scope.user,
|
|
)
|
|
end,
|
|
) { ReactionsSerializerHelpers.op_reactions_data_for_topic(object, scope) }
|
|
|
|
add_to_serializer(:topic_view, :valid_reactions) { DiscourseReactions::Reaction.valid_reactions }
|
|
|
|
add_model_callback(User, :before_destroy) do
|
|
DiscourseReactions::ReactionUser.where(user_id: self.id).delete_all
|
|
end
|
|
|
|
add_report("reactions") do |report|
|
|
main_id = DiscourseReactions::Reaction.main_reaction_id
|
|
|
|
report.icon = "discourse-emojis"
|
|
report.modes = [:table]
|
|
|
|
report.data = []
|
|
|
|
report.labels = [
|
|
{ type: :date, property: :day, title: I18n.t("reports.reactions.labels.day") },
|
|
{
|
|
type: :number,
|
|
property: :like_count,
|
|
title: main_id,
|
|
html_title: PrettyText.unescape_emoji(CGI.escapeHTML(":#{main_id}:")),
|
|
},
|
|
]
|
|
|
|
reactions = SiteSetting.discourse_reactions_enabled_reactions.split("|") - [main_id]
|
|
|
|
reactions.each do |reaction|
|
|
report.labels << {
|
|
type: :number,
|
|
property: "#{reaction}_count",
|
|
title: reaction,
|
|
html_title: PrettyText.unescape_emoji(CGI.escapeHTML(":#{reaction}:")),
|
|
}
|
|
end
|
|
|
|
reactions_results =
|
|
DB.query(<<~SQL, start_date: report.start_date.to_date, end_date: report.end_date.to_date)
|
|
SELECT
|
|
reactions.reaction_value,
|
|
count(reaction_users.id) as reactions_count,
|
|
date_trunc('day', reaction_users.created_at)::date as day
|
|
FROM discourse_reactions_reactions as reactions
|
|
LEFT OUTER JOIN discourse_reactions_reaction_users as reaction_users on reactions.id = reaction_users.reaction_id
|
|
WHERE reactions.reaction_users_count IS NOT NULL
|
|
AND reaction_users.created_at::DATE >= :start_date::DATE AND reaction_users.created_at::DATE <= :end_date::DATE
|
|
GROUP BY reactions.reaction_value, day
|
|
SQL
|
|
|
|
likes_results =
|
|
DB.query(
|
|
<<~SQL,
|
|
SELECT
|
|
count(post_actions.id) as likes_count,
|
|
date_trunc('day', post_actions.created_at)::date as day
|
|
FROM post_actions as post_actions
|
|
WHERE post_actions.created_at::DATE >= :start_date::DATE AND post_actions.created_at::DATE <= :end_date::DATE
|
|
AND #{DiscourseReactions::PostActionExtension.filter_reaction_likes_sql}
|
|
GROUP BY day
|
|
SQL
|
|
start_date: report.start_date.to_date,
|
|
end_date: report.end_date.to_date,
|
|
like: PostActionType::LIKE_POST_ACTION_ID,
|
|
valid_reactions: DiscourseReactions::Reaction.valid_reactions.to_a,
|
|
)
|
|
|
|
(report.start_date.to_date..report.end_date.to_date).each do |date|
|
|
data = { day: date }
|
|
|
|
like_count = 0
|
|
like_reaction_count = 0
|
|
likes_results.select { |r| r.day == date }.each { |result| like_count += result.likes_count }
|
|
|
|
reactions_results
|
|
.select { |r| r.day == date }
|
|
.each do |result|
|
|
if result.reaction_value == main_id
|
|
like_reaction_count += result.reactions_count
|
|
else
|
|
data["#{result.reaction_value}_count"] ||= 0
|
|
data["#{result.reaction_value}_count"] += result.reactions_count
|
|
end
|
|
end
|
|
|
|
data[:like_count] = like_reaction_count + like_count
|
|
|
|
report.data << data
|
|
end
|
|
end
|
|
|
|
field_key = "display_username"
|
|
consolidated_reactions =
|
|
Notifications::ConsolidateNotifications
|
|
.new(
|
|
from: Notification.types[:reaction],
|
|
to: Notification.types[:reaction],
|
|
threshold: -> { SiteSetting.notification_consolidation_threshold },
|
|
consolidation_window: SiteSetting.likes_notification_consolidation_window_mins.minutes,
|
|
unconsolidated_query_blk:
|
|
Proc.new do |notifications, data|
|
|
notifications.where(
|
|
"data::json ->> 'username2' IS NULL AND data::json ->> 'consolidated' IS NULL",
|
|
).where("data::json ->> '#{field_key}' = ?", data[field_key.to_sym].to_s)
|
|
end,
|
|
consolidated_query_blk:
|
|
Proc.new do |notifications, data|
|
|
notifications.where("(data::json ->> 'consolidated')::bool").where(
|
|
"data::json ->> '#{field_key}' = ?",
|
|
data[field_key.to_sym].to_s,
|
|
)
|
|
end,
|
|
)
|
|
.set_mutations(
|
|
set_data_blk:
|
|
Proc.new do |notification|
|
|
data = notification.data_hash
|
|
data.merge(
|
|
username: data[:display_username],
|
|
name: data[:display_name],
|
|
consolidated: true,
|
|
)
|
|
end,
|
|
)
|
|
.set_precondition(precondition_blk: Proc.new { |data| data[:username2].blank? })
|
|
|
|
consolidated_reactions.before_consolidation_callbacks(
|
|
before_consolidation_blk:
|
|
Proc.new do |notifications, data|
|
|
new_icon = data[:reaction_icon]
|
|
|
|
if new_icon
|
|
icons = notifications.pluck("data::json ->> 'reaction_icon'")
|
|
|
|
data.delete(:reaction_icon) if icons.any? { |i| i != new_icon }
|
|
end
|
|
end,
|
|
before_update_blk:
|
|
Proc.new do |consolidated, updated_data, notification|
|
|
if consolidated.data_hash[:reaction_icon] != notification.data_hash[:reaction_icon]
|
|
updated_data.delete(:reaction_icon)
|
|
end
|
|
end,
|
|
)
|
|
|
|
reacted_by_two_users =
|
|
Notifications::DeletePreviousNotifications
|
|
.new(
|
|
type: Notification.types[:reaction],
|
|
previous_query_blk:
|
|
Proc.new do |notifications, data|
|
|
notifications.where(id: data[:previous_notification_id])
|
|
end,
|
|
)
|
|
.set_mutations(
|
|
set_data_blk:
|
|
Proc.new do |notification|
|
|
existing_notification_of_same_type =
|
|
Notification
|
|
.where(user: notification.user)
|
|
.order("notifications.id DESC")
|
|
.where(topic_id: notification.topic_id, post_number: notification.post_number)
|
|
.where(notification_type: notification.notification_type)
|
|
.where("created_at > ?", 1.day.ago)
|
|
.first
|
|
|
|
data = notification.data_hash
|
|
if existing_notification_of_same_type
|
|
same_type_data = existing_notification_of_same_type.data_hash
|
|
|
|
new_data =
|
|
data.merge(
|
|
previous_notification_id: existing_notification_of_same_type.id,
|
|
username2: same_type_data[:display_username],
|
|
name2: same_type_data[:display_name],
|
|
count: (same_type_data[:count] || 1).to_i + 1,
|
|
)
|
|
|
|
new_data
|
|
else
|
|
data
|
|
end
|
|
end,
|
|
)
|
|
.set_precondition(
|
|
precondition_blk:
|
|
Proc.new do |data, notification|
|
|
always_freq = UserOption.like_notification_frequency_type[:always]
|
|
|
|
notification.user&.user_option&.like_notification_frequency == always_freq &&
|
|
data[:previous_notification_id].present?
|
|
end,
|
|
)
|
|
|
|
register_notification_consolidation_plan(reacted_by_two_users)
|
|
register_notification_consolidation_plan(consolidated_reactions)
|
|
|
|
# Filter out Likes that are also Reactions, for the user likes-received page.
|
|
register_modifier(:user_action_stream_builder) do |builder|
|
|
builder.left_join(<<~SQL)
|
|
discourse_reactions_reaction_users ON discourse_reactions_reaction_users.post_id = a.target_post_id
|
|
AND discourse_reactions_reaction_users.user_id = a.acting_user_id
|
|
SQL
|
|
builder.where("discourse_reactions_reaction_users.id IS NULL")
|
|
end
|
|
|
|
# Filter out the users who Liked as well as Reacted to the post, for the
|
|
# user avatars that show beneath the post when you click the "show more actions"
|
|
# [...] button.
|
|
register_modifier(:post_action_users_list) do |query, post|
|
|
where_clause = <<~SQL
|
|
post_actions.id NOT IN (
|
|
SELECT post_actions.id
|
|
FROM post_actions
|
|
INNER JOIN discourse_reactions_reaction_users ON discourse_reactions_reaction_users.post_id = post_actions.post_id
|
|
AND discourse_reactions_reaction_users.user_id = post_actions.user_id
|
|
WHERE post_actions.post_id = #{post.id}
|
|
)
|
|
SQL
|
|
|
|
query.where(where_clause)
|
|
end
|
|
|
|
on(:first_post_moved) do |target_post, original_post|
|
|
id_map = {}
|
|
ActiveRecord::Base.transaction do
|
|
reactions = DiscourseReactions::Reaction.where(post_id: original_post.id)
|
|
next if !reactions.any?
|
|
|
|
reactions_attributes =
|
|
reactions.map { |reaction| reaction.attributes.except("id").merge(post_id: target_post.id) }
|
|
|
|
DiscourseReactions::Reaction
|
|
.insert_all(reactions_attributes)
|
|
.each_with_index { |entry, index| id_map[reactions[index].id] = entry["id"] }
|
|
|
|
reaction_users = DiscourseReactions::ReactionUser.where(post_id: original_post.id)
|
|
next if !reaction_users.any?
|
|
|
|
reaction_users_attributes =
|
|
reaction_users.map do |reaction_user|
|
|
reaction_user
|
|
.attributes
|
|
.except("id")
|
|
.merge(post_id: target_post.id, reaction_id: id_map[reaction_user.reaction_id])
|
|
end
|
|
|
|
DiscourseReactions::ReactionUser.insert_all(reaction_users_attributes)
|
|
end
|
|
end
|
|
|
|
on(:site_setting_changed) do |name, old_value, new_value|
|
|
if name == :discourse_reactions_excluded_from_like &&
|
|
SiteSetting.discourse_reactions_like_sync_enabled
|
|
::Jobs.cancel_scheduled_job(Jobs::DiscourseReactions::LikeSynchronizer)
|
|
::Jobs.enqueue_at(5.minutes.from_now, Jobs::DiscourseReactions::LikeSynchronizer)
|
|
end
|
|
end
|
|
end
|