Backend support for group invites

This commit is contained in:
Sam
2014-05-09 11:45:18 +10:00
parent 34d1668f9f
commit 3f07c1d0a1
7 changed files with 87 additions and 79 deletions

View File

@ -202,9 +202,15 @@ class TopicsController < ApplicationController
username_or_email = params[:user] ? fetch_username : fetch_email username_or_email = params[:user] ? fetch_username : fetch_email
topic = Topic.find_by(id: params[:topic_id]) topic = Topic.find_by(id: params[:topic_id])
guardian.ensure_can_invite_to!(topic)
if topic.invite(current_user, username_or_email) if group_ids = params[:group_ids]
group_ids = group_ids.split(",").map(&:to_i)
group_ids = Group.where(id: group_ids).pluck(:id)
end
guardian.ensure_can_invite_to!(topic,group_ids)
if topic.invite(current_user, username_or_email, group_ids)
user = User.find_by_username_or_email(username_or_email) user = User.find_by_username_or_email(username_or_email)
if user if user
render_json_dump BasicUserSerializer.new(user, scope: guardian, root: 'user') render_json_dump BasicUserSerializer.new(user, scope: guardian, root: 'user')

View File

@ -54,31 +54,45 @@ class Invite < ActiveRecord::Base
# 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.
def self.invite_by_email(email, invited_by, topic=nil) def self.invite_by_email(email, invited_by, topic=nil, group_ids=nil)
lower_email = Email.downcase(email) lower_email = Email.downcase(email)
user = User.find_by(email: lower_email)
if user
topic.grant_permission_to_user(lower_email) if topic && topic.private_message?
return nil
end
invite = Invite.with_deleted invite = Invite.with_deleted
.where('invited_by_id = ? and email = ?', invited_by.id, lower_email) .where(email: lower_email, invited_by_id: invited_by.id)
.order('created_at DESC') .order('created_at DESC')
.first .first
if invite && invite.expired? if invite && (invite.expired? || invite.deleted_at)
invite.destroy invite.destroy
invite = nil invite = nil
end end
if invite.blank? if !invite
invite = Invite.create(invited_by: invited_by, email: lower_email) invite = Invite.create!(invited_by: invited_by, email: lower_email)
unless invite.valid? end
topic.grant_permission_to_user(lower_email) if topic.present? && topic.email_already_exists_for?(invite)
return if topic && !invite.topic_invites.pluck(:topic_id).include?(topic.id)
invite.topic_invites.create!(invite_id: invite.id, topic_id: topic.id)
# to correct association
topic.reload
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
end end
# Recover deleted invites if we invite them again
invite.recover! if invite.deleted_at.present?
topic.topic_invites.create(invite_id: invite.id) if topic.present?
Jobs.enqueue(:invite_email, invite_id: invite.id) Jobs.enqueue(:invite_email, invite_id: invite.id)
invite.reload
invite invite
end end

View File

@ -523,7 +523,7 @@ class Topic < ActiveRecord::Base
end end
# Invite a user to the topic by username or email. Returns success/failure # Invite a user to the topic by username or email. Returns success/failure
def invite(invited_by, username_or_email) def invite(invited_by, username_or_email, group_ids=nil)
if private_message? if private_message?
# If the user exists, add them to the topic. # If the user exists, add them to the topic.
user = User.find_by_username_or_email(username_or_email) user = User.find_by_username_or_email(username_or_email)
@ -541,14 +541,14 @@ class Topic < ActiveRecord::Base
if username_or_email =~ /^.+@.+$/ if username_or_email =~ /^.+@.+$/
# NOTE callers expect an invite object if an invite was sent via email # NOTE callers expect an invite object if an invite was sent via email
invite_by_email(invited_by, username_or_email) invite_by_email(invited_by, username_or_email, group_ids)
else else
false false
end end
end end
def invite_by_email(invited_by, email) def invite_by_email(invited_by, email, group_ids=nil)
Invite.invite_by_email(email, invited_by, self) Invite.invite_by_email(email, invited_by, self, group_ids)
end end
def email_already_exists_for?(invite) def email_already_exists_for?(invite)

View File

@ -196,8 +196,10 @@ class Guardian
) )
end end
def can_invite_to?(object) def can_invite_to?(object, group_ids=nil)
can_see?(object) && can_invite_to_forum? can_see?(object) &&
can_invite_to_forum? &&
( group_ids.blank? || is_admin? )
end end
def can_see_private_messages?(user_id) def can_see_private_messages?(user_id)

View File

