mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-04-29 20:05:04 +08:00
Follow up to #38901 The focus of this PR is to remove callbacks from the `GroupUser` model that were migrated to `GroupUserManager`. Also renamed `GroupManager` to `GroupUserManager` to better describe what it's for. Consolidated `after_commit` calls on `GroupUser` into a `sync_via_manager` callback so that every path (including unknown paths) will flow through the manager. There were quite a few places in the codebase and external plugins, etc. that were calling `GroupUser.create!` directly or some variation that was bypassing `group.add` methods. --------- Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
269 lines
7.8 KiB
Ruby
269 lines
7.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class GroupManager
|
|
def initialize(group)
|
|
@group = group
|
|
end
|
|
|
|
def add(user_ids, automatic: false)
|
|
return [] if user_ids.blank?
|
|
|
|
added_user_ids = bulk_add_transaction(user_ids)
|
|
return [] if added_user_ids.blank?
|
|
|
|
bulk_publish_category_updates(added_user_ids)
|
|
|
|
User
|
|
.where(id: added_user_ids)
|
|
.find_each { |user| @group.trigger_user_added_event(user, automatic) }
|
|
|
|
added_user_ids
|
|
end
|
|
|
|
def remove(user_ids)
|
|
return [] if user_ids.blank?
|
|
|
|
group_users_to_remove = @group.group_users.where(user_id: user_ids)
|
|
return [] if group_users_to_remove.empty?
|
|
|
|
webhook_payloads = build_user_removed_webhook_payloads(group_users_to_remove)
|
|
|
|
removed_user_ids = group_users_to_remove.pluck(:user_id)
|
|
bulk_remove_transaction(removed_user_ids)
|
|
|
|
recalculate_trust_level(removed_user_ids)
|
|
bulk_publish_category_updates(removed_user_ids)
|
|
|
|
User.where(id: removed_user_ids).find_each { |user| @group.trigger_user_removed_event(user) }
|
|
enqueue_user_removed_webhook_events(webhook_payloads)
|
|
|
|
removed_user_ids
|
|
end
|
|
|
|
def sync_add_side_effects(added_user_ids)
|
|
update_title(added_user_ids)
|
|
set_primary_group_and_update_flair(added_user_ids)
|
|
grant_trust_level(added_user_ids)
|
|
GroupUser.bulk_set_category_notifications(@group, added_user_ids)
|
|
GroupUser.bulk_set_tag_notifications(@group, added_user_ids)
|
|
increase_group_user_count(added_user_ids)
|
|
end
|
|
|
|
def sync_removal_side_effects(removed_user_ids)
|
|
decrease_group_user_count(removed_user_ids)
|
|
|
|
removed_user_id = removed_user_ids.first
|
|
return unless User.exists?(removed_user_id)
|
|
|
|
grant_other_available_title(removed_user_ids)
|
|
remove_primary_and_flair_group(removed_user_ids)
|
|
sync_recalculate_trust_level(removed_user_id)
|
|
end
|
|
|
|
def decrease_group_user_count(removed_user_ids)
|
|
Group.update_counters(@group.id, user_count: -removed_user_ids.size)
|
|
end
|
|
|
|
private
|
|
|
|
def bulk_add_transaction(user_ids)
|
|
added_user_ids = nil
|
|
|
|
Group.transaction do
|
|
sql = <<~SQL
|
|
INSERT INTO group_users
|
|
(group_id, user_id, notification_level, created_at, updated_at)
|
|
SELECT
|
|
:group_id,
|
|
u.id,
|
|
:notification_level,
|
|
CURRENT_TIMESTAMP,
|
|
CURRENT_TIMESTAMP
|
|
FROM users AS u
|
|
WHERE u.id IN (:user_ids)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM group_users AS gu
|
|
WHERE gu.user_id = u.id AND
|
|
gu.group_id = :group_id
|
|
)
|
|
ON CONFLICT (group_id, user_id) DO NOTHING
|
|
RETURNING user_id
|
|
SQL
|
|
|
|
added_user_ids =
|
|
DB.query_single(
|
|
sql,
|
|
group_id: @group.id,
|
|
user_ids: user_ids,
|
|
notification_level: @group.default_notification_level,
|
|
)
|
|
|
|
return added_user_ids if added_user_ids.blank?
|
|
|
|
sync_add_side_effects(added_user_ids)
|
|
end
|
|
|
|
added_user_ids
|
|
end
|
|
|
|
def bulk_remove_transaction(removed_user_ids)
|
|
Group.transaction do
|
|
@group.group_users.where(user_id: removed_user_ids).delete_all
|
|
|
|
remove_primary_and_flair_group(removed_user_ids)
|
|
grant_other_available_title(removed_user_ids)
|
|
|
|
decrease_group_user_count(removed_user_ids)
|
|
end
|
|
end
|
|
|
|
def publish_category_updates(user)
|
|
if @group.categories.count < Group::PUBLISH_CATEGORIES_LIMIT
|
|
guardian = Guardian.new(user)
|
|
group_categories = @group.categories.map { |c| Category.set_permission!(guardian, c) }
|
|
updated_categories = group_categories.select(&:permission)
|
|
removed_category_ids = group_categories.reject(&:permission).map(&:id)
|
|
|
|
MessageBus.publish(
|
|
"/categories",
|
|
{
|
|
categories: ActiveModel::ArraySerializer.new(updated_categories).as_json,
|
|
deleted_categories: removed_category_ids,
|
|
},
|
|
user_ids: [user.id],
|
|
)
|
|
else
|
|
Discourse.request_refresh!(user_ids: [user.id])
|
|
end
|
|
end
|
|
|
|
def bulk_publish_category_updates(user_ids)
|
|
return if user_ids.blank?
|
|
return unless @group.categories.exists?
|
|
|
|
if user_ids.size == 1
|
|
user = User.find(user_ids.first)
|
|
publish_category_updates(user)
|
|
else
|
|
Discourse.request_refresh!(user_ids:)
|
|
end
|
|
end
|
|
|
|
def build_user_removed_webhook_payloads(group_users_relation)
|
|
return unless WebHook.active_web_hooks(:group_user)
|
|
|
|
payloads = []
|
|
group_users_relation.find_each do |group_user|
|
|
payloads << {
|
|
id: group_user.id,
|
|
payload: WebHook.generate_payload(:group_user, group_user, WebHookGroupUserSerializer),
|
|
}
|
|
end
|
|
payloads
|
|
end
|
|
|
|
def enqueue_user_removed_webhook_events(webhook_payloads)
|
|
webhook_payloads&.each do |webhook_payload|
|
|
WebHook.enqueue_hooks(
|
|
:group_user,
|
|
:user_removed_from_group,
|
|
id: webhook_payload[:id],
|
|
payload: webhook_payload[:payload],
|
|
group_ids: [@group.id],
|
|
)
|
|
end
|
|
end
|
|
|
|
def update_title(added_user_ids)
|
|
if @group.title.present?
|
|
User.where(id: added_user_ids, title: [nil, ""]).update_all(title: @group.title)
|
|
end
|
|
end
|
|
|
|
def set_primary_group_and_update_flair(added_user_ids)
|
|
return unless @group.primary_group?
|
|
|
|
User
|
|
.where(id: added_user_ids)
|
|
.where("flair_group_id IS NOT DISTINCT FROM primary_group_id")
|
|
.update_all(flair_group_id: @group.id)
|
|
|
|
DB.exec(<<~SQL, user_ids: added_user_ids, new_title: @group.title)
|
|
UPDATE users u
|
|
SET title = :new_title
|
|
WHERE u.id IN (:user_ids)
|
|
AND u.primary_group_id IS NOT NULL
|
|
AND EXISTS (
|
|
SELECT 1 FROM groups g
|
|
WHERE g.id = u.primary_group_id
|
|
AND g.title = u.title
|
|
)
|
|
SQL
|
|
|
|
User.where(id: added_user_ids).update_all(primary_group_id: @group.id)
|
|
end
|
|
|
|
def grant_trust_level(added_user_ids)
|
|
return if @group.grant_trust_level.nil? || @group.grant_trust_level.zero?
|
|
|
|
if added_user_ids.size == 1
|
|
user = User.find(added_user_ids.first)
|
|
TrustLevelGranter.grant(@group.grant_trust_level, user)
|
|
else
|
|
Jobs.enqueue(
|
|
:bulk_grant_trust_level,
|
|
user_ids: added_user_ids,
|
|
trust_level: @group.grant_trust_level,
|
|
)
|
|
end
|
|
end
|
|
|
|
def increase_group_user_count(added_user_ids)
|
|
Group.update_counters(@group.id, user_count: added_user_ids.size)
|
|
end
|
|
|
|
def grant_other_available_title(removed_user_ids)
|
|
if @group.title.present?
|
|
DB.exec(<<~SQL, user_ids: removed_user_ids, title: @group.title)
|
|
UPDATE users u
|
|
SET title = NULL
|
|
WHERE u.id IN (:user_ids)
|
|
AND u.title = :title
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM group_users gu
|
|
JOIN groups g ON g.id = gu.group_id
|
|
WHERE gu.user_id = u.id
|
|
AND g.title IS NOT NULL AND g.title <> ''
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM user_badges ub
|
|
JOIN badges b ON b.id = ub.badge_id
|
|
WHERE ub.user_id = u.id
|
|
AND b.allow_title = true
|
|
)
|
|
SQL
|
|
|
|
User
|
|
.where(id: removed_user_ids, title: @group.title)
|
|
.find_each { |user| user.update_column(:title, user.next_best_title) }
|
|
end
|
|
end
|
|
|
|
def remove_primary_and_flair_group(removed_user_ids)
|
|
User.where(primary_group_id: @group.id, id: removed_user_ids).update_all(primary_group_id: nil)
|
|
User.where(flair_group_id: @group.id, id: removed_user_ids).update_all(flair_group_id: nil)
|
|
end
|
|
|
|
def recalculate_trust_level(removed_user_ids)
|
|
return if @group.grant_trust_level.nil? || @group.grant_trust_level.zero?
|
|
|
|
Jobs.enqueue(:bulk_grant_trust_level, user_ids: removed_user_ids, recalculate: true)
|
|
end
|
|
|
|
def sync_recalculate_trust_level(removed_user_id)
|
|
return if @group.grant_trust_level.nil? || @group.grant_trust_level.zero?
|
|
|
|
user = User.find(removed_user_id)
|
|
Promotion.recalculate(user, use_previous_trust_level: true)
|
|
end
|
|
end
|