discourse/app/jobs/regular/bulk_invite.rb
Michael Brown 3bf3b9a4a5 DEV: pull email address validation out to a new EmailAddressValidator
We validate the *format* of email addresses in many places with a match against
a regex, often with very slightly different syntax.

Adding a separate EmailAddressValidator simplifies the code in a few spots and
feels cleaner.

Deprecated the old location in case someone is using it in a plugin.

No functionality change is in this commit.

Note: the regex used at the moment does not support using address literals, e.g.:
* localpart@[192.168.0.1]
* localpart@[2001:db8::1]
2022-02-17 21:49:22 -05:00

228 lines
5.5 KiB
Ruby

# frozen_string_literal: true
module Jobs
class BulkInvite < ::Jobs::Base
sidekiq_options retry: false
def initialize
super
@logs = []
@sent = 0
@failed = 0
@groups = {}
@user_fields = {}
@valid_groups = {}
end
def execute(args)
@invites = args[:invites]
raise Discourse::InvalidParameters.new(:invites) if @invites.blank?
@current_user = User.find_by(id: args[:current_user_id])
raise Discourse::InvalidParameters.new(:current_user_id) unless @current_user
@guardian = Guardian.new(@current_user)
process_invites(@invites)
if @invites.length > Invite::BULK_INVITE_EMAIL_LIMIT
::Jobs.enqueue(:process_bulk_invite_emails)
end
ensure
notify_user
end
private
def process_invites(invites)
invites.each do |invite|
if EmailAddressValidator.valid_value?(invite[:email])
# email is valid
send_invite(invite)
@sent += 1
else
# invalid email
save_log "Invalid Email '#{invite[:email]}"
@failed += 1
end
end
rescue Exception => e
save_log "Bulk Invite Process Failed -- '#{e.message}'"
@failed += 1
end
def get_groups(group_names)
groups = []
if group_names
group_names = group_names.split(';')
group_names.each { |group_name|
group = fetch_group(group_name)
if group && can_edit_group?(group)
# valid group
groups.push(group)
else
# invalid group
save_log "Invalid Group '#{group_name}'"
@failed += 1
end
}
end
groups
end
def get_topic(topic_id)
topic = nil
if topic_id
topic = Topic.find_by_id(topic_id)
if topic.nil?
save_log "Invalid Topic ID '#{topic_id}'"
@failed += 1
end
end
topic
end
def get_user_fields(fields)
user_fields = {}
fields.each do |key, value|
@user_fields[key] ||= UserField.includes(:user_field_options).where('name ILIKE ?', key).first || :nil
next if @user_fields[key] == :nil
# Automatically correct user field value
if @user_fields[key].field_type == "dropdown"
value = @user_fields[key].user_field_options.find { |ufo| ufo.value.casecmp?(value) }&.value
end
user_fields[@user_fields[key].id] = value
end
user_fields
end
def send_invite(invite)
email = invite[:email]
groups = get_groups(invite[:groups])
topic = get_topic(invite[:topic_id])
locale = invite[:locale]
user_fields = get_user_fields(invite.except(:email, :groups, :topic_id, :locale))
begin
if user = Invite.find_user_by_email(email)
if groups.present?
Group.transaction do
groups.each do |group|
group.add(user)
GroupActionLogger
.new(@current_user, group)
.log_add_user_to_group(user)
end
end
end
if user_fields.present?
user_fields.each do |user_field, value|
user.set_user_field(user_field, value)
end
user.save_custom_fields
end
if locale.present?
user.locale = locale
user.save!
end
else
if user_fields.present? || locale.present?
user = User.where(staged: true).find_by_email(email)
user ||= User.new(username: UserNameSuggester.suggest(email), email: email, staged: true)
if user_fields.present?
user_fields.each do |user_field, value|
user.set_user_field(user_field, value)
end
end
if locale.present?
user.locale = locale
end
user.save!
end
invite_opts = {
email: email,
topic: topic,
group_ids: groups.map(&:id),
}
if @invites.length > Invite::BULK_INVITE_EMAIL_LIMIT
invite_opts[:emailed_status] = Invite.emailed_status_types[:bulk_pending]
end
Invite.generate(@current_user, invite_opts)
end
rescue => e
save_log "Error inviting '#{email}' -- #{Rails::Html::FullSanitizer.new.sanitize(e.message)}"
@sent -= 1
@failed += 1
end
end
def save_log(message)
@logs << "[#{Time.now}] #{message}"
end
def notify_user
if @current_user
if (@sent > 0 && @failed == 0)
SystemMessage.create_from_system_user(
@current_user,
:bulk_invite_succeeded,
sent: @sent
)
else
SystemMessage.create_from_system_user(
@current_user,
:bulk_invite_failed,
sent: @sent,
failed: @failed,
logs: @logs.join("\n")
)
end
end
end
def fetch_group(group_name)
group_name = group_name.downcase
group = @groups[group_name]
unless group
group = Group.find_by('lower(name) = ?', group_name)
@groups[group_name] = group
end
group
end
def can_edit_group?(group)
group_name = group.name.downcase
result = @valid_groups[group_name]
unless result
result = @guardian.can_edit_group?(group)
@valid_groups[group_name] = result
end
result
end
end
end