@ -748,6 +748,24 @@ describe TopicsController do
end end
describe 'invite' do describe 'invite' do
describe "group invites" do
it "works correctly" do
group = Fabricate(:group)
topic = Fabricate(:topic)
admin = log_in(:admin)
xhr :post, :invite, topic_id: topic.id, email: 'hiro@from.heros', group_ids: "#{group.id}"
response.should be_success
invite = Invite.find_by(email: 'hiro@from.heros')
groups = invite.groups.to_a
groups.count.should == 1
groups[0].id.should == group.id
end
end
it "won't allow us to invite toa topic when we're not logged in" do it "won't allow us to invite toa topic when we're not logged in" do
lambda { xhr :post, :invite, topic_id: 1, email: 'jake@adventuretime.ooo' }.should raise_error(Discourse::NotLoggedIn) lambda { xhr :post, :invite, topic_id: 1, email: 'jake@adventuretime.ooo' }.should raise_error(Discourse::NotLoggedIn)
end end
@ -763,7 +781,6 @@ describe TopicsController do
describe 'without permission' do describe 'without permission' do
it "raises an exception when the user doesn't have permission to invite to the topic" do it "raises an exception when the user doesn't have permission to invite to the topic" do
Guardian.any_instance.expects(:can_invite_to?).with(@topic).returns(false)
xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo' xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo'
response.should be_forbidden response.should be_forbidden
end end
@ -771,37 +788,20 @@ describe TopicsController do
describe 'with permission' do describe 'with permission' do
before do let!(:admin) do
Guardian.any_instance.expects(:can_invite_to?).with(@topic).returns(true) log_in :admin
end end
context 'when it returns an invite' do it 'should work as expected' do
before do
Topic.any_instance.expects(:invite_by_email).with(@topic.user, 'jake@adventuretime.ooo').returns(Invite.new)
xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo' xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo'
end
it 'should succeed' do
response.should be_success response.should be_success
end
it 'returns success JSON' do
::JSON.parse(response.body).should == {'success' => 'OK'} ::JSON.parse(response.body).should == {'success' => 'OK'}
end Invite.where(invited_by_id: admin.id).count.should == 1
end end
context 'when it fails and returns nil' do it 'should fail on shoddy email' do
xhr :post, :invite, topic_id: @topic.id, user: 'i_am_not_an_email'
before do
Topic.any_instance.expects(:invite_by_email).with(@topic.user, 'jake@adventuretime.ooo').returns(nil)
xhr :post, :invite, topic_id: @topic.id, user: 'jake@adventuretime.ooo'
end
it 'should succeed' do
response.should_not be_success response.should_not be_success
end
it 'returns success JSON' do
::JSON.parse(response.body).should == {'failed' => 'FAILED'} ::JSON.parse(response.body).should == {'failed' => 'FAILED'}
end end
@ -809,10 +809,6 @@ describe TopicsController do
end end
end
end end
describe 'autoclose' do describe 'autoclose' do

View File

@ -209,18 +209,19 @@ describe Invite do
context 'invited to topics' do context 'invited to topics' do
let!(:topic) { Fabricate(:private_message_topic) } let!(:topic) { Fabricate(:private_message_topic) }
let!(:invite) { topic.invite(topic.user, 'jake@adventuretime.ooo')} let!(:invite) {
topic.invite(topic.user, 'jake@adventuretime.ooo')
}
context 'redeem topic invite' do context 'redeem topic invite' do
let!(:user) { invite.redeem }
it 'adds the user to the topic_users' do it 'adds the user to the topic_users' do
user = invite.redeem
topic.reload
topic.allowed_users.include?(user).should be_true topic.allowed_users.include?(user).should be_true
end
it 'can see the private topic' do
Guardian.new(user).can_see?(topic).should be_true Guardian.new(user).can_see?(topic).should be_true
end end
end end
context 'invited by another user to the same topic' do context 'invited by another user to the same topic' do
@ -229,16 +230,18 @@ describe Invite do
let!(:user) { invite.redeem } let!(:user) { invite.redeem }
it 'adds the user to the topic_users' do it 'adds the user to the topic_users' do
topic.reload
topic.allowed_users.include?(user).should be_true topic.allowed_users.include?(user).should be_true
end end
end end
context 'invited by another user to a different topic' do context 'invited by another user to a different topic' do
let(:coding_horror) { User.find_by(username: "CodingHorror") }
let(:another_topic) { Fabricate(:topic, archetype: "private_message", user: coding_horror) }
let!(:another_invite) { another_topic.invite(coding_horror, 'jake@adventuretime.ooo') } let!(:another_invite) { another_topic.invite(coding_horror, 'jake@adventuretime.ooo') }
let!(:user) { invite.redeem } let!(:user) { invite.redeem }
let(:coding_horror) { User.find_by(username: "CodingHorror") }
let(:another_topic) { Fabricate(:topic, archetype: "private_message", user: coding_horror) }
it 'adds the user to the topic_users of the first topic' do it 'adds the user to the topic_users of the first topic' do
topic.allowed_users.include?(user).should be_true topic.allowed_users.include?(user).should be_true
another_topic.allowed_users.include?(user).should be_true another_topic.allowed_users.include?(user).should be_true
@ -248,12 +251,9 @@ describe Invite do
context 'if they redeem the other invite afterwards' do context 'if they redeem the other invite afterwards' do
before do
@result = another_invite.redeem
end
it 'returns the same user' do it 'returns the same user' do
@result.should == user result = another_invite.redeem
result.should == user
another_invite.reload another_invite.reload
another_invite.should be_redeemed another_invite.should be_redeemed
end end

View File

@ -299,11 +299,6 @@ describe Topic do
context 'invite' do context 'invite' do
it "delegates to topic.invite_by_email when the user doesn't exist, but it's an email" do
topic.expects(:invite_by_email).with(topic.user, 'jake@adventuretime.ooo')
topic.invite(topic.user, 'jake@adventuretime.ooo')
end
context 'existing user' do context 'existing user' do
let(:walter) { Fabricate(:walter_white) } let(:walter) { Fabricate(:walter_white) }
@ -324,19 +319,14 @@ describe Topic do
end end
context 'by email' do context 'by email' do
it 'returns true' do
it 'adds user correctly' do
lambda {
topic.invite(topic.user, walter.email).should be_true topic.invite(topic.user, walter.email).should be_true
end }.should change(Notification, :count)
it 'adds walter to the allowed users' do
topic.invite(topic.user, walter.email)
topic.allowed_users.include?(walter).should be_true topic.allowed_users.include?(walter).should be_true
end end
it 'creates a notification' do
lambda { topic.invite(topic.user, walter.email) }.should change(Notification, :count)
end
end end
end end