diff --git a/Gemfile b/Gemfile index d7b5bfd7d..20f656985 100644 --- a/Gemfile +++ b/Gemfile @@ -15,7 +15,6 @@ gem 'rack-cors', '~> 0.2.4', :require => 'rack/cors' # authentication gem 'devise', '~> 1.3.1' -gem 'devise_invitable', '0.5.0' gem 'jwt' gem 'oauth2-provider', '0.0.19' diff --git a/Gemfile.lock b/Gemfile.lock index 42943b247..b4b489c8e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -138,9 +138,6 @@ GEM bcrypt-ruby (~> 2.1.2) orm_adapter (~> 0.0.3) warden (~> 1.0.3) - devise_invitable (0.5.0) - devise (~> 1.3.1) - rails (>= 3.0.0, <= 3.2) diff-lcs (1.1.3) em-http-request (1.0.2) addressable (>= 2.2.3) @@ -465,7 +462,6 @@ DEPENDENCIES cucumber-rails (= 1.2.1) database_cleaner (= 0.7.1) devise (~> 1.3.1) - devise_invitable (= 0.5.0) diaspora-client! em-synchrony (= 1.0.0) factory_girl_rails diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index 7b1fafa4d..d433c71ba 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -11,16 +11,24 @@ class AdminsController < ApplicationController end def admin_inviter - user = User.find_by_email params[:idenitifer] + inviter = InvitationCode.default_inviter_or(current_user) + email = params[:identifier] + user = User.find_by_email(email) + unless user - Invitation.create(:service => 'email', :identifier => params[:identifier], :admin => true) - flash[:notice] = "invitation sent to #{params[:identifier]}" + EmailInviter.new(email, inviter).send! + flash[:notice] = "invitation sent to #{email}" else - flash[:notice]= "error sending invite to #{params[:identifier]}" + flash[:notice]= "error sending invite to #{email}" end redirect_to user_search_path, :notice => flash[:notice] end + def add_invites + InvitationCode.find_by_token(params[:invite_code_id]).add_invites! + redirect_to user_search_path + end + def weekly_user_stats @created_users = User.where("username IS NOT NULL") @created_users_by_week = Hash.new{ |h,k| h[k] = [] } diff --git a/app/controllers/invitation_codes_controller.rb b/app/controllers/invitation_codes_controller.rb new file mode 100644 index 000000000..e79b4f868 --- /dev/null +++ b/app/controllers/invitation_codes_controller.rb @@ -0,0 +1,16 @@ +class InvitationCodesController < ApplicationController + before_filter :ensure_valid_invite_code + + rescue_from ActiveRecord::RecordNotFound do + redirect_to root_url, :notice => "That invite code is no longer valid" + end + + def show + sign_out(current_user) if user_signed_in? + redirect_to new_user_registration_path(:invite => {:token => params[:id]}) + end + + def ensure_valid_invite_code + InvitationCode.find_by_token!(params[:id]) + end +end diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb index 1b4561972..2d66b9f6a 100644 --- a/app/controllers/invitations_controller.rb +++ b/app/controllers/invitations_controller.rb @@ -2,12 +2,12 @@ # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. -class InvitationsController < Devise::InvitationsController +require Rails.root.join('lib', 'email_inviter') - before_filter :check_token, :only => [:edit, :email] - before_filter :check_if_invites_open, :only =>[:create] +class InvitationsController < ApplicationController def new + @invite_code = current_user.invitation_code @sent_invitations = current_user.invitations_from_me.includes(:recipient) respond_to do |format| format.html do @@ -16,65 +16,21 @@ class InvitationsController < Devise::InvitationsController end end + # this is for legacy invites. We try to look the person who sent them the + # invite, and use their new invite code + # owe will be removing this eventually + # @depreciated + def edit + user = User.find_by_invitation_token(params[:invitation_token]) + invitation_code = user.ugly_accept_invitation_code + redirect_to invite_code_path(invitation_code) + end + + def create - aspect_id = params[:user].delete(:aspects) - message = params[:user].delete(:invite_messages) - emails = params[:user][:email].to_s.gsub(/\s/, '').split(/, */) - #NOTE should we try and find users by email here? probs - aspect = current_user.aspects.find(aspect_id) - - language = params[:user][:language] - - invites = Invitation.batch_invite(emails, :message => message, :sender => current_user, :aspect => aspect, :service => 'email', :language => language) - - flash[:notice] = extract_messages(invites) - - redirect_to :back - end - - def update - invitation_token = params[:user][:invitation_token] - - if invitation_token.nil? || invitation_token.blank? - redirect_to :back, :error => I18n.t('invitations.check_token.not_found') - return - end - - user = User.find_by_invitation_token!(invitation_token) - - user.accept_invitation!(params[:user]) - - if user.persisted? && user.person && user.person.persisted? - user.seed_aspects - flash[:notice] = I18n.t 'registrations.create.success' - sign_in_and_redirect(:user, user) - else - user.errors.delete(:person) - flash[:error] = user.errors.full_messages.join(", ") - redirect_to accept_user_invitation_path(:invitation_token => params[:user][:invitation_token]) - end - end - - def resend - invitation = current_user.invitations_from_me.where(:id => params[:id]).first - if invitation - Resque.enqueue(Jobs::ResendInvitation, invitation.id) - flash[:notice] = I18n.t('invitations.create.sent') + invitation.recipient.email - end - redirect_to :back - end - - def email - @invs = [] - @resource = User.find_by_invitation_token(params[:invitation_token]) - render 'devise/mailer/invitation_instructions', :layout => false - end - - protected - def check_token - if User.find_by_invitation_token(params[:invitation_token]).nil? - render 'invitations/token_not_found' - end + inviter = EmailInviter.new(params[:email_inviter][:emails], current_user, params[:email_inviter]) + inviter.send! + redirect_to :back, :notice => "Great! Invites were sent off to #{inviter.emails.join(', ')}" end def check_if_invites_open @@ -84,26 +40,4 @@ class InvitationsController < Devise::InvitationsController return end end - - # @param invites [Array] Invitations to be sent. - # @return [String] A full list of success and error messages. - def extract_messages(invites) - success_message = "Invites Successfully Sent to: " - failure_message = "There was a problem with: " - following_message = " already are on Diaspora, so you are now sharing with them." - successes, failures = invites.partition{|x| x.persisted? } - - followings, real_failures = failures.partition{|x| x.errors[:recipient].present? } - - success_message += successes.map{|k| k.identifier }.to_sentence - failure_message += real_failures.map{|k| k.identifier }.to_sentence - following_message += followings.map{|k| k.identifier}.to_sentence - - messages = [] - messages << success_message if successes.present? - messages << failure_message if failures.present? - messages << following_message if followings.present? - - messages.join('\n') - end -end +end \ No newline at end of file diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index bcb7cad5b..e1483aa71 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -3,10 +3,12 @@ # the COPYRIGHT file. class RegistrationsController < Devise::RegistrationsController - before_filter :check_registrations_open! + before_filter :check_registrations_open_or_vaild_invite!, :check_valid_invite! def create @user = User.build(params[:user]) + @user.process_invite_acceptence(invite) if invite.present? + if @user.save flash[:notice] = I18n.t 'registrations.create.success' @user.seed_aspects @@ -14,7 +16,7 @@ class RegistrationsController < Devise::RegistrationsController Rails.logger.info("event=registration status=successful user=#{@user.diaspora_handle}") else @user.errors.delete(:person) - + flash[:error] = @user.errors.full_messages.join(";") Rails.logger.info("event=registration status=failure errors='#{@user.errors.full_messages.join(', ')}'") render :new @@ -26,10 +28,26 @@ class RegistrationsController < Devise::RegistrationsController end private - def check_registrations_open! + def check_valid_invite! + return true unless AppConfig[:registrations_closed] #this sucks + return true if invite && invite.can_be_used? + flash[:error] = t('registrations.invalid_invite') + redirect_to new_user_session_path + end + + def check_registrations_open_or_vaild_invite! + return true if invite.present? if AppConfig[:registrations_closed] flash[:error] = t('registrations.closed') redirect_to new_user_session_path end end + + def invite + if params[:invite].present? + @invite ||= InvitationCode.find_by_token(params[:invite][:token]) + end + end + + helper_method :invite end diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb index 25df66366..5f44071bf 100644 --- a/app/controllers/services_controller.rb +++ b/app/controllers/services_controller.rb @@ -69,54 +69,4 @@ class ServicesController < ApplicationController @service = current_user.services.where(:type => "Services::#{params[:provider].titleize}").first @friends = @service ? @service.finder(:remote => params[:remote]).paginate( :page => params[:page], :per_page => 15) : [] end - - def inviter - @uid = params[:uid] - - if i_id = params[:invitation_id] - invite = Invitation.find(i_id) - invited_user = invite.recipient - else - invite = Invitation.create(:service => params[:provider], :identifier => @uid, :sender => current_user, :aspect => current_user.aspects.find(params[:aspect_id])) - invited_user = invite.attach_recipient! - end - - #to make sure a friend you just invited from facebook shows up as invited - service = current_user.services.where(:type => "Services::Facebook").first - su = ServiceUser.where(:service_id => service.id, :uid => @uid).first - su.attach_local_models - su.save - - respond_to do |format| - format.html{ invite_redirect_url(invite, invited_user, su)} - format.json{ render :json => invite_redirect_json(invite, invited_user, su) } - end - end - - def facebook_message_url(user, facebook_uid) - subject = t('services.inviter.join_me_on_diaspora') - message = < user.invitation_token)} -MSG - "https://www.facebook.com/messages/#{facebook_uid}?msg_prefill=#{message}" - end - - def invite_redirect_json(invite, user, service_user) - if invite.email_like_identifer - {:message => t("invitations.create.sent") + service_user.name } - else - {:url => facebook_message_url(user, service_user.uid)} - end - end - - def invite_redirect_url(invite, user, service_user) - if invite.email_like_identifer - redirect_to(friend_finder_path(:provider => 'facebook'), :notice => "you re-invited #{service_user.name}") - else - redirect_to(facebook_message_url(user, service_user.uid)) - end - end -end +end \ No newline at end of file diff --git a/app/helpers/invitation_codes_helper.rb b/app/helpers/invitation_codes_helper.rb new file mode 100644 index 000000000..83ac8ddd5 --- /dev/null +++ b/app/helpers/invitation_codes_helper.rb @@ -0,0 +1,28 @@ +module InvitationCodesHelper + def invite_welcome_message + if invite.present? + content_tag(:div) do + person_image_link(invite.user.person) + + I18n.translate('invitation_codes.excited', :name => invite.user.name) + end + end + end + + def invite_hidden_tag(invite) + if invite.present? + hidden_field_tag 'invite[token]', invite.token + end + end + + def invite_link(invite_code) + text_field_tag :invite_code, invite_code_url(@invite_code), :readonly => true + end + + def invited_by_message + inviter = current_user.invited_by + if inviter.present? + contact = current_user.contact_for(inviter.person) || Contact.new + render :partial => 'people/add_contact', :locals => {:inviter => inviter.person, :contact => contact} + end + end +end diff --git a/app/helpers/notifier_helper.rb b/app/helpers/notifier_helper.rb index 25da4eb4c..21019cd24 100644 --- a/app/helpers/notifier_helper.rb +++ b/app/helpers/notifier_helper.rb @@ -25,11 +25,10 @@ module NotifierHelper end def invite_email_title - names = @invites.collect{|x| x.sender.person.name}.uniq - if @invites.empty? && names.empty? - "Accept Your Diaspora* invite!" + if @inviter.present? + "#{@inviter.person.name} invited you to Diaspora*" else - "#{names.to_sentence} invited you to Diaspora*" + "Accept Your Diaspora* invite!" end end end diff --git a/app/mailers/notifier.rb b/app/mailers/notifier.rb index f9c4ad044..5b5ea9ee6 100644 --- a/app/mailers/notifier.rb +++ b/app/mailers/notifier.rb @@ -33,6 +33,21 @@ class Notifier < ActionMailer::Base mail(default_opts) end + def invite(email, message, inviter, invitation_code, locale) + @inviter = inviter + @message = message + @locale = locale + @invitation_code = invitation_code + + mail_opts = {:to => email, :from => AppConfig[:smtp_sender_address], + :subject => I18n.t('notifier.invited!'), + :host => AppConfig[:pod_uri].host} + + I18n.with_locale(locale) do + mail(mail_opts) + end + end + def started_sharing(recipient_id, sender_id) send_notification(:started_sharing, recipient_id, sender_id) end diff --git a/app/models/invitation.rb b/app/models/invitation.rb index fc364b16c..d19b462ae 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -22,8 +22,6 @@ class Invitation < ActiveRecord::Base validate :sender_owns_aspect?, :unless => :admin? validates_uniqueness_of :sender_id, :scope => [:identifier, :service], :unless => :admin? - after_create :queue_send! #TODO make this after_commit :queue_saved!, :on => :create - # @note options hash is passed through to [Invitation.new] # @see [Invitation.new] @@ -33,7 +31,7 @@ class Invitation < ActiveRecord::Base # @option opts [Aspect] :aspect # @option opts [String] :service # @return [Array] An array of [Invitation] models - # the valid ones are saved, and the invalid ones are not. + # the valid optsnes are saved, and the invalid ones are not. def self.batch_invite(emails, opts) users_on_pod = User.where(:email => emails, :invitation_token => nil) @@ -66,36 +64,16 @@ class Invitation < ActiveRecord::Base !email_like_identifer end - # Attach a recipient [User] to the [Invitation] unless - # there is one already present. - # - # @return [User] The recipient. - def attach_recipient! - unless self.recipient.present? - self.recipient = User.find_or_create_by_invitation(self) - self.save - end - self.recipient - end - # Find or create user, and send that resultant User an # invitation. # # @return [Invitation] self def send! - self.attach_recipient! - - # Sets an instance variable in User (set by devise invitable) - # This determines whether an email should be sent to the recipient. - recipient.skip_invitation = self.skip_email? - - recipient.invite! - - # Logging the invitation action - log_hash = {:event => :invitation_sent, :to => self[:identifier], :service => self[:service]} - log_hash.merge({:inviter => self.sender.diaspora_handle, :invitier_uid => self.sender.id, :inviter_created_at_unix => self.sender.created_at.to_i}) if self.sender - Rails.logger.info(log_hash) - + if email_like_identifer + EmailInviter.new(self.identifier, sender).send! + else + puts "broken facebook invitation_token" + end self end @@ -135,19 +113,7 @@ class Invitation < ActiveRecord::Base when 'email' self.identifier when 'facebook' - if username = ServiceUser.username_of_service_user_by_uid(self.identifier) - unless username.include?('profile.php?') - "#{username}@facebook.com" - else - nil - end - end - end - end - - def queue_send! - unless self.recipient.present? - Resque.enqueue(Jobs::Mail::InviteUserByEmail, self.id) + false end end diff --git a/app/models/invitation_code.rb b/app/models/invitation_code.rb new file mode 100644 index 000000000..e653648ce --- /dev/null +++ b/app/models/invitation_code.rb @@ -0,0 +1,41 @@ +class InvitationCode < ActiveRecord::Base + belongs_to :user + + validates_presence_of :user + + before_create :generate_token, :set_default_invite_count + + def to_param + token + end + + def can_be_used? + self.count > 0 + end + + def add_invites! + self.update_attributes(:count => self.count+100) + end + + def use! + self.update_attributes(:count => self.count-1) + end + + def generate_token + begin + self.token = ActiveSupport::SecureRandom.hex(6) + end while InvitationCode.exists?(:token => self[:token]) + end + + def self.default_inviter_or(user) + if AppConfig[:admin_account].present? + inviter = User.find_by_username(AppConfig[:admin_account]) + end + inviter ||= user + inviter + end + + def set_default_invite_count + self.count = AppConfig[:invite_count] || 25 + end +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index e35314d50..6d7abfb7c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,12 +8,11 @@ require 'rest-client' class User < ActiveRecord::Base include Encryptor::Private - include Connecting include Querying include SocialActions - devise :invitable, :database_authenticatable, :registerable, + devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :timeoutable, :token_authenticatable, :lockable, :lock_strategy => :none, :unlock_strategy => :none @@ -40,7 +39,10 @@ class User < ActiveRecord::Base has_many :invitations_from_me, :class_name => 'Invitation', :foreign_key => :sender_id has_many :invitations_to_me, :class_name => 'Invitation', :foreign_key => :recipient_id has_many :aspects, :order => 'order_id ASC' + belongs_to :auto_follow_back_aspect, :class_name => 'Aspect' + belongs_to :invited_by, :class_name => 'User' + has_many :aspect_memberships, :through => :aspects has_many :contacts @@ -64,7 +66,6 @@ class User < ActiveRecord::Base before_save :guard_unconfirmed_email, :save_person! - before_create :infer_email_from_invitation_provider attr_accessible :getting_started, :password, @@ -106,32 +107,23 @@ class User < ActiveRecord::Base ConversationVisibility.sum(:unread, :conditions => "person_id = #{self.person.id}") end - # @return [User] - def self.find_by_invitation(invitation) - service = invitation.service - identifier = invitation.identifier - - if service == 'email' - existing_user = User.where(:email => identifier).first - else - existing_user = User.joins(:services).where(:services => {:type => "Services::#{service.titleize}", :uid => identifier}).first + #@deprecated + def ugly_accept_invitation_code + begin + self.invitations_to_me.first.sender.invitation_code + rescue Exception => e + nil end - - if existing_user.nil? - i = Invitation.where(:service => service, :identifier => identifier).first - existing_user = i.recipient if i - end - - existing_user end - # @return [User] - def self.find_or_create_by_invitation(invitation) - if existing_user = self.find_by_invitation(invitation) - existing_user - else - self.create_from_invitation!(invitation) - end + def process_invite_acceptence(invite) + self.invited_by = invite.user + invite.use! + end + + + def invitation_code + InvitationCode.find_or_create_by_user_id(self.id) end def hidden_shareables @@ -389,39 +381,6 @@ class User < ActiveRecord::Base end end - # This method is called when an invited user accepts his invitation - # - # @param [Hash] opts the options to accept the invitation with - # @option opts [String] :username The username the invited user wants. - # @option opts [String] :password - # @option opts [String] :password_confirmation - def accept_invitation!(opts = {}) - log_hash = {:event => :invitation_accepted, :username => opts[:username], :uid => self.id} - log_hash[:inviter] = invitations_to_me.first.sender.diaspora_handle if invitations_to_me.first && invitations_to_me.first.sender - - if self.invited? - self.setup(opts) - self.invitation_token = nil - self.password = opts[:password] - self.password_confirmation = opts[:password_confirmation] - - self.save - return unless self.errors.empty? - - # moved old Invitation#share_with! logic into here, - # but i don't think we want to destroy the invitation - # anymore. we may want to just call self.share_with - invitations_to_me.each do |invitation| - if !invitation.admin? && invitation.sender.share_with(self.person, invitation.aspect) - invitation.destroy - end - end - - log_hash[:status] = "success" - Rails.logger.info(log_hash) - self - end - end ###Helpers############ def self.build(opts = {}) @@ -511,14 +470,6 @@ class User < ActiveRecord::Base self.person end - # Set the User's email to the one they've been invited at, if the user - # is being created via an invitation. - # - # @return [User] - def infer_email_from_invitation_provider - self.email = self.invitation_identifier if self.invitation_service == 'email' - self - end def no_person_with_same_username diaspora_id = "#{self.username}#{User.diaspora_id_host}" diff --git a/app/views/admins/user_search.html.haml b/app/views/admins/user_search.html.haml index ff74732ff..8736398e2 100644 --- a/app/views/admins/user_search.html.haml +++ b/app/views/admins/user_search.html.haml @@ -1,65 +1,47 @@ .span-24 = render :partial => 'admins/admin_bar.haml' -%br -%br +.span-24.prepend-4 -%h3 - = form_tag 'admin_inviter', :method => :get do - email to invite: - = text_field_tag 'identifier' - = submit_tag 'invite' + %h3 + you currently have + = current_user.invitation_code.count + invites left + = link_to "add_invites", add_invites_path(current_user.invitation_code) + + = form_tag 'admin_inviter', :method => :get do + email to invite: + = text_field_tag 'identifier' + = submit_tag 'invite' -%h3 - user search -= form_tag 'user_search', :method => :get do - username: - = text_field_tag 'user[username]', params[:user][:username] + %h3 + user search + = form_tag 'user_search', :method => :get do + username: + = text_field_tag 'user[username]', params[:user][:username] - email: - = text_field_tag 'user[email]', params[:user][:email] + email: + = text_field_tag 'user[email]', params[:user][:email] - invitation identifier: - = text_field_tag 'user[invitation_identifier]', params[:user][:invitation_identifier] - - invitation token: - = text_field_tag 'user[invitation_token]', params[:user][:invitation_token] - = submit_tag 'go' + = submit_tag 'go' -= "#{@users.count} users found" -%br -%br -= for user in @users - = user.inspect + = "#{@users.count} users found" %br - - if user.person - = user.person.inspect + %br + - @users.each do |user| + = user.inspect %br - - if user.person.profile - = user.person.profile.inspect + - if user.person + = user.person.inspect + %br + - if user.person.profile + = user.person.profile.inspect + %br + = "invite token: #{invite_code_url(user.invited_by.invite_code)}" if user.invited_by.present? + = link_to "add_invites", add_invites_path(user.invitation_code) %br - = "invite token: #{accept_invitation_url(user, :invitation_token => user.invitation_token)}" if user.invitation_token - %br - %br - %br -%br -= javascript_include_tag 'apiconsole' -#query - %h3 api console - = text_field_tag :api - = submit_tag 'ping this api', :id => 'api_submit' - - response: - %br - %br - #resp - - -%br - post to Diaspora v1 - - - + %br + %br \ No newline at end of file diff --git a/app/views/invitations/new.html.haml b/app/views/invitations/new.html.haml index 5bfa3e319..448876030 100644 --- a/app/views/invitations/new.html.haml +++ b/app/views/invitations/new.html.haml @@ -8,34 +8,35 @@ .description = t('.if_they_accept_info') %br - .span-7.append-1 + .span-7.append-1.last #email_invitation - = form_for User.new, :url => invitation_path(User) do |invite| + = form_tag new_user_invitation_path do %h4 = t('email') - = invite.text_field :email, :title => t('.comma_seperated_plz'), :placeholder => 'foo@bar.com, max@foo.com...' - %br - - %h4 - = t('.aspect') - = invite.select(:aspects, options_from_collection_for_select(all_aspects, 'id', 'name')) - + = text_field_tag 'email_inviter[emails]' ,nil, :title => t('.comma_seperated_plz'), :placeholder => 'foo@bar.com, max@foo.com...' %br %br %h4 = t('.language') - = invite.select(:language, available_language_options, :selected => current_user.language) + = select_tag('email_inviter[locale]', options_from_collection_for_select(available_language_options, "second", "first", :selected => current_user.language)) %br %br %h4 = t('.personal_message') - = invite.text_area :invite_messages, :rows => 3, :value => t('.check_out_diaspora') + = text_area_tag 'email_inviter[message]',nil, :rows => 3, :value => t('.check_out_diaspora') %p - = invite.submit t('.send_an_invitation') + = submit_tag t('.send_an_invitation') + + .clearfix + .span-7.prepend-3.last + + or paste them this link! + = invite_link(@invite_code) + = "#{@invite_code.count} invites left on this code" %br %br diff --git a/app/views/notifier/invite.html.erb b/app/views/notifier/invite.html.erb new file mode 100644 index 000000000..ead22fce2 --- /dev/null +++ b/app/views/notifier/invite.html.erb @@ -0,0 +1,140 @@ + <%- self.extend NotifierHelper -%> + + <%=invite_email_title %> + +

