FEATURE: generate invite token
This commit is contained in:
parent
727827dc25
commit
4ad07b8c09
|
@ -27,7 +27,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
}.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'model.groupNames', 'model.saving'),
|
}.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'model.groupNames', 'model.saving'),
|
||||||
|
|
||||||
buttonTitle: function() {
|
buttonTitle: function() {
|
||||||
return this.get('model.saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action');
|
return this.get('model.saving') ? 'topic.inviting' : 'topic.invite_reply.action';
|
||||||
}.property('model.saving'),
|
}.property('model.saving'),
|
||||||
|
|
||||||
// We are inviting to a topic if the model isn't the current user.
|
// We are inviting to a topic if the model isn't the current user.
|
||||||
|
@ -36,6 +36,10 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
return this.get('model') !== this.currentUser;
|
return this.get('model') !== this.currentUser;
|
||||||
}.property('model'),
|
}.property('model'),
|
||||||
|
|
||||||
|
invitingToForum: function() {
|
||||||
|
return (!Discourse.SiteSettings.enable_sso && !this.get('invitingToTopic') && !this.get('isMessage'));
|
||||||
|
}.property('invitingToTopic', 'isMessage'),
|
||||||
|
|
||||||
topicId: Ember.computed.alias('model.id'),
|
topicId: Ember.computed.alias('model.id'),
|
||||||
|
|
||||||
// Is Private Topic? (i.e. visible only to specific group members)
|
// Is Private Topic? (i.e. visible only to specific group members)
|
||||||
|
@ -95,14 +99,16 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
},
|
},
|
||||||
|
|
||||||
successMessage: function() {
|
successMessage: function() {
|
||||||
if (this.get('isMessage')) {
|
if (this.get('model.inviteLink')) {
|
||||||
|
return I18n.t('user.invited.generated_link_message', {inviteLink: this.get('model.inviteLink'), invitedEmail: this.get('emailOrUsername')});
|
||||||
|
} else if (this.get('isMessage')) {
|
||||||
return I18n.t('topic.invite_private.success');
|
return I18n.t('topic.invite_private.success');
|
||||||
} else if ( Discourse.Utilities.emailValid(this.get('emailOrUsername')) ) {
|
} else if ( Discourse.Utilities.emailValid(this.get('emailOrUsername')) ) {
|
||||||
return I18n.t('topic.invite_reply.success_email', { emailOrUsername: this.get('emailOrUsername') });
|
return I18n.t('topic.invite_reply.success_email', { emailOrUsername: this.get('emailOrUsername') });
|
||||||
} else {
|
} else {
|
||||||
return I18n.t('topic.invite_reply.success_username');
|
return I18n.t('topic.invite_reply.success_username');
|
||||||
}
|
}
|
||||||
}.property('isMessage', 'emailOrUsername'),
|
}.property('model.inviteLink', 'isMessage', 'emailOrUsername'),
|
||||||
|
|
||||||
errorMessage: function() {
|
errorMessage: function() {
|
||||||
return this.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error');
|
return this.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error');
|
||||||
|
@ -121,7 +127,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
groupNames: null,
|
groupNames: null,
|
||||||
error: false,
|
error: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
finished: false
|
finished: false,
|
||||||
|
inviteLink: null
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -147,6 +154,24 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
this.get('model.details.allowed_users').pushObject(result.user);
|
this.get('model.details.allowed_users').pushObject(result.user);
|
||||||
}
|
}
|
||||||
}).catch(() => model.setProperties({ saving: false, error: true }));
|
}).catch(() => model.setProperties({ saving: false, error: true }));
|
||||||
|
},
|
||||||
|
|
||||||
|
generateInvitelink() {
|
||||||
|
if (this.get('disabled')) { return; }
|
||||||
|
|
||||||
|
const groupNames = this.get('model.groupNames'),
|
||||||
|
userInvitedController = this.get('controllers.user-invited-show'),
|
||||||
|
model = this.get('model');
|
||||||
|
|
||||||
|
model.setProperties({ saving: true, error: false });
|
||||||
|
|
||||||
|
return this.get('model').generateInviteLink(this.get('emailOrUsername').trim(), groupNames).then(result => {
|
||||||
|
model.setProperties({ saving: false, finished: true, inviteLink: result });
|
||||||
|
Invite.findInvitedBy(this.currentUser, userInvitedController.get('filter')).then(invite_model => {
|
||||||
|
userInvitedController.set('model', invite_model);
|
||||||
|
userInvitedController.set('totalInvites', invite_model.invites.length);
|
||||||
|
});
|
||||||
|
}).catch(() => model.setProperties({ saving: false, error: true }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -372,6 +372,13 @@ const User = RestModel.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
generateInviteLink: function(email, groupNames) {
|
||||||
|
return Discourse.ajax('/invites/link', {
|
||||||
|
type: 'POST',
|
||||||
|
data: {email: email, group_names: groupNames}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
updateMutedCategories: function() {
|
updateMutedCategories: function() {
|
||||||
this.set("mutedCategories", Discourse.Category.findByIds(this.muted_category_ids));
|
this.set("mutedCategories", Discourse.Category.findByIds(this.muted_category_ids));
|
||||||
}.observes("muted_category_ids"),
|
}.observes("muted_category_ids"),
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
{{#if model.finished}}
|
{{#if model.finished}}
|
||||||
{{d-button class="btn-primary" action="closeModal" label="close"}}
|
{{d-button class="btn-primary" action="closeModal" label="close"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<button class='btn btn-primary' {{bind-attr disabled="disabled"}} {{action "createInvite"}}>{{fa-icon "user-plus"}}{{buttonTitle}}</button>
|
{{d-button icon="envelope" action="createInvite" class="btn-primary" disabled=disabled label=buttonTitle}}
|
||||||
|
{{#if invitingToForum}}
|
||||||
|
{{d-button icon="link" action="generateInvitelink" class="btn-primary" disabled=disabled label='user.invited.generate_link'}}
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@ class InvitesController < ApplicationController
|
||||||
skip_before_filter :check_xhr, :preload_json
|
skip_before_filter :check_xhr, :preload_json
|
||||||
skip_before_filter :redirect_to_login_if_required
|
skip_before_filter :redirect_to_login_if_required
|
||||||
|
|
||||||
before_filter :ensure_logged_in, only: [:destroy, :create, :resend_invite, :check_csv_chunk, :upload_csv_chunk]
|
before_filter :ensure_logged_in, only: [:destroy, :create, :create_invite_link, :resend_invite, :check_csv_chunk, :upload_csv_chunk]
|
||||||
before_filter :ensure_new_registrations_allowed, only: [:show, :redeem_disposable_invite]
|
before_filter :ensure_new_registrations_allowed, only: [:show, :redeem_disposable_invite]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -48,6 +48,21 @@ class InvitesController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_invite_link
|
||||||
|
params.require(:email)
|
||||||
|
group_ids = Group.lookup_group_ids(params)
|
||||||
|
guardian.ensure_can_invite_to_forum!(group_ids)
|
||||||
|
|
||||||
|
invite_exists = Invite.where(email: params[:email], invited_by_id: current_user.id).first
|
||||||
|
if invite_exists
|
||||||
|
guardian.ensure_can_send_multiple_invites!(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
# generate invite link
|
||||||
|
invite_link = Invite.generate_invite_link(params[:email], current_user, group_ids)
|
||||||
|
render_json_dump(invite_link)
|
||||||
|
end
|
||||||
|
|
||||||
def create_disposable_invite
|
def create_disposable_invite
|
||||||
guardian.ensure_can_create_disposable_invite!(current_user)
|
guardian.ensure_can_create_disposable_invite!(current_user)
|
||||||
params.permit(:username, :email, :quantity, :group_names)
|
params.permit(:username, :email, :quantity, :group_names)
|
||||||
|
|
|
@ -71,6 +71,7 @@ class Invite < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create an invite for a user, supplying an optional topic
|
# Create an invite for a user, supplying an optional topic
|
||||||
#
|
#
|
||||||
# Return the previously existing invite if already exists. Returns nil if the invite can't be created.
|
# Return the previously existing invite if already exists. Returns nil if the invite can't be created.
|
||||||
|
@ -121,6 +122,34 @@ class Invite < ActiveRecord::Base
|
||||||
invite
|
invite
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# generate invite link
|
||||||
|
def self.generate_invite_link(email, invited_by, group_ids=nil)
|
||||||
|
lower_email = Email.downcase(email)
|
||||||
|
|
||||||
|
invite = Invite.with_deleted
|
||||||
|
.where(email: lower_email, invited_by_id: invited_by.id)
|
||||||
|
.order('created_at DESC')
|
||||||
|
.first
|
||||||
|
|
||||||
|
if invite && (invite.expired? || invite.deleted_at)
|
||||||
|
invite.destroy
|
||||||
|
invite = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if !invite
|
||||||
|
invite = Invite.create!(invited_by: invited_by, email: lower_email)
|
||||||
|
end
|
||||||
|
|
||||||
|
if group_ids.present?
|
||||||
|
group_ids = group_ids - invite.invited_groups.pluck(:group_id)
|
||||||
|
group_ids.each do |group_id|
|
||||||
|
invite.invited_groups.create!(group_id: group_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return "#{Discourse.base_url}/invites/#{invite.invite_key}"
|
||||||
|
end
|
||||||
|
|
||||||
# generate invite tokens without email
|
# generate invite tokens without email
|
||||||
def self.generate_disposable_tokens(invited_by, quantity=nil, group_names=nil)
|
def self.generate_disposable_tokens(invited_by, quantity=nil, group_names=nil)
|
||||||
invite_tokens = []
|
invite_tokens = []
|
||||||
|
@ -153,6 +182,7 @@ class Invite < ActiveRecord::Base
|
||||||
|
|
||||||
def self.find_all_invites_from(inviter, offset=0, limit=SiteSetting.invites_per_page)
|
def self.find_all_invites_from(inviter, offset=0, limit=SiteSetting.invites_per_page)
|
||||||
Invite.where(invited_by_id: inviter.id)
|
Invite.where(invited_by_id: inviter.id)
|
||||||
|
.where('invites.email IS NOT NULL')
|
||||||
.includes(:user => :user_stat)
|
.includes(:user => :user_stat)
|
||||||
.order('CASE WHEN invites.user_id IS NOT NULL THEN 0 ELSE 1 END',
|
.order('CASE WHEN invites.user_id IS NOT NULL THEN 0 ELSE 1 END',
|
||||||
'user_stats.time_read DESC',
|
'user_stats.time_read DESC',
|
||||||
|
|
|
@ -625,6 +625,8 @@ en:
|
||||||
days_visited: "Days Visited"
|
days_visited: "Days Visited"
|
||||||
account_age_days: "Account age in days"
|
account_age_days: "Account age in days"
|
||||||
create: "Send an Invite"
|
create: "Send an Invite"
|
||||||
|
generate_link: "Copy Invite Link"
|
||||||
|
generated_link_message: '<p>Invite link generated successfully!</p><p><code><b>%{inviteLink}</b></code></p><p>Invite link is only valid for this email address: <b>%{invitedEmail}</b></p>'
|
||||||
bulk_invite:
|
bulk_invite:
|
||||||
none: "You haven't invited anyone here yet. You can send individual invites, or invite a bunch of people at once by <a href='https://meta.discourse.org/t/send-bulk-invites/16468'>uploading a bulk invite file</a>."
|
none: "You haven't invited anyone here yet. You can send individual invites, or invite a bunch of people at once by <a href='https://meta.discourse.org/t/send-bulk-invites/16468'>uploading a bulk invite file</a>."
|
||||||
text: "Bulk Invite from File"
|
text: "Bulk Invite from File"
|
||||||
|
|
|
@ -498,6 +498,7 @@ Discourse::Application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
post "invites/reinvite" => "invites#resend_invite"
|
post "invites/reinvite" => "invites#resend_invite"
|
||||||
|
post "invites/link" => "invites#create_invite_link"
|
||||||
post "invites/disposable" => "invites#create_disposable_invite"
|
post "invites/disposable" => "invites#create_disposable_invite"
|
||||||
get "invites/redeem/:token" => "invites#redeem_disposable_invite"
|
get "invites/redeem/:token" => "invites#redeem_disposable_invite"
|
||||||
delete "invites" => "invites#destroy"
|
delete "invites" => "invites#destroy"
|
||||||
|
|
|
@ -80,6 +80,49 @@ describe InvitesController do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context '.create_invite_link' do
|
||||||
|
it 'requires you to be logged in' do
|
||||||
|
expect {
|
||||||
|
post :create_invite_link, email: 'jake@adventuretime.ooo'
|
||||||
|
}.to raise_error(Discourse::NotLoggedIn)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'while logged in' do
|
||||||
|
let(:email) { 'jake@adventuretime.ooo' }
|
||||||
|
|
||||||
|
it "fails if you can't invite to the forum" do
|
||||||
|
log_in
|
||||||
|
post :create_invite_link, email: email
|
||||||
|
expect(response).not_to be_success
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails for normal user if invite email already exists" do
|
||||||
|
user = log_in(:trust_level_4)
|
||||||
|
invite = Invite.invite_by_email("invite@example.com", user)
|
||||||
|
invite.reload
|
||||||
|
post :create_invite_link, email: invite.email
|
||||||
|
expect(response).not_to be_success
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows admins to invite to groups" do
|
||||||
|
group = Fabricate(:group)
|
||||||
|
log_in(:admin)
|
||||||
|
post :create_invite_link, email: email, group_names: group.name
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(Invite.find_by(email: email).invited_groups.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows multiple group invite" do
|
||||||
|
group_1 = Fabricate(:group, name: "security")
|
||||||
|
group_2 = Fabricate(:group, name: "support")
|
||||||
|
log_in(:admin)
|
||||||
|
post :create_invite_link, email: email, group_names: "security,support"
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(Invite.find_by(email: email).invited_groups.count).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context '.show' do
|
context '.show' do
|
||||||
|
|
||||||
context 'with an invalid invite id' do
|
context 'with an invalid invite id' do
|
||||||
|
|
Loading…
Reference in New Issue