mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-27 20:52:53 +08:00
Previously, anonymous users clicking event RSVP buttons hit a dead-end with no signup prompt, and other engagement actions (Like, React, Vote) opened the login modal but lost the click on the way to authentication. This change captures the intent in a short-lived signed cookie via `POST /anonymous-action` and replays it through each action's existing service when `CurrentUser#log_on_user` completes, so anonymous engagement converts to signups with the original action carried through. Ref - t/184288 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
717 lines
21 KiB
Ruby
Vendored
717 lines
21 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
require "csv"
|
|
|
|
class InvitesController < ApplicationController
|
|
ALLOWED_BULK_INVITE_COLUMNS = %w[email groups topic_id locale]
|
|
|
|
requires_login only: %i[
|
|
create
|
|
create_multiple
|
|
update
|
|
retrieve
|
|
destroy
|
|
destroy_all_expired
|
|
resend_invite
|
|
resend_all_invites
|
|
upload_csv
|
|
]
|
|
|
|
skip_before_action :check_xhr, except: [:perform_accept_invitation]
|
|
skip_before_action :preload_json, except: [:show]
|
|
skip_before_action :redirect_to_login_if_required
|
|
skip_before_action :redirect_to_profile_if_required
|
|
|
|
before_action :ensure_invites_allowed, only: %i[show perform_accept_invitation]
|
|
before_action :ensure_new_registrations_allowed, only: %i[show perform_accept_invitation]
|
|
|
|
def show
|
|
expires_now
|
|
|
|
RateLimiter.new(nil, "invites-show-#{request.remote_ip}", 100, 1.minute).performed!
|
|
|
|
invite = Invite.find_by(invite_key: params[:id])
|
|
|
|
if !invite.present? || !invite.redeemable?
|
|
show_irredeemable_invite(invite)
|
|
return
|
|
end
|
|
|
|
# automatically redirect to the topic if the user is logged in and can see it,
|
|
# but only if the invite wouldn't also add the user to a group
|
|
if current_user
|
|
new_group_ids = invite.groups.pluck(:id) - current_user.group_users.pluck(:group_id)
|
|
if new_group_ids.empty? && topic = invite.topics.first
|
|
return redirect_to(topic.url) if current_user.guardian.can_see?(topic)
|
|
end
|
|
end
|
|
|
|
show_invite(invite)
|
|
rescue RateLimiter::LimitExceeded => e
|
|
flash.now[:error] = e.description
|
|
render layout: "no_ember"
|
|
end
|
|
|
|
def create_multiple
|
|
guardian.ensure_can_bulk_invite_to_forum!
|
|
emails = params[:email]
|
|
# validate that topics and groups can accept invites.
|
|
if params[:topic_id].present?
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
raise Discourse::InvalidParameters.new(:topic_id) if topic.blank?
|
|
guardian.ensure_can_invite_to!(topic)
|
|
end
|
|
|
|
if params[:group_ids].present? || params[:group_names].present?
|
|
groups = Group.lookup_groups(group_ids: params[:group_ids], group_names: params[:group_names])
|
|
end
|
|
|
|
guardian.ensure_can_invite_to_forum!(groups)
|
|
|
|
if !groups_can_see_topic?(groups, topic)
|
|
editable_topic_groups = topic.category.groups.filter { |g| guardian.can_edit_group?(g) }
|
|
return(
|
|
render_json_error(
|
|
I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", ")),
|
|
)
|
|
)
|
|
end
|
|
|
|
if emails.size > SiteSetting.max_api_invites
|
|
return(
|
|
render_json_error(
|
|
I18n.t("invite.max_invite_emails_limit_exceeded", max: SiteSetting.max_api_invites),
|
|
422,
|
|
)
|
|
)
|
|
end
|
|
|
|
success = []
|
|
fail = []
|
|
|
|
emails.map do |email|
|
|
invite =
|
|
Invite.generate(
|
|
current_user,
|
|
email: email,
|
|
description: params[:description],
|
|
domain: params[:domain],
|
|
skip_email: skip_email_param,
|
|
invited_by: current_user,
|
|
custom_message: params["custom_message"],
|
|
max_redemptions_allowed: params[:max_redemptions_allowed],
|
|
topic_id: topic&.id,
|
|
group_ids: groups&.map(&:id),
|
|
expires_at: params[:expires_at],
|
|
invite_to_topic: params[:invite_to_topic],
|
|
)
|
|
success.push({ email: email, invite: invite }) if invite
|
|
rescue Invite::UserExists => e
|
|
fail.push({ email: email, error: e.message })
|
|
rescue ActiveRecord::RecordInvalid => e
|
|
fail.push({ email: email, error: e.record.errors.full_messages.first })
|
|
end
|
|
|
|
render json: {
|
|
num_successfully_created_invitations: success.length,
|
|
num_failed_invitations: fail.length,
|
|
failed_invitations: fail,
|
|
successful_invitations:
|
|
success.map do |s| InviteSerializer.new(s[:invite], scope: guardian) end,
|
|
}
|
|
end
|
|
|
|
def create
|
|
if params[:topic_id].present?
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
raise Discourse::InvalidParameters.new(:topic_id) if topic.blank?
|
|
guardian.ensure_can_invite_to!(topic)
|
|
end
|
|
|
|
if params[:group_ids].present? || params[:group_names].present?
|
|
groups = Group.lookup_groups(group_ids: params[:group_ids], group_names: params[:group_names])
|
|
end
|
|
|
|
guardian.ensure_can_invite_to_forum!(groups)
|
|
|
|
if !groups_can_see_topic?(groups, topic)
|
|
editable_topic_groups = topic.category.groups.filter { |g| guardian.can_edit_group?(g) }
|
|
return(
|
|
render_json_error(
|
|
I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", ")),
|
|
)
|
|
)
|
|
end
|
|
|
|
invite =
|
|
Invite.generate(
|
|
current_user,
|
|
email: params[:email],
|
|
description: params[:description],
|
|
domain: params[:domain],
|
|
skip_email: skip_email_param,
|
|
invited_by: current_user,
|
|
custom_message: params[:custom_message],
|
|
max_redemptions_allowed: params[:max_redemptions_allowed],
|
|
topic_id: topic&.id,
|
|
group_ids: groups&.map(&:id),
|
|
expires_at: params[:expires_at],
|
|
invite_to_topic: params[:invite_to_topic],
|
|
)
|
|
|
|
if invite.present?
|
|
render_serialized(
|
|
invite,
|
|
InviteSerializer,
|
|
scope: guardian,
|
|
root: nil,
|
|
show_emails: params.has_key?(:email),
|
|
show_warnings: true,
|
|
)
|
|
else
|
|
render json: failed_json, status: :unprocessable_entity
|
|
end
|
|
rescue Invite::UserExists => e
|
|
render_json_error(e.message)
|
|
rescue ActiveRecord::RecordInvalid => e
|
|
render_json_error(e.record.errors.full_messages.first)
|
|
end
|
|
|
|
def retrieve
|
|
params.require(:email)
|
|
|
|
invite = Invite.find_by(invited_by: current_user, email: params[:email])
|
|
raise Discourse::InvalidParameters.new(:email) if invite.blank?
|
|
|
|
guardian.ensure_can_invite_to_forum!(nil)
|
|
|
|
render_serialized(
|
|
invite,
|
|
InviteSerializer,
|
|
scope: guardian,
|
|
root: nil,
|
|
show_emails: params.has_key?(:email),
|
|
show_warnings: true,
|
|
)
|
|
end
|
|
|
|
def update
|
|
invite = Invite.find_by(invited_by: current_user, id: params[:id])
|
|
raise Discourse::InvalidParameters.new(:id) if invite.blank?
|
|
|
|
if params[:topic_id].present?
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
raise Discourse::InvalidParameters.new(:topic_id) if topic.blank?
|
|
guardian.ensure_can_invite_to!(topic)
|
|
end
|
|
|
|
invite.topics.each { |t| guardian.ensure_can_invite_to!(t) } if !params.has_key?(:topic_id)
|
|
|
|
if params[:group_ids].present? || params[:group_names].present?
|
|
groups = Group.lookup_groups(group_ids: params[:group_ids], group_names: params[:group_names])
|
|
end
|
|
|
|
guardian.ensure_can_invite_to_forum!(groups)
|
|
|
|
Invite.transaction do
|
|
if params.has_key?(:topic_id)
|
|
invite.topic_invites.destroy_all
|
|
invite.topic_invites.create!(topic_id: topic.id) if topic.present?
|
|
end
|
|
|
|
if params.has_key?(:group_ids) || params.has_key?(:group_names)
|
|
invite.invited_groups.destroy_all
|
|
if groups.present?
|
|
groups.each { |group| invite.invited_groups.find_or_create_by!(group_id: group.id) }
|
|
end
|
|
end
|
|
|
|
if !groups_can_see_topic?(invite.groups, invite.topics.first)
|
|
editable_topic_groups =
|
|
invite.topics.first.category.groups.filter { |g| guardian.can_edit_group?(g) }
|
|
return(
|
|
render_json_error(
|
|
I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", ")),
|
|
)
|
|
)
|
|
end
|
|
|
|
if params.has_key?(:email)
|
|
old_email = invite.email.presence
|
|
new_email = params[:email].presence
|
|
|
|
if new_email
|
|
if Invite
|
|
.where.not(id: invite.id)
|
|
.find_by(email: new_email.downcase, invited_by_id: current_user.id)
|
|
&.redeemable?
|
|
return(
|
|
render_json_error(
|
|
I18n.t("invite.invite_exists", email: CGI.escapeHTML(new_email)),
|
|
status: 409,
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
if old_email != new_email
|
|
invite.emailed_status =
|
|
if new_email && !params[:skip_email]
|
|
Invite.emailed_status_types[:pending]
|
|
else
|
|
Invite.emailed_status_types[:not_required]
|
|
end
|
|
end
|
|
|
|
invite.domain = nil if invite.email.present?
|
|
end
|
|
|
|
if params.has_key?(:domain)
|
|
invite.domain = params[:domain]
|
|
|
|
if invite.domain.present?
|
|
invite.email = nil
|
|
invite.emailed_status = Invite.emailed_status_types[:not_required]
|
|
end
|
|
end
|
|
|
|
if params[:send_email]
|
|
if !SiteSetting.allow_email_invites
|
|
return render_json_error(I18n.t("invite.email_invites_disabled"))
|
|
end
|
|
|
|
if invite.emailed_status != Invite.emailed_status_types[:pending]
|
|
begin
|
|
RateLimiter.new(current_user, "resend-invite-per-hour", 10, 1.hour).performed!
|
|
rescue RateLimiter::LimitExceeded
|
|
return render_json_error(I18n.t("rate_limiter.slow_down"))
|
|
end
|
|
end
|
|
|
|
invite.emailed_status = Invite.emailed_status_types[:pending]
|
|
end
|
|
|
|
begin
|
|
invite.update!(
|
|
params.permit(
|
|
:email,
|
|
:description,
|
|
:custom_message,
|
|
:max_redemptions_allowed,
|
|
:expires_at,
|
|
),
|
|
)
|
|
rescue ActiveRecord::RecordInvalid => e
|
|
return render_json_error(e.record.errors.full_messages.first)
|
|
end
|
|
end
|
|
|
|
if invite.emailed_status == Invite.emailed_status_types[:pending]
|
|
invite.update_column(:emailed_status, Invite.emailed_status_types[:sending])
|
|
Jobs.enqueue(:invite_email, invite_id: invite.id, invite_to_topic: params[:invite_to_topic])
|
|
end
|
|
|
|
render_serialized(
|
|
invite,
|
|
InviteSerializer,
|
|
scope: guardian,
|
|
root: nil,
|
|
show_emails: params.has_key?(:email),
|
|
show_warnings: true,
|
|
)
|
|
end
|
|
|
|
def destroy
|
|
params.require(:id)
|
|
|
|
invite = Invite.find_by(id: params[:id])
|
|
raise Discourse::InvalidParameters.new(:id) if invite.blank?
|
|
|
|
guardian.ensure_can_destroy_invite!(invite)
|
|
|
|
invite.trash!(current_user)
|
|
|
|
render json: success_json
|
|
end
|
|
|
|
def perform_accept_invitation
|
|
# When DiscourseConnect is enabled, all invite acceptance is done
|
|
# via the SSO flow (SessionController#sso_login)
|
|
raise Discourse::NotFound if SiteSetting.enable_discourse_connect
|
|
|
|
params.require(:id)
|
|
params.permit(
|
|
:email,
|
|
:username,
|
|
:name,
|
|
:password,
|
|
:timezone,
|
|
:email_token,
|
|
user_custom_fields: {
|
|
},
|
|
)
|
|
|
|
invite = Invite.find_by(invite_key: params[:id])
|
|
redeeming_user = current_user
|
|
|
|
if invite.present?
|
|
begin
|
|
attrs = { ip_address: request.remote_ip, session: server_session }
|
|
|
|
if redeeming_user
|
|
attrs[:redeeming_user] = redeeming_user
|
|
else
|
|
attrs[:username] = params[:username]
|
|
attrs[:name] = params[:name]
|
|
attrs[:password] = params[:password]
|
|
attrs[:user_custom_fields] = params[:user_custom_fields]
|
|
|
|
# If the invite is not scoped to an email then we allow the
|
|
# user to provide it themselves
|
|
if invite.is_invite_link?
|
|
params.require(:email)
|
|
attrs[:email] = params[:email]
|
|
else
|
|
# Otherwise we always use the email from the invitation.
|
|
attrs[:email] = invite.email
|
|
attrs[:email_token] = params[:email_token] if params[:email_token].present?
|
|
end
|
|
end
|
|
|
|
user = invite.redeem(**attrs)
|
|
rescue ActiveRecord::RecordInvalid,
|
|
ActiveRecord::RecordNotSaved,
|
|
ActiveRecord::LockWaitTimeout,
|
|
Invite::UserExists => e
|
|
return render json: failed_json.merge(message: e.message), status: :precondition_failed
|
|
end
|
|
|
|
if user.blank?
|
|
return(
|
|
render json: failed_json.merge(message: I18n.t("invite.not_found_json")),
|
|
status: :not_found
|
|
)
|
|
end
|
|
|
|
if !redeeming_user && user.active? && user.guardian.can_access_forum?
|
|
log_on_user(user, replay_anonymous_action: true)
|
|
end
|
|
|
|
user.update_timezone_if_missing(params[:timezone])
|
|
post_process_invite(user)
|
|
create_topic_invite_notifications(invite, user)
|
|
|
|
topic = invite.topics.first
|
|
response = {}
|
|
|
|
if user.present?
|
|
if user.active? && user.guardian.can_access_forum?
|
|
response[:message] = I18n.t("invite.existing_user_success") if redeeming_user
|
|
|
|
if user.guardian.can_see?(topic)
|
|
response[:redirect_to] = path(topic.relative_url)
|
|
else
|
|
response[:redirect_to] = path("/")
|
|
end
|
|
else
|
|
response[:message] = if user.active?
|
|
I18n.t("activation.approval_required")
|
|
else
|
|
I18n.t("invite.confirm_email")
|
|
end
|
|
|
|
cookies[:destination_url] = path(topic.relative_url) if user.guardian.can_see?(topic)
|
|
end
|
|
end
|
|
|
|
render json: success_json.merge(response)
|
|
else
|
|
render json: failed_json.merge(message: I18n.t("invite.not_found_json")), status: :not_found
|
|
end
|
|
end
|
|
|
|
def destroy_all_expired
|
|
user = fetch_user_from_params
|
|
guardian.ensure_can_destroy_all_invites!(user)
|
|
|
|
Invite
|
|
.where(invited_by: user)
|
|
.where("expires_at < ?", Time.zone.now)
|
|
.find_each { |invite| invite.trash!(current_user) }
|
|
|
|
render json: success_json
|
|
end
|
|
|
|
def resend_invite
|
|
if !SiteSetting.allow_email_invites
|
|
return render_json_error(I18n.t("invite.email_invites_disabled"))
|
|
end
|
|
|
|
params.require(:email)
|
|
RateLimiter.new(current_user, "resend-invite-per-hour", 10, 1.hour).performed!
|
|
|
|
invite = Invite.find_by(invited_by_id: current_user.id, email: params[:email])
|
|
raise Discourse::InvalidParameters.new(:email) if invite.blank?
|
|
invite.resend_invite
|
|
render json: success_json
|
|
rescue RateLimiter::LimitExceeded
|
|
render_json_error(I18n.t("rate_limiter.slow_down"))
|
|
end
|
|
|
|
def resend_all_invites
|
|
if !SiteSetting.allow_email_invites
|
|
return render_json_error(I18n.t("invite.email_invites_disabled"))
|
|
end
|
|
|
|
guardian.ensure_can_resend_all_invites!
|
|
|
|
begin
|
|
RateLimiter.new(
|
|
current_user,
|
|
"bulk-reinvite-per-day",
|
|
1,
|
|
1.day,
|
|
apply_limit_to_staff: true,
|
|
).performed!
|
|
rescue RateLimiter::LimitExceeded
|
|
return render_json_error(I18n.t("rate_limiter.slow_down"))
|
|
end
|
|
|
|
Invite.pending(current_user).where.not(email: nil).find_each { |invite| invite.resend_invite }
|
|
|
|
render json: success_json
|
|
end
|
|
|
|
def upload_csv
|
|
guardian.ensure_can_bulk_invite_to_forum!
|
|
|
|
hijack do
|
|
file = params[:file] || params[:files].first
|
|
|
|
csv_header = nil
|
|
invites = []
|
|
valid_columns = nil
|
|
|
|
CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row|
|
|
# Try to extract a CSV header, if it exists
|
|
if csv_header.nil?
|
|
if row[0] == "email"
|
|
csv_header = row
|
|
valid_columns = Set.new(ALLOWED_BULK_INVITE_COLUMNS + UserField.pluck(:name))
|
|
next
|
|
else
|
|
csv_header = %w[email groups topic_id]
|
|
end
|
|
end
|
|
|
|
if row[0].present?
|
|
invite =
|
|
csv_header
|
|
.zip(row)
|
|
.map
|
|
.to_h
|
|
.filter { |k, v| v.present? && (!valid_columns || valid_columns.include?(k)) }
|
|
invites.push(invite)
|
|
end
|
|
|
|
break if invites.count >= SiteSetting.max_bulk_invites
|
|
end
|
|
|
|
if invites.present?
|
|
custom_error =
|
|
DiscoursePluginRegistry.apply_modifier(:invite_bulk_csv_custom_error, nil, invites)
|
|
|
|
if custom_error.present?
|
|
return(
|
|
render json: failed_json.merge(errors: [custom_error]), status: :unprocessable_entity
|
|
)
|
|
end
|
|
|
|
Jobs.enqueue(:bulk_invite, invites: invites, current_user_id: current_user.id)
|
|
|
|
if invites.count >= SiteSetting.max_bulk_invites
|
|
render json:
|
|
failed_json.merge(
|
|
errors: [
|
|
I18n.t(
|
|
"bulk_invite.max_rows",
|
|
max_bulk_invites: SiteSetting.max_bulk_invites,
|
|
),
|
|
],
|
|
),
|
|
status: :unprocessable_entity
|
|
else
|
|
render json: success_json
|
|
end
|
|
else
|
|
render json: failed_json.merge(errors: [I18n.t("bulk_invite.error")]),
|
|
status: :unprocessable_entity
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def skip_email_param
|
|
!SiteSetting.allow_email_invites || params[:skip_email]
|
|
end
|
|
|
|
def show_invite(invite)
|
|
email = Email.obfuscate(invite.email)
|
|
|
|
# Show email if the user already authenticated their email
|
|
different_external_email = false
|
|
email_verified_by_authentication = false
|
|
|
|
if server_session[:authentication]
|
|
auth_result = Auth::Result.from_session_data(server_session[:authentication], user: nil)
|
|
if invite.email == auth_result.email
|
|
email = invite.email
|
|
email_verified_by_authentication = auth_result.email_valid
|
|
else
|
|
different_external_email = true
|
|
end
|
|
end
|
|
|
|
email_verified_by_link = invite.email_token.present? && params[:t] == invite.email_token
|
|
|
|
email = invite.email if email_verified_by_link
|
|
|
|
hidden_email = email != invite.email
|
|
|
|
if hidden_email || invite.email.nil? || !SiteSetting.use_email_for_username_and_name_suggestions
|
|
username = ""
|
|
else
|
|
username = UserNameSuggester.suggest(invite.email)
|
|
end
|
|
|
|
info = {
|
|
invited_by: UserNameSerializer.new(invite.invited_by, scope: guardian, root: false),
|
|
email: email,
|
|
hidden_email: hidden_email,
|
|
username: username,
|
|
is_invite_link: invite.is_invite_link?,
|
|
email_verified_by_link: email_verified_by_link,
|
|
}
|
|
|
|
info[:different_external_email] = true if different_external_email
|
|
|
|
if (email_verified_by_link || email_verified_by_authentication) &&
|
|
(staged_user = User.where(staged: true).with_email(invite.email).first)
|
|
info[:username] = staged_user.username
|
|
info[:user_fields] = staged_user.user_fields
|
|
end
|
|
|
|
if current_user
|
|
info[:existing_user_id] = current_user.id
|
|
info[:existing_user_can_redeem] = invite.can_be_redeemed_by?(current_user)
|
|
info[:existing_user_can_redeem_error] = existing_user_can_redeem_error(invite)
|
|
info[:email] = current_user.email
|
|
info[:username] = current_user.username
|
|
end
|
|
|
|
server_session["invite-key"] = invite.invite_key
|
|
|
|
respond_to do |format|
|
|
format.html { store_preloaded("invite_info", MultiJson.dump(info)) }
|
|
format.json { render_json_dump(info) }
|
|
end
|
|
end
|
|
|
|
def show_irredeemable_invite(invite)
|
|
flash.now[:error] = if invite.blank?
|
|
I18n.t("invite.not_found", base_url: Discourse.base_url)
|
|
elsif invite.redeemed?
|
|
if invite.is_invite_link?
|
|
I18n.t(
|
|
"invite.not_found_template_link",
|
|
site_name: SiteSetting.title,
|
|
base_url: Discourse.base_url,
|
|
)
|
|
else
|
|
I18n.t(
|
|
"invite.not_found_template",
|
|
site_name: SiteSetting.title,
|
|
base_url: Discourse.base_url,
|
|
)
|
|
end
|
|
elsif invite.expired?
|
|
I18n.t("invite.expired", base_url: Discourse.base_url)
|
|
end
|
|
|
|
render layout: "no_ember"
|
|
end
|
|
|
|
def ensure_invites_allowed
|
|
return if SiteSetting.enable_local_logins
|
|
return if SiteSetting.enable_discourse_connect
|
|
return if Discourse.enabled_auth_providers.present?
|
|
|
|
raise Discourse::NotFound
|
|
end
|
|
|
|
def ensure_new_registrations_allowed
|
|
return if SiteSetting.allow_new_registrations
|
|
|
|
flash[:error] = I18n.t("login.new_registrations_disabled")
|
|
render layout: "no_ember"
|
|
|
|
false
|
|
end
|
|
|
|
def groups_can_see_topic?(groups, topic)
|
|
if topic&.read_restricted_category?
|
|
topic_groups = topic.category.groups
|
|
return false if (groups & topic_groups).blank?
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def post_process_invite(user)
|
|
user.enqueue_welcome_message("welcome_invite") if user.send_welcome_message
|
|
|
|
Group.refresh_automatic_groups!(:admins, :moderators, :staff) if user.staff?
|
|
|
|
if user.has_password?
|
|
if !user.active
|
|
email_token =
|
|
user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup])
|
|
EmailToken.enqueue_signup_email(email_token)
|
|
end
|
|
elsif !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins
|
|
Jobs.enqueue(:invite_password_instructions_email, username: user.username)
|
|
end
|
|
end
|
|
|
|
def create_topic_invite_notifications(invite, user)
|
|
invite.topics.each do |topic|
|
|
if user.guardian.can_see?(topic)
|
|
last_notification =
|
|
user
|
|
.notifications
|
|
.where(notification_type: Notification.types[:invited_to_topic])
|
|
.where(topic_id: topic.id)
|
|
.where(post_number: 1)
|
|
.where("created_at > ?", 1.hour.ago)
|
|
|
|
if !last_notification.exists?
|
|
topic.create_invite_notification!(
|
|
user,
|
|
Notification.types[:invited_to_topic],
|
|
invite.invited_by,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def existing_user_can_redeem_error(invite)
|
|
return if invite.can_be_redeemed_by?(current_user)
|
|
if invite.invited_users.exists?(user: current_user)
|
|
I18n.t("invite.existing_user_already_redemeed")
|
|
else
|
|
I18n.t("invite.existing_user_cannot_redeem")
|
|
end
|
|
end
|
|
end
|