<%= t('devise.mailer.invitation_instructions.displaying_correctly', :link => link_to(t('devise.mailer.invitation_instructions.view_in'), invite_email_url(:invitation_code => @invitation_code), :style => "color: #3F8FBA; text-decoration: none;")).html_safe %>

+ + + + +
+ + + + + + + + + + + + + + + + + + + <% if @inviter.present? %> + <%= @inviter.inspect %> + <% end %> + + + + + + + + + + + + + + + + + + + + +
+ + Diaspora + +
+ <%= t('devise.mailer.invitation_instructions.finally') %>
+
+ +
+ <%= t('devise.mailer.invitation_instructions.arrived', :strong_diaspora => content_tag(:strong, "DIASPORA*")).html_safe %> +
+
+ <%= link_to(t('devise.mailer.invitation_instructions.sign_up_now').html_safe, invite_code_url(@invitation_code), :style => "color: #3F8FBA; text-decoration: underline; font-weight: bold; font-size: 20px;", :target => "_blank").html_safe %> +
+ 1. <%= t('devise.mailer.invitation_instructions.get_connected') %>
+ + + + + +
+ + + <%= t('devise.mailer.invitation_instructions.get_connected_paragraph', :strong_diaspora => content_tag(:strong, "DIASPORA*")).html_safe %> +
+ +
+
+ 2. <%= t('devise.mailer.invitation_instructions.be_yourself') %>
+ + + + + + +
+ + <%= t('devise.mailer.invitation_instructions.be_yourself_paragraph', :strong_diaspora => content_tag(:strong, "DIASPORA*")).html_safe %> + +
+ +
+ +
+
+ 3. <%= t('devise.mailer.invitation_instructions.have_fun') %>
+ + + + + +
+ + + + <%= t('devise.mailer.invitation_instructions.have_fun_paragraph', :strong_diaspora => content_tag(:strong, "DIASPORA*"), :link => link_to(t('devise.mailer.invitation_instructions.cubbies'), "https://cubbi.es", :style => "color: #3F8FBA; text-decoration: underline; font-weight: bold; font-size: 20px;", :target => "_blank")).html_safe %> + +
+
+ + + + + + +
+ <%= link_to(t('devise.mailer.invitation_instructions.sign_up_now').html_safe, invite_code_url(@invitation_code), :style => "color: #3F8FBA; text-decoration: underline; font-weight: bold; font-size: 20px;", :target => "_blank").html_safe %> +
+
+ + + + + + +
+ <%= t('devise.mailer.invitation_instructions.made_by_people', :strong_diaspora => content_tag(:strong, "DIASPORA*"), :jointeam => link_to(t('devise.mailer.invitation_instructions.join_team'), "https://github.com/diaspora/diaspora/wiki/Become-a-Contributor", :style =>"color: #3F8FBA; text-decoration: underline; font-weight: bold; font-size: 18px;", :target => "_blank"), :helpfund => link_to(t('devise.mailer.invitation_instructions.help_fund'), "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QG4L6VYD8YGPU", :style =>"color: #3F8FBA; text-decoration: underline; font-weight: bold; font-size: 18px;", :target => "_blank")).html_safe %> + +
+
+ <%= t('devise.mailer.invitation_instructions.love') %>
+ <%= t('devise.mailer.invitation_instructions.team_diaspora') %>
+
+ <% if AppConfig[:pod_uri].host.match(/joindiaspora.com/) %> + <%= t('devise.mailer.invitation_instructions.unsubscribe', :link => link_to(t('devise.mailer.invitation_instructions.here'), "http://joindiaspora.us1.list-manage.com/unsubscribe?u=d759919b94f9cdcf39d204f3f&id=7b5ceb2f8b", :style => "color: #3F8FBA; text-decoration: none;")).html_safe %> + <% end %> + <%= t('devise.mailer.invitation_instructions.email_us', :email => link_to(t('devise.mailer.invitation_instructions.email_address'), "mailto:questions@joindiaspora.com", :style => "color: #3F8FBA; text-decoration: none;")).html_safe %> +
+
diff --git a/app/views/notifier/invite.text.erb b/app/views/notifier/invite.text.erb new file mode 100644 index 000000000..947541336 --- /dev/null +++ b/app/views/notifier/invite.text.erb @@ -0,0 +1,12 @@ +Hello! + +You have been invited to join Diaspora*! + +Click this link to get started + +<%= invite_code_url(@invitation_code)%> + + +Love, + +The Diaspora* email robot! \ No newline at end of file diff --git a/app/views/people/_add_contact.html.haml b/app/views/people/_add_contact.html.haml new file mode 100644 index 000000000..7026e5d47 --- /dev/null +++ b/app/views/people/_add_contact.html.haml @@ -0,0 +1,5 @@ +.alert-message.block-message.success + you were invited by + = person_image_link inviter + = person_link inviter + = aspect_membership_dropdown(contact, inviter, false) diff --git a/app/views/registrations/new.html.haml b/app/views/registrations/new.html.haml index b4a27188e..472a5fdaf 100644 --- a/app/views/registrations/new.html.haml +++ b/app/views/registrations/new.html.haml @@ -11,16 +11,19 @@ = t('welcome') %h3.accept_invitation_text = t('.sign_up_message') + - flash.each do |name, msg| %p{:class => "login_#{name}"}= msg = image_tag 'diaspora_collage.png', :style => "margin-left:-50px;" - .span-10 %br %br %br %br + + = invite_welcome_message + = form_for(resource, :as => resource_name, :html => {:class => 'new_user_form'}, :url => registration_path(resource_name), :validate => true) do |f| %fieldset .clearfix @@ -42,6 +45,10 @@ = t('password_confirmation') = f.password_field :password_confirmation, :title => t('registrations.new.enter_password_again') + .clearfix + + = invite_hidden_tag(invite) + .submit_field = f.submit t('registrations.new.create_my_account'), :class => 'in_aspects' diff --git a/app/views/services/_remote_friend.html.haml b/app/views/services/_remote_friend.html.haml index 36e74627e..dd6dfcdcd 100644 --- a/app/views/services/_remote_friend.html.haml +++ b/app/views/services/_remote_friend.html.haml @@ -1,9 +1,6 @@ .stream_element{:id => friend.id} .float-right - - - if friend.already_invited? - = link_to t('.resend'), service_inviter_path(:uid => friend.uid, :provider => 'facebook', :invitation_id => friend.invitation_id), :class => 'button resend' - - elsif friend.on_diaspora? + - if friend.on_diaspora? = aspect_membership_dropdown(contact_proxy(friend), friend.person, 'left') - else = render 'shared/aspect_dropdown', :selected_aspects => contact_proxy(friend).aspects, :person => friend.person, :hang => 'left', :dropdown_class => 'inviter', :service_uid => friend.uid diff --git a/app/views/users/getting_started.haml b/app/views/users/getting_started.haml index 770b3bb36..7347c2cfc 100644 --- a/app/views/users/getting_started.haml +++ b/app/views/users/getting_started.haml @@ -14,6 +14,8 @@ %p.center = t(".community_welcome") + = invited_by_message + .clearfix %br %br diff --git a/config/application.yml.example b/config/application.yml.example index d5e318f27..1de63b89a 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -49,6 +49,12 @@ defaults: &defaults # Set this to true if you want users to invite as many people as they want open_invitations: true + #the 'admin' account for your pod... ie for jd.com, this is diasporahq + admin_account: '' + + #the default amount of invitiations for an invite link + invite_count: 25 + # Set this to true if you don't want your users to follow the diasporahq@joindiaspora.com # account on account creation. The diasporahq account helps users start with some # activity in their stream and get news about Diaspora, but if you don't want your server diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index fc3ebca21..f2db7e70e 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -319,6 +319,8 @@ en: simplicity_explanation: "Diaspora makes sharing clean and easy – and this goes for privacy too. Inherently private, Diaspora doesn’t make you wade through pages of settings and options just to keep your profile secure." learn_about_host: "Learn about how to host your own Diaspora server." + invitation_codes: + excited: "%{name} is excited to see you here." invitations: create: sent: "Invitations have been sent to: " @@ -332,6 +334,7 @@ en: invite_someone_to_join: "Invite someone to join Diaspora!" if_they_accept_info: "if they accept, they will be added to the aspect you invited them." comma_seperated_plz: "You can enter multiple email addresses separated by commas." + check_out_diaspora: "Hey! You should check out Diaspora*" to: "To" personal_message: "Personal message" send_an_invitation: "Send an invitation" @@ -679,6 +682,7 @@ en: update: "Update" cancel_my_account: "Cancel my account" closed: "Signups are closed on this Diaspora pod." + invalid_invite: "The invite link you provided is no longer valid!" requests: manage_aspect_contacts: diff --git a/config/routes.rb b/config/routes.rb index 44e6aa3b7..e22da84f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,9 +3,7 @@ # the COPYRIGHT file. Diaspora::Application.routes.draw do - # Posting and Reading - resources :reshares resources :status_messages, :only => [:new, :create] @@ -33,6 +31,7 @@ Diaspora::Application.routes.draw do get "liked" => "streams#liked", :as => "liked_stream" get "commented" => "streams#commented", :as => "commented_stream" get "aspects" => "streams#aspects", :as => "aspects_stream" + resources :aspects do put :toggle_contact_visibility @@ -97,11 +96,13 @@ Diaspora::Application.routes.draw do devise_for :users, :controllers => {:registrations => "registrations", :password => "devise/passwords", - :sessions => "sessions", - :invitations => "invitations"} do - get 'invitations/resend/:id' => 'invitations#resend', :as => 'invitation_resend' - get 'invitations/email' => 'invitations#email', :as => 'invite_email' - end + :sessions => "sessions"} + + #legacy routes to support old invite routes + get 'users/invitations/accept' => 'invitations#edit' + get 'users/invitations/email' => 'invitations#email', :as => 'invite_email' + get 'users/invitations' => 'invitations#new', :as => 'new_user_invitation' + post 'users/invitations' => 'invitations#create', :as => 'new_user_invitation' get 'login' => redirect('/users/sign_in') @@ -111,6 +112,7 @@ Diaspora::Application.routes.draw do get :weekly_user_stats get :correlations get :stats, :as => 'pod_stats' + get "add_invites/:invite_code_id" => 'admins#add_invites', :as => 'add_invites' end resource :profile, :only => [:edit, :update] @@ -122,7 +124,7 @@ Diaspora::Application.routes.draw do resources :share_visibilities, :only => [:update] resources :blocks, :only => [:create, :destroy] - get 'community_spotlight' => "contacts#spotlight", :as => 'community_spotlight' + get 'i/:id' => 'invitation_codes#show', :as => 'invite_code' get 'people/refresh_search' => "people#refresh_search" resources :people, :except => [:edit, :update] do @@ -189,7 +191,7 @@ Diaspora::Application.routes.draw do end end - + get 'community_spotlight' => "contacts#spotlight", :as => 'community_spotlight' # Mobile site get 'mobile/toggle', :to => 'home#toggle_mobile', :as => 'toggle_mobile' diff --git a/db/migrate/20110105051803_create_import_tables.rb b/db/migrate/20110105051803_create_import_tables.rb index f514f3157..0388d2cef 100644 --- a/db/migrate/20110105051803_create_import_tables.rb +++ b/db/migrate/20110105051803_create_import_tables.rb @@ -166,7 +166,6 @@ class CreateImportTables < ActiveRecord::Migration t.string :language t.string :email t.database_authenticatable - t.invitable t.recoverable t.rememberable t.trackable diff --git a/db/migrate/20111211213438_create_invitation_codes.rb b/db/migrate/20111211213438_create_invitation_codes.rb new file mode 100644 index 000000000..f2bdde858 --- /dev/null +++ b/db/migrate/20111211213438_create_invitation_codes.rb @@ -0,0 +1,15 @@ +class CreateInvitationCodes < ActiveRecord::Migration + def self.up + create_table :invitation_codes do |t| + t.string :token + t.integer :user_id + t.integer :count + + t.timestamps + end + end + + def self.down + drop_table :invitation_codes + end +end diff --git a/db/schema.rb b/db/schema.rb index cc3532834..f662bf314 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -109,6 +109,14 @@ ActiveRecord::Schema.define(:version => 20120301143226) do add_index "conversations", ["author_id"], :name => "conversations_author_id_fk" + create_table "invitation_codes", :force => true do |t| + t.string "token" + t.integer "user_id" + t.integer "count" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "invitations", :force => true do |t| t.text "message" t.integer "sender_id" diff --git a/features/accepts_invitation.feature b/features/accepts_invitation.feature index 98635b492..f10dcc827 100644 --- a/features/accepts_invitation.feature +++ b/features/accepts_invitation.feature @@ -19,7 +19,7 @@ Feature: invitation acceptance Then I should be on the stream page Scenario: accept invitation from user - Given I have been invited by a user + Given I have been invited by bob And I am on my acceptance form page And I fill in the following: | user_username | ohai | @@ -35,11 +35,15 @@ Feature: invitation acceptance And I preemptively confirm the alert And I follow "awesome_button" Then I should be on the stream page + And I log out + And I sign in as "bob@bob.bob" + And I follow "By email" + Then I should see one less invite Scenario: sends an invitation Given a user with email "bob@bob.bob" When I sign in as "bob@bob.bob" And I follow "By email" - And I fill in "user_email" with "alex@example.com" + And I fill in "email_inviter_emails" with "alex@example.com" And I press "Send an invitation" Then I should have 1 Devise email delivery diff --git a/features/invitations.feature b/features/invitations.feature new file mode 100644 index 000000000..d3b92045c --- /dev/null +++ b/features/invitations.feature @@ -0,0 +1,10 @@ +@javascript +Feature: Invitations + + Scenario: Accepting an invitation + When I visit alice's invitation code url + Then I should see the "alice is excited" message + When I fill in the new user form + And I press "Create my account!" + Then I should see the "welcome to diaspora" message + And I should be able to friend Alice diff --git a/features/step_definitions/message_steps.rb b/features/step_definitions/message_steps.rb index dc8d140ad..004acffca 100644 --- a/features/step_definitions/message_steps.rb +++ b/features/step_definitions/message_steps.rb @@ -11,6 +11,7 @@ Then /^I should see the "(.*)" message$/ do |message| I18n.translate('profiles.edit.you_are_nsfw') else raise "muriel, you don't have that message key, add one here" - end + end + page.should have_content(text) -end \ No newline at end of file +end diff --git a/features/step_definitions/user_steps.rb b/features/step_definitions/user_steps.rb index 8fd3f009a..b7c62a60b 100644 --- a/features/step_definitions/user_steps.rb +++ b/features/step_definitions/user_steps.rb @@ -26,15 +26,21 @@ Given /^a nsfw user with email "([^\"]*)"$/ do |email| end Given /^I have been invited by an admin$/ do - i = Invitation.create!(:admin => true, :service => 'email', :identifier => "new_invitee@example.com") - @me = i.attach_recipient! + admin = Factory(:user) + admin.invitation_code + i = EmailInviter.new("new_invitee@example.com", admin) + i.send! end -Given /^I have been invited by a user$/ do - @inviter = Factory(:user) - aspect = @inviter.aspects.create(:name => "Rocket Scientists") - i = Invitation.create!(:aspect => aspect, :sender => @inviter, :service => 'email', :identifier => "new_invitee@example.com", :message =>"Hey, tell me about your rockets!") - @me = i.attach_recipient! +Given /^I have been invited by bob$/ do + @inviter = Factory(:user, :email => 'bob@bob.bob') + @inviter_invite_count = @inviter.invitation_code.count + i = EmailInviter.new("new_invitee@example.com", @inviter) + i.send! +end + +When /^I should see one less invite$/ do + step "I should see \"#{@inviter_invite_count -1} invites left\"" end When /^I click on my name$/ do @@ -167,3 +173,22 @@ When /^I view "([^\"]*)"'s first post$/ do |email| post = user.posts.first visit post_path(post) end + +Given /^I visit alice's invitation code url$/ do + @alice ||= Factory(:user, :username => 'alice', :getting_started => false) + invite_code = InvitationCode.find_or_create_by_user_id(@alice.id) + visit invite_code_path(invite_code) +end + +When /^I fill in the new user form$/ do + step 'I fill in "user_username" with "ohai"' + step 'I fill in "user_email" with "ohai@example.com"' + step 'I fill in "user_password" with "secret"' + step 'I fill in "user_password_confirmation" with "secret"' +end + +And /^I should be able to friend Alice$/ do + alice = User.find_by_username 'alice' + step 'I should see "Add contact"' + step "I should see \"#{alice.name}\"" +end diff --git a/features/support/paths.rb b/features/support/paths.rb index b6eb520df..318d39f7f 100644 --- a/features/support/paths.rb +++ b/features/support/paths.rb @@ -20,7 +20,7 @@ module NavigationHelpers when /^my profile page$/ person_path(@me.person) when /^my acceptance form page$/ - accept_user_invitation_path(:invitation_token => @me.invitation_token) + invite_code_path(InvitationCode.first) when /^the requestors profile$/ person_path(Request.where(:recipient_id => @me.person.id).first.sender) when /^"([^\"]*)"'s page$/ diff --git a/lib/email_inviter.rb b/lib/email_inviter.rb new file mode 100644 index 000000000..298379c81 --- /dev/null +++ b/lib/email_inviter.rb @@ -0,0 +1,30 @@ +class EmailInviter + attr_accessor :emails, :message, :inviter, :locale + + def initialize(emails, inviter, options={}) + self.message = options[:message] + self.locale = options.fetch(:locale, 'en') + self.inviter = inviter + self.emails = emails + end + + def emails=(list) + emails = list.split(%r{[,\s]+}) + emails.reject!{|x| x == inviter.email } unless inviter.nil? + @emails = emails + end + + def invitation_code + @invitation_code ||= inviter.invitation_code + end + + def send! + self.emails.each{ |email| mail(email)} + end + + private + + def mail(email) + Notifier.invite(email, message, inviter, invitation_code, locale).deliver! + end +end \ No newline at end of file diff --git a/lib/rake_helpers.rb b/lib/rake_helpers.rb index d0f2a5a44..f7a075277 100644 --- a/lib/rake_helpers.rb +++ b/lib/rake_helpers.rb @@ -26,10 +26,15 @@ module RakeHelpers possible_invite = Invitation.find_by_identifier(backer_email) possible_user ||= possible_invite.recipient if possible_invite.present? + admin_account = User.find_by_username(AppConfig[:admin_account]) + raise "no admin_account in application.yml" unless admin_account.present? + admin_account.invitation_code.count += num_to_process + admin_account.invitation_code.save + unless possible_user puts "#{n}: sending email to: #{backer_name} #{backer_email}" unless Rails.env == 'test' unless test - i = Invitation.new(:service => 'email', :identifier => backer_email, :admin => true) + i = EmailInviter.new(backer_email) i.send! end else diff --git a/spec/controllers/admins_controller_spec.rb b/spec/controllers/admins_controller_spec.rb index 69299f01f..4f269d419 100644 --- a/spec/controllers/admins_controller_spec.rb +++ b/spec/controllers/admins_controller_spec.rb @@ -73,11 +73,6 @@ describe AdminsController do AppConfig[:admins] = [@user.username] end - it 'succeeds' do - get :admin_inviter, :identifier => 'bob@moms.com' - response.should be_redirect - end - it 'does not die if you do it twice' do get :admin_inviter, :identifier => 'bob@moms.com' get :admin_inviter, :identifier => 'bob@moms.com' @@ -85,7 +80,7 @@ describe AdminsController do end it 'invites a new user' do - Invitation.should_receive(:create) + EmailInviter.should_receive(:new).and_return(stub.as_null_object) get :admin_inviter, :identifier => 'bob@moms.com' response.should redirect_to user_search_path flash.notice.should include("invitation sent") diff --git a/spec/controllers/invitations_controller_spec.rb b/spec/controllers/invitations_controller_spec.rb index be3ca2447..2f551c252 100644 --- a/spec/controllers/invitations_controller_spec.rb +++ b/spec/controllers/invitations_controller_spec.rb @@ -5,16 +5,11 @@ require 'spec_helper' describe InvitationsController do - include Devise::TestHelpers before do AppConfig[:open_invitations] = true @user = alice - @aspect = @user.aspects.first - @invite = {:invite_message=>"test", :aspects=> @aspect.id.to_s, :email=>"abc@example.com"} - - request.env["devise.mapping"] = Devise.mappings[:user] - Webfinger.stub_chain(:new, :fetch).and_return(Factory(:person)) + @invite = {'email_inviter' => {'message' => "test", 'emails' => "abc@example.com"}} end describe "#create" do @@ -24,120 +19,26 @@ describe InvitationsController do request.env["HTTP_REFERER"]= 'http://test.host/cats/foo' end - it 'saves an invitation' do - expect { - post :create, :user => @invite - }.should change(Invitation, :count).by(1) + it 'creates an EmailInviter' do + inviter = stub(:emails => ['mbs@gmail.com'], :send! => true) + EmailInviter.should_receive(:new).with(@invite['email_inviter']['emails'], @user, @invite['email_inviter']). + and_return(inviter) + post :create, @invite end - it 'handles a comma-separated list of emails' do - expect{ - post :create, :user => @invite.merge( - :email => "foofoofoofoo@example.com, mbs@gmail.com") - }.should change(Invitation, :count).by(2) - end - - it 'handles a comma-separated list of emails with whitespace' do - expect { - post :create, :user => @invite.merge( - :email => "foofoofoofoo@example.com , mbs@gmail.com") - }.should change(Invitation, :count).by(2) - end - - it "allows invitations without if invitations are open" do + it "redirects if invitations are closed" do open_bit = AppConfig[:open_invitations] - AppConfig[:open_invitations] = true + AppConfig[:open_invitations] = false - expect{ - post :create, :user => @invite - }.to change(Invitation, :count).by(1) + post :create, @invite + response.should be_redirect AppConfig[:open_invitations] = open_bit end it 'returns to the previous page on success' do - post :create, :user => @invite + post :create, @invite response.should redirect_to("http://test.host/cats/foo") end - - it 'strips out your own email' do - lambda { - post :create, :user => @invite.merge(:email => @user.email) - }.should_not change(Invitation, :count) - - expect{ - post :create, :user => @invite.merge(:email => "hello@example.org, #{@user.email}") - }.should change(Invitation, :count).by(1) - end - end - - describe "#email" do - before do - invites = Invitation.batch_invite(["foo@example.com"], :message => "hi", :sender => @user, :aspect => @user.aspects.first, :service => 'email', :language => "en-US") - invites.first.send! - @invited_user = User.find_by_email("foo@example.com") - end - - it "succeeds" do - get :email, :invitation_token => @invited_user.invitation_token - response.should be_success - end - - it "shows an error if there's no such invitation token" do - get :email, :invitation_token => "12345" - response.should render_template(:token_not_found) - end - end - - describe "#update" do - before do - invite = Factory(:invitation, :sender => @user, :service => 'email', :identifier => "a@a.com") - @invited_user = invite.attach_recipient! - - @accept_params = {:user=> - {:password_confirmation =>"password", - :email => "a@a.com", - :username=>"josh", - :password=>"password", - :invitation_token => @invited_user.invitation_token}} - - end - - context 'success' do - let(:invited) {User.find_by_username(@accept_params[:user][:username])} - - it 'creates a user' do - put :update, @accept_params - invited.should_not be_nil - end - - it 'seeds the aspects' do - put :update, @accept_params - invited.aspects.count.should == 4 - end - - it 'adds a contact' do - lambda { - put :update, @accept_params - }.should change(@user.contacts, :count).by(1) - end - end - - context 'failure' do - before do - @fail_params = @accept_params - @fail_params[:user][:username] = @user.username - end - - it 'stays on the invitation accept form' do - put :update, @fail_params - response.location.include?(accept_user_invitation_path).should be_true - end - - it 'keeps the invitation token' do - put :update, @fail_params - response.location.include?("invitation_token=#{@invited_user.invitation_token}").should be_true - end - end end describe '#new' do @@ -146,43 +47,4 @@ describe InvitationsController do get :new end end - - describe '#resend' do - before do - sign_in :user, @user - @controller.stub!(:current_user).and_return(@user) - request.env["HTTP_REFERER"]= 'http://test.host/cats/foo' - - invite = Factory(:invitation, :sender => @user, :service => 'email', :identifier => "a@a.com") - @invited_user = invite.attach_recipient! - end - - it 'calls resend invitation if one exists' do - @user.reload.invitations_from_me.count.should == 1 - invitation = @user.invitations_from_me.first - Resque.should_receive(:enqueue) - put :resend, :id => invitation.id - end - - it 'does not send an invitation for a different user' do - invitation2 = Factory(:invitation, :sender => bob, :service => 'email', :identifier => "a@a.com") - - Resque.should_not_receive(:enqueue) - put :resend, :id => invitation2.id - end - end - - - describe '#extract_messages' do - before do - sign_in alice - end - it 'displays a message that tells the user how many invites were sent, and which REJECTED' do - post :create, :user => @invite.merge( - :email => "mbs@gmail.com, foo@bar.com, foo.com, lala@foo, cool@bar.com") - flash[:notice].should_not be_blank - flash[:notice].should =~ /foo\.com/ - flash[:notice].should =~ /lala@foo/ - end - end end diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index c75c379a0..f1ff07342 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -39,10 +39,25 @@ describe RegistrationsController do flash[:error].should == I18n.t('registrations.closed') response.should redirect_to new_user_session_path end + + it 'does not redirect if there is a valid invite token' do + i = InvitationCode.create(:user => bob) + get :new, :invite => {:token => i.token} + response.should_not be_redirect + end + + it 'does redirect if there is an invalid invite token' do + get :new, :invite => {:token => 'fssdfsd'} + response.should be_redirect + end end describe "#create" do context "with valid parameters" do + before do + AppConfig[:registrations_closed] = false + end + before do user = Factory.build(:user) User.stub!(:build).and_return(user) diff --git a/spec/controllers/services_controller_spec.rb b/spec/controllers/services_controller_spec.rb index fd11b2150..9017c3959 100644 --- a/spec/controllers/services_controller_spec.rb +++ b/spec/controllers/services_controller_spec.rb @@ -143,51 +143,4 @@ describe ServicesController do Nokogiri(response.body).css('.translation_missing').should be_empty end end - - describe '#inviter' do - before do - @uid = "abc" - fb = Factory(:service, :type => "Services::Facebook", :user => @user) - fb = Services::Facebook.find(fb.id) - @su = Factory(:service_user, :service => fb, :uid => @uid) - @invite_params = {:provider => 'facebook', :uid => @uid, :aspect_id => @user.aspects.first.id} - end - - it 'redirects to a prefilled facebook message url' do - put :inviter, @invite_params - response.location.should match(/https:\/\/www\.facebook\.com\/messages\/.*?msg_prefill=.*/) - end - - it 'creates an invitation' do - lambda { - put :inviter, @invite_params - }.should change(Invitation, :count).by(1) - end - - it 'sets the invitation_id on the service_user' do - post :inviter, @invite_params - @su.reload.invitation.should_not be_nil - end - - it 'does not create a duplicate invitation' do - invited_user = Factory.build(:user, :username =>nil) - invited_user.save(:validate => false) - inv = Invitation.create!(:sender => @user, :recipient => invited_user, :aspect => @user.aspects.first, :identifier => eve.email) - @invite_params[:invitation_id] = inv.id - - lambda { - put :inviter, @invite_params - }.should_not change(Invitation, :count) - end - - it 'disregards the amount of invites if open_invitations are enabled' do - open_bit = AppConfig[:open_invitations] - AppConfig[:open_invitations] = true - - lambda { - put :inviter, @invite_params - }.should change(Invitation, :count).by(1) - AppConfig[:open_invitations] = open_bit - end - end end diff --git a/spec/factories.rb b/spec/factories.rb index 56358bb82..4f13b6ec6 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -130,6 +130,12 @@ FactoryGirl.define do end end + factory :invitation_code do + sequence(:token){|n| "sdfsdsf#{n}"} + association :user + count 0 + end + factory :service do |service| nickname "sirrobertking" type "Services::Twitter" @@ -239,4 +245,4 @@ FactoryGirl.define do end factory(:status, :parent => :status_message) -end \ No newline at end of file +end diff --git a/spec/helpers/invitation_codes_helper_spec.rb b/spec/helpers/invitation_codes_helper_spec.rb new file mode 100644 index 000000000..c2dfae147 --- /dev/null +++ b/spec/helpers/invitation_codes_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the InvitationCodesHelper. For example: +# +# describe InvitationCodesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe InvitationCodesHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/lib/email_inviter_spec.rb b/spec/lib/email_inviter_spec.rb new file mode 100644 index 000000000..3d423d362 --- /dev/null +++ b/spec/lib/email_inviter_spec.rb @@ -0,0 +1,56 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'email_inviter') + +describe EmailInviter do + before do + @user = stub(:invitation_code => 'coolcodebro', :present? => true, + :email => 'foo@bar.com') + @emails = "mbs333@gmail.com, foo1@bar.com maxwell@dude.com" + end + + it 'has a list of emails' do + inviter = EmailInviter.new(@emails, @user) + inviter.emails.should_not be_empty + end + + it 'should parse three emails' do + inviter = EmailInviter.new(@emails, @user) + inviter.emails.count.should == 3 + end + + it 'has an inviter' do + inviter = EmailInviter.new(@emails, @user) + inviter.inviter.should_not be_nil + end + + it 'can have a message' do + message = "you guys suck hard" + inviter = EmailInviter.new("emails", @user, :message => message) + inviter.message.should == message + end + + describe '#emails' do + it 'rejects the inviter email if present' do + inviter = EmailInviter.new(@emails + " #{@user.email}", @user) + inviter.emails.should_not include(@user.email) + end + end + + describe 'language' do + it 'defaults to english' do + inviter = EmailInviter.new(@emails, @user) + inviter.locale.should == 'en' + end + + it 'listens to the langauge option' do + inviter = EmailInviter.new(@emails, @user, :locale => 'es') + inviter.locale.should == 'es' + end + end + + describe '#invitation_code' do + it 'delegates to the user' do + inviter = EmailInviter.new(@emails, @user) + inviter.invitation_code.should == @user.invitation_code + end + end +end \ No newline at end of file diff --git a/spec/lib/rake_helper_spec.rb b/spec/lib/rake_helper_spec.rb index b2b8de3c4..48b617fc7 100644 --- a/spec/lib/rake_helper_spec.rb +++ b/spec/lib/rake_helper_spec.rb @@ -12,20 +12,18 @@ describe RakeHelpers do describe '#process_emails' do before do Devise.mailer.deliveries = [] - end - it 'should send emails to each backer' do - expect{ - process_emails(@csv, 100, 1, false) - }.to change(User, :count).by(3) + @old_admin = AppConfig[:admin_account] + AppConfig[:admin_account] = Factory(:user).username end - it 'should not send the email to the same email twice' do - process_emails(@csv, 100, 1, false) + after do + AppConfig[:admin_account] = @old_admin + end - Devise.mailer.deliveries.count.should == 3 - process_emails(@csv, 100, 1, false) + it 'should send emails to each email' do - Devise.mailer.deliveries.count.should == 3 + EmailInviter.should_receive(:new).exactly(3).times.and_return(stub.as_null_object) + process_emails(@csv, 100, 1, false) end end end diff --git a/spec/models/invitation_code_spec.rb b/spec/models/invitation_code_spec.rb new file mode 100644 index 000000000..18d949f98 --- /dev/null +++ b/spec/models/invitation_code_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe InvitationCode do + it 'has a valid factory' do + Factory(:invitation_code).should be_valid + end + + it 'sets the count to a default value' do + code = Factory(:invitation_code) + code.count.should > 0 + end + + describe '#use!' do + it 'decrements the count of the code' do + code = Factory(:invitation_code) + + expect{ + code.use! + }.to change(code, :count).by(-1) + end + end + + describe '.default_inviter_or' do + before do + @old_account = AppConfig[:admin_account] + AppConfig[:admin_account] = 'bob' + end + + after do + AppConfig[:admin_account] = @old_account + end + + it 'grabs the set admin account for the pod...' do + InvitationCode.default_inviter_or(alice).username.should == 'bob' + end + + it '..or the given user' do + AppConfig[:admin_account] = '' + InvitationCode.default_inviter_or(alice).username.should == 'alice' + end + end +end diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb index ad19d8b59..c379ee819 100644 --- a/spec/models/invitation_spec.rb +++ b/spec/models/invitation_spec.rb @@ -48,44 +48,6 @@ describe Invitation do @invitation.message.should == "!" end - describe 'the invite process' do - before do - end - - it 'works for a new user' do - invite = Invitation.new(:sender => alice, :aspect => alice.aspects.first, :service => 'email', :identifier => 'foo@bar.com', :language => alice.language) - lambda { - invite.save - invite.send! - }.should change(User, :count).by(1) - end - - it 'works for a current user(with the right email)' do - invite = Invitation.create(:sender => alice, :aspect => alice.aspects.first, :service => 'email', :identifier => bob.email, :language => alice.language) - lambda { - invite.send! - }.should_not change(User, :count) - end - - it 'works for a current user(with the same fb id)' do - bob.services << Factory.build(:service, :type => "Services::Facebook") - invite = Invitation.create(:sender => alice, :aspect => alice.aspects.first, :service => 'facebook', :identifier => bob.services.first.uid) - lambda { - invite.send! - }.should_not change(User, :count) - end - end - - describe '#convert_to_admin!' do - it 'reset sender and aspect to nil, and sets admin flag to true' do - invite = Factory(:invitation) - invite.convert_to_admin! - invite.reload - invite.admin?.should be_true - invite.sender_id.should be_nil - invite.aspect_id.should be_nil - end - end describe '.batch_invite' do before do @@ -109,63 +71,4 @@ describe Invitation do end end - - describe 'send' do - before do - @invitation = Factory(:invitation, :sender => alice, :aspect => alice.aspects.first, :service => 'email', :identifier => 'a@a.com' , :language => alice.language) - end - - it 'sends an email' do - lambda { - @invitation.send! - }.should change(Devise.mailer.deliveries, :count).by(1) - end - - it 'sends an email with from header' do - @invitation.send! - Devise.mailer.deliveries.first.from.should_not be_blank - end - - it 'sends an email with from header' do - @invitation.send! - Devise.mailer.deliveries.first.to.should == ["a@a.com"] - end - - context "re-send" do - it 'sends another email' do - lambda { - @invitation.resend - }.should change(Devise.mailer.deliveries, :count).by(1) - end - end - end - - describe '#recipient_identifier' do - it 'calls email if the invitation_service is email' do - email = 'abc@abc.com' - invitation = Factory(:invitation, :sender => alice, :service => 'email', :identifier => email, :aspect => alice.aspects.first, :language => alice.language) - invitation.recipient_identifier.should == email - end - - context 'facebook' do - before do - @uid = '23526464' - @service = "facebook" - alice.services << Services::Facebook.new(:uid => "13234895") - alice.reload.services(true).first.service_users.create(:uid => @uid, :photo_url => 'url', :name => "Remote User") - end - - it 'gets the name if the invitation_service is facebook' do - invitation = Factory(:invitation, :sender => alice, :identifier => @uid, :service => @service, :aspect => alice.aspects.first) - invitation.recipient_identifier.should == "Remote User" - end - - it 'does not error if the facebook user is not recorded' do - invitation = Factory(:invitation, :sender => alice, :identifier => @uid, :service => @service, :aspect => alice.aspects.first) - alice.services.first.service_users.delete_all - invitation.recipient_identifier.should == "A Facebook user" - end - end - end -end - +end \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f4f286622..1738beda5 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -33,28 +33,6 @@ describe User do alice.save end end - - describe '#infer_email_from_invitation_provider' do - it 'sets corresponding email if invitation_service is email' do - addr = '12345@alice.com' - alice.invitation_service = 'email' - alice.invitation_identifier = addr - - lambda { - alice.infer_email_from_invitation_provider - }.should change(alice, :email) - end - - it 'does not set an email if invitation_service is not email' do - addr = '1233123' - alice.invitation_service = 'facebook' - alice.invitation_identifier = addr - - lambda { - alice.infer_email_from_invitation_provider - }.should_not change(alice, :email) - end - end end describe 'hidden_shareables' do @@ -400,70 +378,13 @@ describe User do end end - describe '.find_by_invitation' do - let(:invited_user) { - inv = Factory.build(:invitation, :recipient => @recipient, :service => @type, :identifier => @identifier) - User.find_by_invitation(inv) - } - context 'send a request to an existing' do - before do - @recipient = alice - end - - context 'active user' do - it 'by service' do - @type = 'facebook' - @identifier = '123456' - - @recipient.services << Services::Facebook.new(:uid => @identifier) - @recipient.save - - invited_user.should == @recipient - end - - it 'by email' do - @type = 'email' - @identifier = alice.email - - invited_user.should == @recipient - end - end - - context 'invited user' do - it 'by service and identifier' do - @identifier = alice.email - @type = 'email' - invited_user.should == alice - end - end - - context 'not on server (not yet invited)' do - it 'returns nil' do - @recipient = nil - @identifier = 'foo@bar.com' - @type = 'email' - invited_user.should be_nil - end - end - end - end - - describe '.find_or_create_by_invitation' - - describe '.create_from_invitation!' do - before do - @identifier = 'max@foobar.com' - @inv = Factory.build(:invitation, :admin => true, :service => 'email', :identifier => @identifier) - @user = User.create_from_invitation!(@inv) - end - - it 'creates a persisted user' do - @user.should be_persisted - end - - it 'sets the email if the service is email' do - @user.email.should == @inv.identifier + describe '#process_invite_acceptence' do + it 'sets the inviter on user' do + inv = InvitationCode.create(:user => bob) + user = Factory(:user) + user.process_invite_acceptence(inv) + user.invited_by_id.should == bob.id end end @@ -842,54 +763,6 @@ describe User do end end - describe "#accept_invitation!" do - before do - fantasy_resque do - @invitation = Factory(:invitation, :sender => eve, :identifier => 'invitee@example.org', :aspect => eve.aspects.first) - end - - @invitation.reload - @form_params = { - :invitation_token => "abc", - :email => "a@a.com", - :username => "user", - :password => "secret", - :password_confirmation => "secret", - :person => { - :profile => {:first_name => "Bob", :last_name => "Smith"} - } - } - end - - context 'after invitation acceptance' do - it 'destroys the invitations' do - user = @invitation.recipient.accept_invitation!(@form_params) - user.invitations_to_me.count.should == 0 - end - - it "should create the person with the passed in params" do - lambda { - @invitation.recipient.accept_invitation!(@form_params) - }.should change(Person, :count).by(1) - end - - it 'resolves incoming invitations into contact requests' do - user = @invitation.recipient.accept_invitation!(@form_params) - eve.contacts.where(:person_id => user.person.id).count.should == 1 - end - end - - context 'from an admin' do - it 'should work' do - i = nil - fantasy_resque do - i = Invitation.create!(:admin => true, :service => 'email', :identifier => "new_invitee@example.com") - end - i.reload - i.recipient.accept_invitation!(@form_params) - end - end - end describe '#retract' do before do diff --git a/tmp/.gitkeep b/tmp/.gitkeep deleted file mode 100644 index e69de29bb..000000000