diff --git a/Gemfile b/Gemfile index 3a8199dd7..baed1cd43 100644 --- a/Gemfile +++ b/Gemfile @@ -97,13 +97,13 @@ group :test, :development do gem 'linecache', '0.43', :platforms => :mri_18 end gem 'launchy' - gem 'jasmine', '1.0.2.1' + gem 'jasmine', '1.1.0.rc3' end group :test do gem 'factory_girl_rails' gem 'fixture_builder', '0.2.2' - gem 'selenium-webdriver', '0.2.2' + gem 'selenium-webdriver', '2.4' gem 'capybara', '~> 0.3.9' gem 'cucumber-rails', '0.3.2' gem 'rspec', '>= 2.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 15d2b5f03..1e8a31b37 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -143,7 +143,7 @@ GEM ohai (>= 0.5.7) rest-client (< 1.7.0, >= 1.0.4) uuidtools - childprocess (0.2.0) + childprocess (0.2.1) ffi (~> 1.0.6) closure-compiler (1.1.1) cloudfiles (1.4.10) @@ -225,11 +225,12 @@ GEM jammit (0.5.4) closure-compiler (>= 0.1.0) yui-compressor (>= 0.9.1) - jasmine (1.0.2.1) - json_pure (>= 1.4.3) + jasmine (1.1.0.rc3) + jasmine-core (>= 1.1.0.rc2) rack (>= 1.1) rspec (>= 1.3.1) selenium-webdriver (>= 0.1.3) + jasmine-core (1.1.0.rc3) json (1.4.6) json_pure (1.5.3) launchy (2.0.3) @@ -399,8 +400,8 @@ GEM rubyntlm (0.1.1) rubyzip (0.9.4) sass (3.1.4) - selenium-webdriver (0.2.2) - childprocess (>= 0.1.9) + selenium-webdriver (2.4.0) + childprocess (>= 0.2.1) ffi (>= 1.0.7) json_pure rubyzip @@ -484,7 +485,7 @@ DEPENDENCIES http_accept_language! i18n-inflector-rails (~> 1.0) jammit (= 0.5.4) - jasmine (= 1.0.2.1) + jasmine (= 1.1.0.rc3) json (= 1.4.6) jwt! launchy @@ -511,7 +512,7 @@ DEPENDENCIES ruby-debug ruby-debug19 sass (= 3.1.4) - selenium-webdriver (= 0.2.2) + selenium-webdriver (= 2.4) settingslogic (= 2.0.6) sod! sqlite3 diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index 5a6256221..fbd1ed0a5 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -8,12 +8,12 @@ class AdminsController < ApplicationController @users = params[:user].empty? ? [] : User.where(params[:user]) end - def admin_inviter - opts = {:service => 'email', :identifier => params[:identifier]} - existing_user = Invitation.find_existing_user('email', params[:identifier]) - opts.merge!(:existing_user => existing_user) if existing_user - Invitation.create_invitee(opts) - flash[:notice] = "invitation sent to #{params[:identifier]}" + def admin_inviter + user = User.find_by_email params[:idenitifer] + unless user + Invitation.create(:service => 'email', :identifer => params[:identifier], :admin => true) + flash[:notice] = "invitation sent to #{params[:identifier]}" + end redirect_to user_search_path end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index b111c5736..0272ccd73 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -57,7 +57,7 @@ class CommentsController < ApplicationController def index @post = current_user.find_visible_post_by_id(params[:post_id]) if @post - @comments = @post.comments.includes(:author => :profile) + @comments = @post.comments.includes(:author => :profile).order('created_at ASC') render :layout => false else raise ActiveRecord::RecordNotFound.new diff --git a/app/controllers/conversations_controller.rb b/app/controllers/conversations_controller.rb index 80d963caa..93342958f 100644 --- a/app/controllers/conversations_controller.rb +++ b/app/controllers/conversations_controller.rb @@ -31,15 +31,17 @@ class ConversationsController < ApplicationController message_text = params[:conversation].delete(:text) params[:conversation][:messages_attributes] = [ {:author => current_user.person, :text => message_text }] - if @conversation = Conversation.create(params[:conversation]) + @conversation = Conversation.new(params[:conversation]) + if @conversation.save Postzord::Dispatch.new(current_user, @conversation).post - flash[:notice] = I18n.t('conversations.create.sent') - if params[:profile] - redirect_to person_path(params[:profile]) - else - redirect_to conversations_path(:conversation_id => @conversation.id) - end + else + flash[:error] = I18n.t('conversations.create.fail') + end + if params[:profile] + redirect_to person_path(params[:profile]) + else + redirect_to conversations_path(:conversation_id => @conversation.id) end end diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb index be2993777..e29490341 100644 --- a/app/controllers/invitations_controller.rb +++ b/app/controllers/invitations_controller.rb @@ -5,6 +5,7 @@ class InvitationsController < Devise::InvitationsController before_filter :check_token, :only => [:edit] + before_filter :check_if_invites_open, :only =>[:create] def new @sent_invitations = current_user.invitations_from_me.includes(:recipient) @@ -16,63 +17,36 @@ class InvitationsController < Devise::InvitationsController end def create - unless AppConfig[:open_invitations] - flash[:error] = I18n.t 'invitations.create.no_more' - redirect_to :back - return - end - aspect = params[:user].delete(:aspects) + 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) - good_emails, bad_emails = emails.partition{|e| e.try(:match, Devise.email_regexp)} + invites = Invitation.batch_invite(emails, :sender => current_user, :aspect => aspect, :service => 'email') - if good_emails.include?(current_user.email) - if good_emails.length == 1 - flash[:error] = I18n.t 'invitations.create.own_address' - redirect_to :back - return - else - bad_emails.push(current_user.email) - good_emails.delete(current_user.email) - end - end - - good_emails.each{|e| Resque.enqueue(Job::Mail::InviteUserByEmail, current_user.id, e, aspect, message)} - - if bad_emails.any? - flash[:error] = I18n.t('invitations.create.sent') + good_emails.join(', ') + " "+ I18n.t('invitations.create.rejected') + bad_emails.join(', ') - else - flash[:notice] = I18n.t('invitations.create.sent') + good_emails.join(', ') - end + flash[:notice] = extract_messages(invites) redirect_to :back end def update - begin invitation_token = params[:user][:invitation_token] + if invitation_token.nil? || invitation_token.blank? raise I18n.t('invitations.check_token.not_found') end - user = User.find_by_invitation_token(params[:user][:invitation_token]) + + user = User.find_by_invitation_token!(invitation_token) + user.accept_invitation!(params[:user]) - user.seed_aspects - rescue Exception => e #What exception is this trying to rescue? If it is ActiveRecord::NotFound, we should say so. - raise e unless e.respond_to?(:record) - user = nil - record = e.record - record.errors.delete(:person) - flash[:error] = record.errors.full_messages.join(", ") - end - - if user - flash[:notice] = I18n.t 'registrations.create.success' - sign_in_and_redirect(:user, 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 - redirect_to accept_user_invitation_path( - :invitation_token => params[:user][:invitation_token]) + redirect_to accept_user_invitation_path(:invitation_token => params[:user][:invitation_token]), :error => user.errors.full_messages.join(", ") end end @@ -90,12 +64,37 @@ class InvitationsController < Devise::InvitationsController @resource = User.find_by_invitation_token(params[:invitation_token]) render 'devise/mailer/invitation_instructions', :layout => false end - protected + protected def check_token if User.find_by_invitation_token(params[:invitation_token]).nil? flash[:error] = I18n.t 'invitations.check_token.not_found' redirect_to root_url end end + + def check_if_invites_open + unless AppConfig[:open_invitations] + flash[:error] = I18n.t 'invitations.create.no_more' + redirect_to :back + 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: " + successes, failures = invites.partition{|x| x.persisted? } + + success_message += successes.map{|k| k.identifier }.join(', ') + failure_message += failures.map{|k| k.identifier }.join(', ') + + messages = [] + messages << success_message if successes.present? + messages << failure_message if failures.present? + + messages.join('\n') + end end diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 97f21556c..c6f656166 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -19,11 +19,10 @@ class MessagesController < ApplicationController if message.save Rails.logger.info("event=create type=comment user=#{current_user.diaspora_handle} status=success message=#{message.id} chars=#{params[:message][:text].length}") Postzord::Dispatch.new(current_user, message).post - - redirect_to conversations_path(:conversation_id => cnv.id) else - render :nothing => true, :status => 422 + flash[:error] = I18n.t('conversations.new_message.fail') end + redirect_to conversations_path(:conversation_id => cnv.id) else render :nothing => true, :status => 422 end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 62d2c4d67..b665a6c37 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -33,7 +33,7 @@ class NotificationsController < VannaController end notifications.each do |n| n[:actors] = n.actors - n[:translation] = object_link(n, n.actors.map { |a| person_link(a) }) + n[:translation] = notification_message_for(n) n[:translation_key] = n.popup_translation_key n[:target] = n.translation_key == "notifications.mentioned" ? n.target.post : n.target end diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb index c9a332399..9c3386944 100644 --- a/app/controllers/services_controller.rb +++ b/app/controllers/services_controller.rb @@ -59,7 +59,8 @@ class ServicesController < ApplicationController if i_id = params[:invitation_id] invited_user = Invitation.find(i_id).recipient else - invited_user = current_user.invite_user(params[:aspect_id], params[:provider], @uid) + 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 @subject = t('services.inviter.join_me_on_diaspora') diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index bd288fee8..3f69849e2 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -60,7 +60,7 @@ module NotificationsHelper number_of_actors = actors.count sentence_translations = {:two_words_connector => " #{t('notifications.index.and')} ", :last_word_connector => ", #{t('notifications.index.and')} " } actor_links = actors.collect{ |person| - person_link(person, :class => 'hovercardable', :what => 'thefuck' ) + person_link(person, :class => 'hovercardable') } if number_of_actors < 4 diff --git a/app/models/invitation.rb b/app/models/invitation.rb index 04c29b4b7..344bc7d04 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2010, Diaspora Inc. This file is +# Copyright (c) 2011, Diaspora Inc. This file is # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. @@ -8,121 +8,145 @@ class Invitation < ActiveRecord::Base belongs_to :recipient, :class_name => 'User' belongs_to :aspect - validates_presence_of :sender, - :recipient, - :aspect + attr_accessible :sender, :recipient, :aspect, :service, :identifier, :admin - # @param opts [Hash] Takes :identifier, :service, :idenfitier, :from, :message - # @return [User] - def self.invite(opts={}) - opts[:identifier].downcase! if opts[:identifier] - # return if the current user is trying to invite himself via email - return false if opts[:identifier] == opts[:from].email + before_validation :set_email_as_default_service - if existing_user = self.find_existing_user(opts[:service], opts[:identifier]) - # If the sender of the invitation is already connected to the person - # he is inviting, raise an error. - if opts[:from].contact_for(opts[:from].person) - raise "You are already connceted to this person" + validates_presence_of :identifier, :service + validate :valid_identifier? + validates_presence_of :sender, :aspect, :unless => :admin? + validate :ensure_not_inviting_self, :on => :create, :unless => :admin? + validate :sender_owns_aspect?, :unless => :admin? + validates_uniqueness_of :sender_id, :scope => [:identifier, :service], :unless => :admin? - # Check whether or not the existing User has already been invited; - # and if so, start sharing with the Person. - elsif not existing_user.invited? - opts[:from].share_with(existing_user.person, opts[:into]) - return + after_create :queue_send! #TODO make this after_commit :queue_saved!, :on => :create - # If the sender has already invited the recipient, raise an error. - elsif Invitation.where(:sender_id => opts[:from].id, :recipient_id => existing_user.id).first - raise "You already invited this person" - # When everything checks out, we merge the existing user into the - # options hash to pass on to self.create_invitee. - else - opts.merge(:existing_user => existing_user) - end + # @note options hash is passed through to [Invitation.new] + # @see [Invitation.new] + # + # @param [Array] emails + # @option opts [User] :sender + # @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. + def self.batch_invite(emails, opts) + + users_on_pod = User.where(:email => emails, :invitation_token => nil) + + #share with anyone whose email you entered who is on the pod + emails = emails - users_on_pod.map{|u| u.email} + users_on_pod.each{|u| opts[:sender].share_with(u.person, opts[:aspect])} + + emails.map! do |e| + Invitation.create(opts.merge(:identifier => e)) end - - create_invitee(opts) + emails end - # @param service [String] String representation of the service invitation provider (i.e. facebook, email) - # @param identifier [String] String representation of the reciepients identity on the provider (i.e. 'bob.smith', bob@aol.com) - # @return [User] - def self.find_existing_user(service, identifier) - unless existing_user = User.where(:invitation_service => service, - :invitation_identifier => identifier).first - 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 - end - end - - existing_user + # Downcases the incoming service identifier and assigns it + # + # @param ident [String] Service identifier + # @see super + def identifier=(ident) + ident.downcase! if ident + super end - # @params opts [Hash] Takes :from, :existing_user, :service, :identifier, :message - # @return [User] - def self.create_invitee(opts={}) - invitee = opts[:existing_user] - invitee ||= User.new(:invitation_service => opts[:service], :invitation_identifier => opts[:identifier]) + # Determine if we want to skip emailing the recipient. + # + # @return [Boolean] + # @return [void] + def skip_email? + self.service != 'email' + end - # (dan) I'm not sure why, but we need to call .valid? on our User. - invitee.valid? - - # Return a User immediately if an invalid email is passed in - return invitee if opts[:service] == 'email' && !opts[:identifier].match(Devise.email_regexp) - - if invitee.new_record? - invitee.errors.clear - invitee.serialized_private_key = User.generate_key if invitee.serialized_private_key.blank? - invitee.send(:generate_invitation_token) - elsif invitee.invitation_token.nil? - return invitee + # 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 - # Logic if there is an explicit sender - if opts[:from] - invitee.save(:validate => false) - Invitation.create!(:sender => opts[:from], - :recipient => invitee, - :aspect => opts[:into], - :message => opts[:message]) - invitee.reload - end - invitee.skip_invitation = (opts[:service] != 'email') - invitee.invite! + # 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 => opts[:identifier], :service => opts[:service]} - log_hash.merge({:inviter => opts[:from].diaspora_handle, :invitier_uid => opts[:from].id, :inviter_created_at_unix => opts[:from].created_at.to_i}) if opts[:from] + 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) - invitee + self end + # @return [Invitation] self def resend - recipient.invite! - end - - # @return Contact - def share_with! - if contact = sender.share_with(recipient.person, aspect) - self.destroy - end - contact + self.send! end # @return [String] def recipient_identifier - if recipient.invitation_service == 'email' - recipient.invitation_identifier - elsif recipient.invitation_service == 'facebook' - if su = ServiceUser.where(:uid => recipient.invitation_identifier).first + case self.service + when 'email' + self.identifier + when'facebook' + if su = ServiceUser.where(:uid => self.identifier).first su.name else I18n.t('invitations.a_facebook_user') end end end + + def queue_send! + unless self.recipient.present? + Resque.enqueue(Job::Mail::InviteUserByEmail, self.id) + end + end + + # @note before_save + def set_email_as_default_service + self.service ||= 'email' + end + + # @note Validation + def ensure_not_inviting_self + if self.identifier == self.sender.email + errors[:base] << 'You can not invite yourself.' + end + end + + # @note Validation + def sender_owns_aspect? + if self.sender_id != self.aspect.user_id + errors[:base] << 'You do not own that aspect.' + end + end + + + # @note Validation + def valid_identifier? + return false unless self.identifier + if self.service == 'email' + unless self.identifier.match(Devise.email_regexp) + errors[:base] << 'invalid email' + end + end + end end diff --git a/app/models/job/http_multi.rb b/app/models/job/http_multi.rb index 1972eb8b1..5bdeac98a 100644 --- a/app/models/job/http_multi.rb +++ b/app/models/job/http_multi.rb @@ -49,7 +49,7 @@ module Job end unless response.success? pod = Pod.find_or_create_by_url(response.effective_url) - log_line = "event=http_multi_fail sender_id=#{user_id} recipient_id=#{person.id} url=#{response.effective_url} response_code='#{response.code}' xml='#{Base64.decode64(enc_object_xml)}'" + log_line = "event=http_multi_fail sender_id=#{user_id} recipient_id=#{person.id} url=#{response.effective_url} response_code='#{response.code}'" Rails.logger.info(log_line) pod.pod_stats.create(:error_message => log_line, :person_id => person.id, :error_code => response.code.to_i) failed_request_people << person.id diff --git a/app/models/job/mail/invite_user_by_email.rb b/app/models/job/mail/invite_user_by_email.rb index 6e4275f60..2864798e4 100644 --- a/app/models/job/mail/invite_user_by_email.rb +++ b/app/models/job/mail/invite_user_by_email.rb @@ -7,9 +7,9 @@ module Job module Mail class InviteUserByEmail < Base @queue = :mail - def self.perform(sender_id, email, aspect_id, invite_message) - user = User.find(sender_id) - user.invite_user(aspect_id, 'email', email, invite_message) + def self.perform(invite_id) + invite = Invitation.find(invite_id) + invite.send! end end end diff --git a/app/models/job/receive_local_batch.rb b/app/models/job/receive_local_batch.rb index ceca8f807..cc42d1cfa 100644 --- a/app/models/job/receive_local_batch.rb +++ b/app/models/job/receive_local_batch.rb @@ -14,12 +14,23 @@ module Job socket_to_users(post, recipient_user_ids) if post.respond_to?(:socket_to_user) notify_mentioned_users(post) end + def self.create_visibilities(post, recipient_user_ids) contacts = Contact.where(:user_id => recipient_user_ids, :person_id => post.author_id) - new_post_visibilities = contacts.map do |contact| - PostVisibility.new(:contact_id => contact.id, :post_id => post.id) + + if postgres? + # Take the naive approach to inserting our new visibilities for now. + contacts.each do |contact| + PostVisibility.find_or_create_by_contact_id_and_post_id(contact.id, post.id) + end + else + # Use a batch insert on mySQL. + new_post_visibilities = contacts.map do |contact| + PostVisibility.new(:contact_id => contact.id, :post_id => post.id) + end + PostVisibility.import(new_post_visibilities) end - PostVisibility.import new_post_visibilities + end def self.socket_to_users(post, recipient_user_ids) recipient_user_ids.each do |id| diff --git a/app/models/job/resend_invitation.rb b/app/models/job/resend_invitation.rb index 12cf073b4..c4d2d79a8 100644 --- a/app/models/job/resend_invitation.rb +++ b/app/models/job/resend_invitation.rb @@ -7,7 +7,7 @@ module Job class ResendInvitation < Base @queue = :mail def self.perform(invitation_id) - inv = Invitation.where(:id => invitation_id).first + inv = Invitation.find(invitation_id) inv.resend end end diff --git a/app/models/message.rb b/app/models/message.rb index fb46d91e4..4d13d7807 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -14,6 +14,8 @@ class Message < ActiveRecord::Base belongs_to :author, :class_name => 'Person' belongs_to :conversation, :touch => true + validates_presence_of :text + after_create do #sign comment as commenter self.author_signature = self.sign_with_key(self.author.owner.encryption_key) if self.author.owner diff --git a/app/models/person.rb b/app/models/person.rb index 84f9b41e0..cbae71017 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -19,6 +19,7 @@ class Person < ActiveRecord::Base has_one :profile, :dependent => :destroy delegate :last_name, :to => :profile + accepts_nested_attributes_for :profile before_validation :downcase_diaspora_handle def downcase_diaspora_handle @@ -51,7 +52,21 @@ class Person < ActiveRecord::Base AppConfig[:featured_users].present? ? Person.where(:diaspora_handle => AppConfig[:featured_users]) : [] end - + # Set a default of an empty profile when a new Person record is instantiated. + # Passing :profile => nil to Person.new will instantiate a person with no profile. + # Calling Person.new with a block: + # Person.new do |p| + # p.profile = nil + # end + # will not work! The nil profile will be overriden with an empty one. + def initialize(params={}) + profile_set = params.has_key?(:profile) || params.has_key?("profile") + params[:profile_attributes] = params.delete(:profile) if params.has_key?(:profile) && params[:profile].is_a?(Hash) + super + self.profile ||= Profile.new unless profile_set + end + + def self.find_from_id_or_username(params) p = if params[:id].present? Person.where(:id => params[:id]).first @@ -67,18 +82,12 @@ class Person < ActiveRecord::Base def self.search_query_string(query) query = query.downcase + like_operator = postgres? ? "ILIKE" : "LIKE" - if postgres? - where_clause = <<-SQL - profiles.full_name ILIKE ? OR - profiles.diaspora_handle ILIKE ? - SQL - else - where_clause = <<-SQL - profiles.full_name LIKE ? OR - people.diaspora_handle LIKE ? - SQL - end + where_clause = <<-SQL + profiles.full_name #{like_operator} ? OR + people.diaspora_handle #{like_operator} ? + SQL q_tokens = query.to_s.strip.gsub(/(\s|$|^)/) { "%#{$1}" } [where_clause, [q_tokens, q_tokens]] diff --git a/app/models/service_user.rb b/app/models/service_user.rb index a721ae505..7157d26c1 100644 --- a/app/models/service_user.rb +++ b/app/models/service_user.rb @@ -30,8 +30,8 @@ class ServiceUser < ActiveRecord::Base self.contact = self.service.user.contact_for(self.person) end - self.invitation = Invitation.joins(:recipient).where(:sender_id => self.service.user_id, - :users => {:invitation_service => self.service.provider, - :invitation_identifier => self.uid}).first + self.invitation = Invitation.where(:sender_id => self.service.user_id, + :service => self.service.provider, + :identifier => self.uid).first end end diff --git a/app/models/services/facebook.rb b/app/models/services/facebook.rb index 3a208039a..8cac6b75f 100644 --- a/app/models/services/facebook.rb +++ b/app/models/services/facebook.rb @@ -42,11 +42,31 @@ class Services::Facebook < Service su.attach_local_models su } - ServiceUser.import(data, :on_duplicate_key_update => [:updated_at, :contact_id, :person_id, :request_id, :invitation_id, :photo_url, :name, :username]) + + + if postgres? + # Take the naive approach to inserting our new visibilities for now. + data.each do |su| + if existing = ServiceUser.find_by_uid(su.uid) + update_hash = OVERRIDE_FIELDS_ON_FB_UPDATE.inject({}) do |acc, element| + acc[element] = su.send(element) + acc + end + + existing.update_attributes(update_hash) + else + su.save + end + end + else + ServiceUser.import(data, :on_duplicate_key_update => OVERRIDE_FIELDS_ON_FB_UPDATE + [:updated_at]) + end end private + OVERRIDE_FIELDS_ON_FB_UPDATE = [:contact_id, :person_id, :request_id, :invitation_id, :photo_url, :name, :username] + def prevent_service_users_from_being_empty if self.service_users.blank? self.save_friends diff --git a/app/models/user.rb b/app/models/user.rb index 9206d0f77..733de5159 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -59,24 +59,46 @@ class User < ActiveRecord::Base :invitation_service, :invitation_identifier - # Sometimes we access the person in a strange way and need to do this - # @note we should make this method depricated. - # - # @return [Person] - def save_person! - self.person.save if self.person && self.person.changed? - self.person + + # @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 + end + + if existing_user.nil? + i = Invitation.where(:service => service, :identifier => identifier).first + existing_user = i.recipient if i + end + + existing_user 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 + 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 end + def self.create_from_invitation!(invitation) + user = User.new + user.generate_keys + user.send(:generate_invitation_token) + user.email = invitation.identifier if invitation.service == 'email' + # we need to make a custom validator here to make this safer + user.save(:validate => false) + user + end + + def update_user_preferences(pref_hash) if self.disable_mail UserPreference::VALID_EMAIL_TYPES.each{|x| self.user_preferences.find_or_create_by_email_type(x)} @@ -291,19 +313,6 @@ class User < ActiveRecord::Base end end - ###Invitations############ - def invite_user(aspect_id, service, identifier, invite_message = "") - if aspect = aspects.find(aspect_id) - Invitation.invite(:service => service, - :identifier => identifier, - :from => self, - :into => aspect, - :message => invite_message) - else - false - end - end - # This method is called when an invited user accepts his invitation # # @param [Hash] opts the options to accept the invitation with @@ -312,25 +321,29 @@ class User < ActiveRecord::Base # @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 - begin - if self.invited? - self.setup(opts) - self.invitation_token = nil - self.password = opts[:password] - self.password_confirmation = opts[:password_confirmation] - self.save! - invitations_to_me.each{|invitation| invitation.share_with!} - log_hash[:status] = "success" - Rails.logger.info log_hash + log_hash[:inviter] = invitations_to_me.first.sender.diaspora_handle if invitations_to_me.first && invitations_to_me.first.sender - self.reload # Because to_request adds a request and saves elsewhere - self + 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 - rescue Exception => e - log_hash[:status] = "failure" - Rails.logger.info log_hash - raise e + + log_hash[:status] = "success" + Rails.logger.info(log_hash) + self end end @@ -344,28 +357,22 @@ class User < ActiveRecord::Base def setup(opts) self.username = opts[:username] self.email = opts[:email] + self.language ||= 'en' self.valid? errors = self.errors errors.delete :person return if errors.size > 0 - - opts[:person] ||= {} - unless opts[:person][:profile].is_a?(Profile) - opts[:person][:profile] ||= Profile.new - opts[:person][:profile] = Profile.new(opts[:person][:profile]) - end - - self.person = Person.new(opts[:person]) - self.person.diaspora_handle = "#{opts[:username]}@#{AppConfig[:pod_uri].authority}" - self.person.url = AppConfig[:pod_url] - - - self.serialized_private_key = User.generate_key if self.serialized_private_key.blank? - self.person.serialized_public_key = OpenSSL::PKey::RSA.new(self.serialized_private_key).public_key - + self.set_person(Person.new(opts[:person] || {} )) + self.generate_keys self end + def set_person(person) + person.url = AppConfig[:pod_url] + person.diaspora_handle = "#{self.username}@#{AppConfig[:pod_uri].authority}" + self.person = person + end + def seed_aspects self.aspects.create(:name => I18n.t('aspects.seed.family')) self.aspects.create(:name => I18n.t('aspects.seed.friends')) @@ -379,10 +386,6 @@ class User < ActiveRecord::Base aq end - def self.generate_key - key_size = (Rails.env == 'test' ? 512 : 4096) - OpenSSL::PKey::RSA::generate(key_size) - end def encryption_key OpenSSL::PKey::RSA.new(serialized_private_key) @@ -433,4 +436,34 @@ class User < ActiveRecord::Base i += 1 end end + + + # Generate public/private keys for User and associated Person + def generate_keys + key_size = (Rails.env == 'test' ? 512 : 4096) + + self.serialized_private_key = OpenSSL::PKey::RSA::generate(key_size) if self.serialized_private_key.blank? + + if self.person && self.person.serialized_public_key.blank? + self.person.serialized_public_key = OpenSSL::PKey::RSA.new(self.serialized_private_key).public_key + end + end + + # Sometimes we access the person in a strange way and need to do this + # @note we should make this method depricated. + # + # @return [Person] + def save_person! + self.person.save if self.person && self.person.changed? + 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 end diff --git a/app/views/devise/mailer/invitation_instructions.haml b/app/views/devise/mailer/invitation_instructions.haml index fc24f2d92..b945d898f 100644 --- a/app/views/devise/mailer/invitation_instructions.haml +++ b/app/views/devise/mailer/invitation_instructions.haml @@ -1,5 +1,5 @@ - @invs = @resource.invitations_to_me --if @invs.count > 0 +-unless @invs.first.admin? !!! %html %head diff --git a/config/locales/devise/devise.el.yml b/config/locales/devise/devise.el.yml index abdfe4f8d..583e81369 100644 --- a/config/locales/devise/devise.el.yml +++ b/config/locales/devise/devise.el.yml @@ -34,7 +34,7 @@ el: no_account_till: "Ο λογαριασμός σας δεν θα δημιουργηθεί μέχρι να μεταβείτε στον παρακάτω σύνδεσμο και συνδεθείτε. " subject: "Έχετε προσκληθεί να συμμετάσχετε στο Diaspora!" inviters: - accept_at: ", στο %{url}, μπορείτε να το αποδεχτείτε, μέσω του επόμενου συνδέσμου." + accept_at: "στο %{url}, μπορείτε να το αποδεχτείτε, μέσω του παρακάτω συνδέσμου." has_invited_you: "Ο χρήστης %{name} σας προσκάλεσε να γίνετε μέλος του Diaspora" have_invited_you: "Ο χρήστης %{names} σας κάλεσε να γίνετε μέλος του Diaspora" reset_password_instructions: diff --git a/config/locales/devise/devise.pt-PT.yml b/config/locales/devise/devise.pt-PT.yml index 2ab7b3e40..1a3032c52 100644 --- a/config/locales/devise/devise.pt-PT.yml +++ b/config/locales/devise/devise.pt-PT.yml @@ -34,7 +34,7 @@ pt-PT: no_account_till: "A sua conta não será criada até que aceda à hiperligação acima e se registe." subject: "Foi convidado para se juntar ao Diaspora!" inviters: - accept_at: ", em %{url}, pode aceitar através da seguinte hiperligação:" + accept_at: "em %{url}, pode aceitar através da seguinte hiperligação:" has_invited_you: "%{name} convidou-o para se juntar ao Diaspora" have_invited_you: "%{names} convidaram-no para se juntar ao Diaspora" reset_password_instructions: diff --git a/config/locales/diaspora/el.yml b/config/locales/diaspora/el.yml index 8cf3c38b6..465f2be7f 100644 --- a/config/locales/diaspora/el.yml +++ b/config/locales/diaspora/el.yml @@ -37,7 +37,7 @@ el: person: invalid: "είναι άκυρη." username: - invalid: "is invalid. We only allow letters, numbers, and underscores" + invalid: "μη έγκυρο. Επιτρέπονται μόνο γράμματα, νούμερα και \"κάτω παύλες\" (_)" taken: "είναι ήδη σε χρήση." ago: "%{time} πριν" all_aspects: "Όλες οι πτυχές" @@ -253,7 +253,7 @@ el: tagline_first_half: "Μοιραστείτε ό,τι θέλετε," tagline_second_half: "με όποιον θέλετε." invitations: - a_facebook_user: "A Facebook user" + a_facebook_user: "Ένας χρήστης του Facebook" check_token: not_found: "Το σύμβολο της πρόσκλησης δεν βρέθηκε" create: @@ -392,7 +392,7 @@ el: other: "Οι χρήστες %{actors} άρχισαν να διαμοιράζονται μαζί σας." zero: "Οι χρήστες %{actors} άρχισαν να διαμοιράζονται μαζί σας." notifier: - a_post_you_shared: "a post." + a_post_you_shared: "μια δημοσίευση." click_here: "πατήστε εδώ" comment_on_post: reply: "Απαντήστε ή δείτε τη δημοσίευση του χρήστη %{name} >" @@ -473,7 +473,7 @@ el: fail: "Λυπούμαστε, δεν ήταν δυνατή η εύρεση του %{handle}." zero: "κανένα άτομο" photos: - comment_email_subject: "%{name}'s photo" + comment_email_subject: "φωτογραφία του χρήστη %{name}" create: integrity_error: "Η μεταφόρτωση της φωτογραφίας απέτυχε. Είστε σίγουρος/η ότι ήταν φωτογραφία;" runtime_error: "Η μεταφόρτωση της φωτογραφίας απέτυχε. Είστε σίγουρος/η πως φορέσατε ζώνη ασφαλείας πριν ξεκινήσετε;" @@ -501,7 +501,7 @@ el: edit: "Επεξεργασία" edit_delete_photo: "Επεξεργασία περιγραφής φωτογραφίας / διαγραφή φωτογραφίας" make_profile_photo: "ορισμός ως φωτογραφίας προφίλ" - show_original_post: "Show original post" + show_original_post: "Προβολή αρχικής δημοσίευσης" update_photo: "Ενημέρωση Φωτογραφίας" update: error: "Αποτυχία επεξεργασίας φωτογραφίας." @@ -577,7 +577,7 @@ el: new_request_to_person: sent: "εστάλη!" reshares: - comment_email_subject: "%{resharer}'s reshare of %{author}'s post" + comment_email_subject: "κοινοποίηση της δημοσίευσης του %{author} από τον %{resharer}" create: failure: "Υπήρξε κάποιο σφάλμα κατά την κοινοποίηση αυτής της δημοσίευσης." reshare: @@ -600,8 +600,8 @@ el: failure: error: "εμφανίστηκε ένα σφάλμα κατά τη σύνδεση με αυτή την υπηρεσία" finder: - no_friends: "No Facebook friends found." - service_friends: "%{service} Friends" + no_friends: "Δεν βρέθηκαν φίλοι στο Facebook. " + service_friends: "%{service} Φίλοι" index: connect_to_facebook: "Σύνδεση με Facebook" connect_to_tumblr: "Σύνδεση με το Tumblr" @@ -616,7 +616,7 @@ el: join_me_on_diaspora: "Συνδεθείτε μαζί μου στο DIASPORA*" remote_friend: invite: "πρόσκληση" - not_on_diaspora: "Not yet on Diaspora" + not_on_diaspora: "Όχι ακόμα στο Diaspora" resend: "αποστολή ξανά" settings: "Ρυθμίσεις" shared: @@ -746,11 +746,11 @@ el: your_email: "Το email σας" your_handle: "Το αναγνωριστικό σας στο diaspora" getting_started: - aspects: "aspects" + aspects: "πτυχές" connect_to: "Συνδεθείτε με" connect_to_your_other_social_networks: "Συνδεθείτε με άλλα κοινωνικά δίκτυα" connect_with_people: "Συνδεθείτε με ενδιαφέροντα άτομα" - connect_with_people_explanation_pt1: "Connect with people by placing them into one or more of your" + connect_with_people_explanation_pt1: "Συνδεθείτε με άτομα τοποθετώντας τα σε μια ή περισσότερες από τις " connect_with_people_explanation_pt2: "Aspects are an intuitive way to group new and familar faces, private to you, allowing you to filter down or share with subsets of your contacts easily." featured_tags: "Αξιόλογες ετικέτες" featured_users: "Αξιόλογοι χρήστες" diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 0f09acb6b..700fb2585 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -249,6 +249,9 @@ en: other: "%{count} new messages" create: sent: "Message sent" + fail: "Invalid message" + new_message: + fail: "Invalid message" destroy: success: "Conversation successfully removed" diff --git a/config/locales/diaspora/pt-PT.yml b/config/locales/diaspora/pt-PT.yml index 40d992571..e1f3e697f 100644 --- a/config/locales/diaspora/pt-PT.yml +++ b/config/locales/diaspora/pt-PT.yml @@ -108,7 +108,7 @@ pt-PT: content_2: "Give it to anyone and they'll be able to find you on Diaspora." heading: "Diaspora ID" donate: "Donate" - handle_explanation: "Este é o seu endereço do Diaspora. Tal como um endereço de email, pode dá-lo a quem deseja que o contacte." + handle_explanation: "Esta é a sua identificação do Diaspora. Tal como um endereço de email, pode dá-la a quem deseja que o contacte." keep_us_running: "Keep %{pod} running fast, buy our servers their monthly coffee fix!" no_contacts: "Não há contactos" no_tags: "No tags" @@ -134,7 +134,7 @@ pt-PT: try_adding_some_more_contacts: "You can search (top) or invite (right) more contacts." you_should_add_some_more_contacts: "You should add some more contacts!" no_posts_message: - start_talking: "Ainda ninguém disse nada. Dê início à conversa!" + start_talking: "Ainda ninguém disse nada!" one: "1 aspecto" other: "%{count} aspectos" seed: @@ -207,7 +207,7 @@ pt-PT: many: "%{count} novas mensagens" one: "1 nova mensagem" other: "%{count} novas mensagens" - zero: "não há novas mensagens" + zero: "Não há novas mensagens" index: create_a_new_message: "criar uma nova mensagem" inbox: "Caixa de Entrada" @@ -267,7 +267,7 @@ pt-PT: accept_your_invitation: "Accept your invitation" your_account_awaits: "Your account awaits!" new: - already_invited: "Já foi convidado" + already_invited: "As pessoas seguintes não aceitaram o seu convite:" aspect: "Aspecto" comma_seperated_plz: "Pode inserir vários endereços de email separados por vírgulas." if_they_accept_info: "se aceitarem, serão adicionados ao aspecto para o qual os convidou." @@ -343,7 +343,7 @@ pt-PT: many: "%{count} novas notificações" one: "1 nova notificação" other: "%{count} novas notificações" - zero: "não há novas notificações" + zero: "Não há novas notificações" index: and: "e" and_others: @@ -531,8 +531,8 @@ pt-PT: your_photo: "A sua fotografia" your_private_profile: "O seu perfil privado" your_public_profile: "O seu perfil público" - your_tags: "Você: em 5 #tags" - your_tags_placeholder: "p.ex. #diaspora #varrer #gatos #música" + your_tags: "Descreva-se em 5 palavras" + your_tags_placeholder: "como #filmes #gatos #viagens #professor #novaiorque" update: failed: "Falhou ao actualizar o perfil" updated: "Perfil actualizado" @@ -550,7 +550,7 @@ pt-PT: update: "Actualizar" new: create_my_account: "Create my account" - enter_email: "Introduza um endereço de e-mail" + enter_email: "Introduza um endereço de email" enter_password: "Introduza uma palavra-passe" enter_password_again: "Introduza de novo a mesma palavra-passe" enter_username: "Escolha um nome de utilizador (apenas letras, números e underscores)" @@ -622,8 +622,8 @@ pt-PT: shared: add_contact: add_new_contact: "Add a new contact" - create_request: "Encontrar através do endereço do Diaspora" - diaspora_handle: "diaspora@handle.org" + create_request: "Encontrar através da identificação do Diaspora" + diaspora_handle: "diaspora@pod.org" enter_a_diaspora_username: "Introduza um nome de utilizador do Diaspora:" know_email: "Sabe os seus endereços de email? Deveria convidá-los" your_diaspora_username_is: "O seu nome de utilizador do Diaspora é: %{diaspora_handle}" @@ -633,7 +633,7 @@ pt-PT: logged_in_as: "iniciou sessão como %{name}" your_aspects: "os seus aspectos" invitations: - by_email: "Por Email" + by_email: "Por email" dont_have_now: "Não tem nenhum agora, mas terá mais convites brevemente!" from_facebook: "do Facebook" invitations_left: "ainda tem %{count}" @@ -660,7 +660,7 @@ pt-PT: share: "Partilhar" share_with: "partilhar com" upload_photos: "Upload photos" - whats_on_your_mind: "em que está a pensar?" + whats_on_your_mind: "Em que está a pensar?" reshare: reshare: "Voltar a partilhar" stream_element: @@ -686,7 +686,7 @@ pt-PT: other: "por favor não utilize mais de %{count} caracteres ao escrever as suas mensagens de estado" zero: "por favor não utilize mais de %{count} caracteres ao escrever as suas mensagens de estado" stream_helper: - hide_comments: "ocultar comentários" + hide_comments: "Ocultar todos comentários" show_more_comments: "Show %{number} more comments" tag_followings: create: @@ -727,8 +727,8 @@ pt-PT: also_commented: "...alguém também comenta na publicação do seu contacto?" change: "Alterar" change_email: "Change E-Mail" - change_language: "Mudar de Idioma" - change_password: "Alterar a Palavra-passe" + change_language: "Mudar de idioma" + change_password: "Alterar a palavra-passe" close_account: "Encerrar a Conta" comment_on_post: "...alguém comenta as suas publicações?" current_password: "Palavra-passe actual" @@ -739,12 +739,12 @@ pt-PT: export_data: "Exportar Dados" liked: "...alguém gosta da sua publicação?" mentioned: "...é mencionado numa publicação?" - new_password: "Nova Palavra-passe" + new_password: "Nova palavra-passe" private_message: "...recebe uma mensagem privada?" receive_email_notifications: "Receber notificações por email quando..." started_sharing: "...alguém começa a partilhar consigo?" your_email: "O seu endereço de email" - your_handle: "O seu endereço do Diaspora" + your_handle: "A sua identificação do Diaspora" getting_started: aspects: "aspects" connect_to: "Connect to" @@ -757,7 +757,7 @@ pt-PT: fill_out_your_profile: "Fill out your profile" find_friends: "Find friends" find_friends_from_facebook: "find friends from Facebook" - finished: "Terminado!" + finished: "Terminado" follow_your_interests: "Follow your interests" hashtag_explanation: "Hashtags allow you to talk about and follow your interests. They're also a great way to find new people on Diaspora." profile_description: "Make it easier for people to find you by filling out your profile information." @@ -770,17 +770,17 @@ pt-PT: photo: "Photo" tags: "Tags" see_all_featured_users: "See all featured users" - welcome: "Bem-vindo(a) ao Diaspora!" + welcome: "Bem-vindo(a)!" welcome_with_name: "Welcome, %{name}!" public: does_not_exist: "O utilizador %{username} não existe!" update: email_notifications_changed: "As notificações por email foram alteradas" - language_changed: "O Idioma Foi Alterado" - language_not_changed: "Falha ao Alterar o Idioma" + language_changed: "O idioma foi alterado" + language_not_changed: "Falha ao alterar o idioma" password_changed: "A palavra-passe foi alterada. Já pode iniciar sessão com a sua nova palavra-passe." password_not_changed: "Falhou ao alterar a palavra-passe" - unconfirmed_email_changed: "E-Mail Changed. Needs activation." + unconfirmed_email_changed: "O endereço de email foi alterado. É necessária activação." unconfirmed_email_not_changed: "E-Mail Change Failed" webfinger: fetch_failed: "erro a obter perfil webfinger profile para %{profile_url}" diff --git a/config/locales/diaspora/zh-TW.yml b/config/locales/diaspora/zh-TW.yml index 7554b7b05..c6c59fb2a 100644 --- a/config/locales/diaspora/zh-TW.yml +++ b/config/locales/diaspora/zh-TW.yml @@ -750,8 +750,8 @@ zh-TW: connect_to: "連結至" connect_to_your_other_social_networks: "與其他社交網站連結" connect_with_people: "與酷咖連結" - connect_with_people_explanation_pt1: "Connect with people by placing them into one or more of your" - connect_with_people_explanation_pt2: "Aspects are an intuitive way to group new and familar faces, private to you, allowing you to filter down or share with subsets of your contacts easily." + connect_with_people_explanation_pt1: "為了和別人聯繫, 將他們放入你的任何一個或多個" + connect_with_people_explanation_pt2: "面向是區分新人或熟人的直覺方式, 並且只有你自己知道, 讓你可以輕易過濾他們, 或只和部份聯絡人分享." featured_tags: "特色標籤" featured_users: "特色使用者" fill_out_your_profile: "填寫個人檔案" diff --git a/config/locales/javascript/javascript.el.yml b/config/locales/javascript/javascript.el.yml index 18a9be648..88c7cf51a 100644 --- a/config/locales/javascript/javascript.el.yml +++ b/config/locales/javascript/javascript.el.yml @@ -23,8 +23,8 @@ el: no_more: "Δεν υπάρχουν άλλες δημοσιεύσεις." publisher: at_least_one_aspect: "Πρέπει να κάνετε δημοσίευση σε τουλάχιστον μια πτυχή" - limited: "Limited - your post will only be seen by people you are sharing with" - public: "Public - your post will be visible to everyone and found by search engines" + limited: "Περιορισμένο - οι δημοσιεύσεις σας θα είναι ορατές μόνο από τα άτομα με τα οποία διαμοιράζεστε. " + public: "Δημόσιο - οι δημοσιεύσεις σας θα είναι ορατές στον καθένα και θα μπορούν να βρεθούν από τις μηχανές αναζήτησης." reshares: duplicate: "Έχετε ήδη κοινοποιήσει αυτή τη δημοσίευση!" search_for: "Αναζήτηση για {{name}}" diff --git a/config/locales/javascript/javascript.pt-PT.yml b/config/locales/javascript/javascript.pt-PT.yml index 194ec6034..27a26e0cf 100644 --- a/config/locales/javascript/javascript.pt-PT.yml +++ b/config/locales/javascript/javascript.pt-PT.yml @@ -23,10 +23,10 @@ pt-PT: no_more: "Não há mais publicações." publisher: at_least_one_aspect: "Tem de publicar para pelo menos um aspecto" - limited: "Limited - your post will only be seen by people you are sharing with" - public: "Public - your post will be visible to everyone and found by search engines" + limited: "Limitado - a sua publicação só será vista pelas pessoas com as quais a está partilhar" + public: "Público - A sua publicação vai ser visível para todos e encontrada por motores de busca" reshares: - duplicate: "You've already reshared that post!" + duplicate: "Já republicou essa publicação!" search_for: "Procurar por {{name}}" show_more: "mostrar mais" timeago: diff --git a/db/migrate/20110707234802_likes_on_comments.rb b/db/migrate/20110707234802_likes_on_comments.rb index 78a2fa272..67b039332 100644 --- a/db/migrate/20110707234802_likes_on_comments.rb +++ b/db/migrate/20110707234802_likes_on_comments.rb @@ -18,10 +18,12 @@ SQL SQL #There are some duplicate likes. - keeper_likes = Like.group(:target_id, :author_id, :target_type).having('COUNT(*) > 1') - keeper_likes.each do |like| - l = Like.arel_table - Like.where(:target_id => like.target_id, :author_id => like.author_id, :target_type => like.target_type).where(l[:id].not_eq(like.id)).delete_all + if Like.count > 0 + keeper_likes = Like.group(:target_id, :author_id, :target_type).having('COUNT(*) > 1') + keeper_likes.each do |like| + l = Like.arel_table + Like.where(:target_id => like.target_id, :author_id => like.author_id, :target_type => like.target_type).where(l[:id].not_eq(like.id)).delete_all + end end add_index :likes, [:target_id, :author_id, :target_type], :unique => true end diff --git a/db/migrate/20110729045734_add_full_name_to_profile.rb b/db/migrate/20110729045734_add_full_name_to_profile.rb index 047eed869..6096b3447 100644 --- a/db/migrate/20110729045734_add_full_name_to_profile.rb +++ b/db/migrate/20110729045734_add_full_name_to_profile.rb @@ -1,4 +1,5 @@ class AddFullNameToProfile < ActiveRecord::Migration + class Profile < ActiveRecord::Base; end def self.up add_column :profiles, :full_name, :string, :limit => 70 @@ -8,7 +9,13 @@ class AddFullNameToProfile < ActiveRecord::Migration remove_index :profiles, [:first_name, :searchable] remove_index :profiles, [:last_name, :searchable] - execute("UPDATE profiles SET full_name=LOWER(CONCAT(first_name, ' ', last_name))") + if Profile.count > 0 + if postgres? + execute("UPDATE profiles SET full_name=LOWER(first_name || ' ' || last_name)") + else + execute("UPDATE profiles SET full_name=LOWER(CONCAT(first_name, ' ', last_name))") + end + end end def self.down diff --git a/db/migrate/20110816061820_add_fields_to_invitations.rb b/db/migrate/20110816061820_add_fields_to_invitations.rb new file mode 100644 index 000000000..f8005ef0c --- /dev/null +++ b/db/migrate/20110816061820_add_fields_to_invitations.rb @@ -0,0 +1,17 @@ +class AddFieldsToInvitations < ActiveRecord::Migration + def self.up + add_column :invitations, :service, :string + add_column :invitations, :identifier, :string + add_column :invitations, :admin, :boolean, :default => false + change_column :invitations, :recipient_id, :integer, :null => true + change_column :invitations, :sender_id, :integer, :null => true + end + + def self.down + remove_column :invitations, :service + remove_column :invitations, :identifier + remove_column :invitations, :admin + change_column :invitations, :recipient_id, :integer, :null => false + change_column :invitations, :sender_id, :integer, :null => false + end +end diff --git a/db/schema.rb b/db/schema.rb index 0f485397a..b94110f5f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110815210933) do +ActiveRecord::Schema.define(:version => 20110816061820) do create_table "aspect_memberships", :force => true do |t| t.integer "aspect_id", :null => false @@ -99,11 +99,14 @@ ActiveRecord::Schema.define(:version => 20110815210933) do create_table "invitations", :force => true do |t| t.text "message" - t.integer "sender_id", :null => false - t.integer "recipient_id", :null => false + t.integer "sender_id" + t.integer "recipient_id" t.integer "aspect_id" t.datetime "created_at" t.datetime "updated_at" + t.string "service" + t.string "identifier" + t.boolean "admin", :default => false end add_index "invitations", ["aspect_id"], :name => "index_invitations_on_aspect_id" diff --git a/db/seeds.rb b/db/seeds.rb index 53e29fd9a..1800ec806 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -15,9 +15,9 @@ require 'factory_girl_rails' require File.join(File.dirname(__FILE__), "..", "spec", "helper_methods") include HelperMethods -alice = Factory(:user_with_aspect, :username => "alice", :password => 'evankorth', :invites => 10) -bob = Factory(:user_with_aspect, :username => "bob", :password => 'evankorth', :invites => 10) -eve = Factory(:user_with_aspect, :username => "eve", :password => 'evankorth', :invites => 10) +alice = Factory(:user_with_aspect, :username => "alice", :password => 'evankorth') +bob = Factory(:user_with_aspect, :username => "bob", :password => 'evankorth') +eve = Factory(:user_with_aspect, :username => "eve", :password => 'evankorth') print "Creating seeded users... " alice.person.profile.update_attributes(:first_name => "Alice", :last_name => "Smith", diff --git a/features/conversations.feature b/features/conversations.feature index 55626aa03..9bf3b6566 100644 --- a/features/conversations.feature +++ b/features/conversations.feature @@ -16,3 +16,7 @@ Feature: private messages And I should see "Greetings" within "#conversation_show" And "Alice Awesome" should be part of active conversation And I should see "hello, alice!" within ".stream" + + Scenario: send an empty message + Given I send a message with subject "Greetings" and text " " to "Alice Awesome" + Then I should not see "Greetings" within "#conversation_inbox" diff --git a/features/step_definitions/user_steps.rb b/features/step_definitions/user_steps.rb index 951b255c1..25f0aa54f 100644 --- a/features/step_definitions/user_steps.rb +++ b/features/step_definitions/user_steps.rb @@ -35,13 +35,15 @@ Given /^a user named "([^\"]*)" with email "([^\"]*)"$/ do |name, email| end Given /^I have been invited by an admin$/ do - @me = Invitation.create_invitee(:service => 'email', :identifier => "new_invitee@example.com") + i = Invitation.create!(:admin => true, :service => 'email', :identifier => "new_invitee@example.com") + @me = i.attach_recipient! end Given /^I have been invited by a user$/ do @inviter = Factory(:user) aspect = @inviter.aspects.create(:name => "Rocket Scientists") - @me = @inviter.invite_user(aspect.id, 'email', "new_invitee@example.com", "Hey, tell me about your rockets!") + 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! end When /^I click on my name$/ do diff --git a/lib/rake_helpers.rb b/lib/rake_helpers.rb index 2e842c750..b796ae5f2 100644 --- a/lib/rake_helpers.rb +++ b/lib/rake_helpers.rb @@ -21,9 +21,17 @@ module RakeHelpers churn_through = n backer_name = backers[n+offset][1].to_s.strip backer_email = backers[n+offset][0].to_s.strip - unless User.find_by_email(backer_email) || User.find_by_invitation_identifier(backer_email) + + possible_user = User.find_by_email(backer_email) + possible_invite = Invitation.find_by_identifier(backer_email) + possible_user ||= possible_invite.recipient if possible_invite.present? + + unless possible_user puts "#{n}: sending email to: #{backer_name} #{backer_email}" unless Rails.env == 'test' - Invitation.create_invitee(:service => 'email', :identifier => backer_email, :name => backer_name ) unless test + unless test + i = Invitation.new(:service => 'email', :identifier => backer_email, :admin => true) + i.send! + end else puts "user with the email exists: #{backer_email} , #{backer_name} " unless Rails.env == 'test' end diff --git a/lib/tasks/backup.rake b/lib/tasks/backup.rake index 4f8a3ce4a..785b625ac 100644 --- a/lib/tasks/backup.rake +++ b/lib/tasks/backup.rake @@ -24,7 +24,7 @@ namespace :backup do puts "Dumping Mysql at #{Time.now.to_s}" `mkdir -p /tmp/backup/mysql` - `nice mysqldump --single-transaction --user=#{user} --password=#{password} #{database} > /tmp/backup/mysql/backup.txt ` + `nice mysqldump --single-transaction --quick --user=#{user} --password=#{password} #{database} > /tmp/backup/mysql/backup.txt ` puts "Gzipping dump at #{Time.now.to_s}" tar_name = "mysql_#{Time.now.to_i}.tar" @@ -36,7 +36,6 @@ namespace :backup do if file.write File.open("/tmp/backup/" + tar_name) puts("event=backup status=success type=mysql") `rm /tmp/backup/#{tar_name}` - `rm -rf /tmp/backup/mysql/` files = mysql_container.objects files.sort!.pop(NUMBER_OF_DAYS * 24) diff --git a/public/javascripts/aspect-filters.js b/public/javascripts/aspect-filters.js index 4d14c82a8..35134a89e 100644 --- a/public/javascripts/aspect-filters.js +++ b/public/javascripts/aspect-filters.js @@ -5,7 +5,7 @@ var AspectFilters = { selectedGUIDS: [], - requests: 0, + activeRequest: null, initialize: function(){ AspectFilters.initializeSelectedGUIDS(); AspectFilters.interceptAspectLinks(); @@ -39,8 +39,6 @@ var AspectFilters = { $('html, body').animate({scrollTop:0}, 'fast'); }, switchToAspect: function(aspectLi){ - AspectFilters.requests++; - var guid = aspectLi.attr('data-guid'); // select correct aspect in filter list & deselect others @@ -55,8 +53,6 @@ var AspectFilters = { $("#aspect_nav a.aspect_selector").click(function(e){ e.preventDefault(); - AspectFilters.requests++; - // loading animation AspectFilters.fadeOut(); @@ -128,11 +124,15 @@ var AspectFilters = { history.pushState(null, document.title, newURL); } - $.ajax({ + try { + AspectFilters.activeRequest.abort(); + } catch(e) {} finally { + AspectFilters.activeRequest = null; + } + AspectFilters.activeRequest = $.ajax({ url : newURL, dataType : 'script', success : function(data){ - AspectFilters.requests--; // fill in publisher // (not cached because this element changes) @@ -155,9 +155,7 @@ var AspectFilters = { Diaspora.widgets.publish("stream/reloaded"); // fade contents back in - if(AspectFilters.requests === 0){ - AspectFilters.fadeIn(); - } + AspectFilters.fadeIn(); } }); }, diff --git a/public/stylesheets/vendor/facebox.css b/public/stylesheets/vendor/facebox.css index 285633cd5..bfa2ed16e 100755 --- a/public/stylesheets/vendor/facebox.css +++ b/public/stylesheets/vendor/facebox.css @@ -12,9 +12,9 @@ -webkit-border-radius:2px; -moz-border-radius:2px; border-radius:2px; - -webkit-box-shadow:0 0 12px rgba(0,0,0,0.8); - -moz-box-shadow:0 0 12px rgba(0,0,0,0.8); - box-shadow:0 0 12px rgba(0,0,0,0.8); + -webkit-box-shadow:0 0 10px rgba(0,0,0,0.8), 0 2px 200px rgba(255,255,255,0.2); + -moz-box-shadow:0 0 10px rgba(0,0,0,0.8), 0 2px 200px rgba(255,255,255,0.2);; + box-shadow:0 0 10px rgba(0,0,0,0.8), 0 2px 200px rgba(255,255,255,0.2);; } #facebox .content { @@ -73,6 +73,6 @@ } .facebox_overlayBG { - background-color: #fff; + background-color: #000; z-index: 99; } diff --git a/spec/controllers/admins_controller_spec.rb b/spec/controllers/admins_controller_spec.rb index 5dfd068ae..d9cca8040 100644 --- a/spec/controllers/admins_controller_spec.rb +++ b/spec/controllers/admins_controller_spec.rb @@ -85,19 +85,11 @@ describe AdminsController do end it 'invites a new user' do - Invitation.should_receive(:create_invitee).with(:service => 'email', :identifier => 'bob@moms.com') + Invitation.should_receive(:create) get :admin_inviter, :identifier => 'bob@moms.com' response.should redirect_to user_search_path flash.notice.should include("invitation sent") end - - it 'passes an existing user to create_invitee' do - Factory.create(:user, :email => 'bob@moms.com') - bob = User.where(:email => 'bob@moms.com').first - Invitation.should_receive(:find_existing_user).with('email', 'bob@moms.com').and_return(bob) - Invitation.should_receive(:create_invitee).with(:service => 'email', :identifier => 'bob@moms.com', :existing_user => bob) - get :admin_inviter, :identifier => 'bob@moms.com' - end end end diff --git a/spec/controllers/contacts_controller_spec.rb b/spec/controllers/contacts_controller_spec.rb index a0b00d5ca..92374536a 100644 --- a/spec/controllers/contacts_controller_spec.rb +++ b/spec/controllers/contacts_controller_spec.rb @@ -59,7 +59,7 @@ describe ContactsController do it 'will return the contacts for multiple aspects' do get :index, :aspect_ids => bob.aspect_ids, :format => 'json' - assigns[:people].should == bob.contacts.map(&:person) + assigns[:people].map(&:id).should =~ bob.contacts.map{|c| c.person.id} response.should be_success end @@ -73,7 +73,7 @@ describe ContactsController do aspect.contacts << bob.contact_for(eve.person) get :index, :format => 'json', :aspect_ids => bob.aspect_ids assigns[:people].map{|p| p.id}.uniq.should == assigns[:people].map{|p| p.id} - assigns[:people].should == bob.contacts.map(&:person) + assigns[:people].map(&:id).should =~ bob.contacts.map{|c| c.person.id} end end diff --git a/spec/controllers/conversations_controller_spec.rb b/spec/controllers/conversations_controller_spec.rb index 6ee8b1d59..09c93b403 100644 --- a/spec/controllers/conversations_controller_spec.rb +++ b/spec/controllers/conversations_controller_spec.rb @@ -54,47 +54,75 @@ describe ConversationsController do end describe '#create' do - before do - @hash = { - :conversation => { - :subject => "secret stuff", - :text => 'text'}, - :contact_ids => [alice.contacts.first.id] - } - end - - it 'creates a conversation' do - lambda { - post :create, @hash - }.should change(Conversation, :count).by(1) - end - - it 'creates a message' do - lambda { - post :create, @hash - }.should change(Message, :count).by(1) - end - - it 'sets the author to the current_user' do - @hash[:author] = Factory.create(:user) - post :create, @hash - Message.first.author.should == alice.person - Conversation.first.author.should == alice.person - end - - it 'dispatches the conversation' do - cnv = Conversation.create( - { - :author => alice.person, - :participant_ids => [alice.contacts.first.person.id, alice.person.id], - :subject => 'not spam', - :messages_attributes => [ {:author => alice.person, :text => 'cool stuff'} ] + context 'with a valid conversation' do + before do + @hash = { + :conversation => { + :subject => "secret stuff", + :text => 'text debug' + }, + :contact_ids => [alice.contacts.first.id] } - ) - p = Postzord::Dispatch.new(alice, cnv) - Postzord::Dispatch.stub!(:new).and_return(p) - p.should_receive(:post) - post :create, @hash + end + + it 'creates a conversation' do + lambda { + post :create, @hash + }.should change(Conversation, :count).by(1) + end + + it 'creates a message' do + lambda { + post :create, @hash + }.should change(Message, :count).by(1) + end + + it 'sets the author to the current_user' do + @hash[:author] = Factory.create(:user) + post :create, @hash + Message.first.author.should == alice.person + Conversation.first.author.should == alice.person + end + + it 'dispatches the conversation' do + cnv = Conversation.create( + { + :author => alice.person, + :participant_ids => [alice.contacts.first.person.id, alice.person.id], + :subject => 'not spam', + :messages_attributes => [ {:author => alice.person, :text => 'cool stuff'} ] + } + ) + + p = Postzord::Dispatch.new(alice, cnv) + Postzord::Dispatch.stub!(:new).and_return(p) + p.should_receive(:post) + post :create, @hash + end + end + + context 'with empty text' do + before do + @hash = { + :conversation => { + :subject => 'secret stuff', + :text => ' ' + }, + :contact_ids => [alice.contacts.first.id] + } + end + + it 'does not create a conversation' do + lambda { + post :create, @hash + }.should_not change(Conversation, :count).by(1) + end + + it 'does not create a message' do + lambda { + post :create, @hash + }.should_not change(Message, :count).by(1) + end end end diff --git a/spec/controllers/invitations_controller_spec.rb b/spec/controllers/invitations_controller_spec.rb index 460917783..dc1b3fdad 100644 --- a/spec/controllers/invitations_controller_spec.rb +++ b/spec/controllers/invitations_controller_spec.rb @@ -11,6 +11,7 @@ describe InvitationsController 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)) @@ -19,43 +20,37 @@ describe InvitationsController do describe "#create" do before do sign_in :user, @user - @invite = {:invite_message=>"test", :aspect_id=> @aspect.id.to_s, :email=>"abc@example.com"} @controller.stub!(:current_user).and_return(@user) request.env["HTTP_REFERER"]= 'http://test.host/cats/foo' end - it 'calls the resque job Job::InviteUser' do - Resque.should_receive(:enqueue) - post :create, :user => @invite + it 'saves and invitation' do + expect { + post :create, :user => @invite + }.should change(Invitation, :count).by(1) end it 'handles a comma seperated list of emails' do - Resque.should_receive(:enqueue).twice() - post :create, :user => @invite.merge( + expect{ + post :create, :user => @invite.merge( :email => "foofoofoofoo@example.com, mbs@gmail.com") + }.should change(Invitation, :count).by(2) end it 'handles a comma seperated list of emails with whitespace' do - Resque.should_receive(:enqueue).twice() - post :create, :user => @invite.merge( - :email => "foofoofoofoo@example.com , mbs@gmail.com") - 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[:error].should_not be_empty - flash[:error].should =~ /foo\.com/ - flash[:error].should =~ /lala@foo/ + 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 open_bit = AppConfig[:open_invitations] AppConfig[:open_invitations] = true - Resque.should_receive(:enqueue).once - post :create, :user => @invite - + expect{ + post :create, :user => @invite + }.to change(Invitation, :count).by(1) AppConfig[:open_invitations] = open_bit end @@ -67,16 +62,19 @@ describe InvitationsController do it 'strips out your own email' do lambda { post :create, :user => @invite.merge(:email => @user.email) - }.should_not change(User, :count) + }.should_not change(Invitation, :count) - Resque.should_receive(:enqueue).once - post :create, :user => @invite.merge(:email => "hello@example.org, #{@user.email}") + expect{ + post :create, :user => @invite.merge(:email => "hello@example.org, #{@user.email}") + }.should change(Invitation, :count).by(1) end end describe "#update" do before do - @invited_user = @user.invite_user(@aspect.id, 'email', "a@a.com") + 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", @@ -137,7 +135,8 @@ describe InvitationsController do @controller.stub!(:current_user).and_return(@user) request.env["HTTP_REFERER"]= 'http://test.host/cats/foo' - @invited_user = @user.invite_user(@aspect.id, 'email', "a@a.com", "") + 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 @@ -148,12 +147,24 @@ describe InvitationsController do end it 'does not send an invitation for a different user' do - @user2 = bob - @aspect2 = @user2.aspects.create(:name => "cats") - @user2.invite_user(@aspect2.id, 'email', "b@b.com", "") - invitation2 = @user2.reload.invitations_from_me.first + invitation2 = Factory(:invitation, :sender => bob, :service => 'email', :identifier => "a@a.com") + Resque.should_not_receive(:enqueue) - put :resend, :id => invitation2.id + 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_empty + flash[:notice].should =~ /foo\.com/ + flash[:notice].should =~ /lala@foo/ end end end diff --git a/spec/controllers/messages_controller_spec.rb b/spec/controllers/messages_controller_spec.rb index 6a120effd..36a899b99 100644 --- a/spec/controllers/messages_controller_spec.rb +++ b/spec/controllers/messages_controller_spec.rb @@ -28,15 +28,34 @@ describe MessagesController do context "on my own post" do before do @cnv = Conversation.create(@create_hash) - @message_hash = {:conversation_id => @cnv.id, :message => {:text => "here is something else"}} end - it 'redirects to conversation' do - lambda{ - post :create, @message_hash - }.should change(Message, :count).by(1) - response.code.should == '302' - response.should redirect_to(conversations_path(:conversation_id => @cnv)) + context "with a valid message" do + before do + @message_hash = {:conversation_id => @cnv.id, :message => {:text => "here is something else"}} + end + + it 'redirects to conversation' do + lambda{ + post :create, @message_hash + }.should change(Message, :count).by(1) + response.code.should == '302' + response.should redirect_to(conversations_path(:conversation_id => @cnv)) + end + end + + context "with an empty message" do + before do + @message_hash = {:conversation_id => @cnv.id, :message => {:text => " "}} + end + + it 'redirects to conversation' do + lambda{ + post :create, @message_hash + }.should_not change(Message, :count).by(1) + response.code.should == '302' + response.should redirect_to(conversations_path(:conversation_id => @cnv)) + end end end diff --git a/spec/controllers/services_controller_spec.rb b/spec/controllers/services_controller_spec.rb index e7674e675..1333262f4 100644 --- a/spec/controllers/services_controller_spec.rb +++ b/spec/controllers/services_controller_spec.rb @@ -146,7 +146,7 @@ describe ServicesController do end it 'does not create a duplicate invitation' do - inv = Invitation.create!(:sender_id => @user.id, :recipient_id => eve.id, :aspect_id => @user.aspects.first.id) + inv = Invitation.create!(:sender => @user, :recipient => eve, :aspect => @user.aspects.first, :identifier => eve.email) @invite_params[:invitation_id] = inv.id lambda { diff --git a/spec/factories.rb b/spec/factories.rb index e5bca7665..d6edc0355 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -21,7 +21,7 @@ Factory.define :person do |p| p.sequence(:url) { |n| AppConfig[:pod_url] } p.serialized_public_key OpenSSL::PKey::RSA.generate(1024).public_key.export p.after_build do |person| - person.profile ||= Factory.build(:profile, :person => person) + person.profile = Factory.build(:profile, :person => person) unless person.profile.first_name.present? end p.after_create do |person| person.profile.save @@ -96,6 +96,15 @@ Factory.define :reshare do |r| r.association(:author, :factory => :person) end +Factory.define :invitation do |i| + i.service "email" + i.identifier "bob.smith@smith.com" + i.association :sender, :factory => :user_with_aspect + i.after_build do |i| + i.aspect = i.sender.aspects.first + end +end + Factory.define :service do |service| service.nickname "sirrobertking" service.type "Services::Twitter" diff --git a/spec/helpers/getting_started_helper_spec.rb b/spec/helpers/getting_started_helper_spec.rb index 0223b9230..1176300b4 100644 --- a/spec/helpers/getting_started_helper_spec.rb +++ b/spec/helpers/getting_started_helper_spec.rb @@ -16,7 +16,7 @@ describe GettingStartedHelper do it 'returns true if the current user has filled out all 7 suggested fields (from getting started)' do profile = @current_user.person.profile profile.update_attributes!( - {:first_name => "alice", :last_name => "smith", :image_url => "abcd.jpg", :birthday => Date.new, + {:first_name => "alice", :last_name => "smith", :image_url => "abcd.jpg", :birthday => Time.now, :gender => "cow", :location => "san fran", :tag_string => "#sup", :bio => "holler" }) has_completed_profile?.should be_true end @@ -59,7 +59,7 @@ describe GettingStartedHelper do end it 'returns false if the current_user has less than 2 contacts (inclusive)' do - @current_user.contacts.delete_all + @current_user.contacts.destroy_all has_few_contacts?.should be_false end end diff --git a/spec/javascripts/aspect-filters-spec.js b/spec/javascripts/aspect-filters-spec.js index 5f955e41a..89bed4ced 100644 --- a/spec/javascripts/aspect-filters-spec.js +++ b/spec/javascripts/aspect-filters-spec.js @@ -7,7 +7,7 @@ describe('AspectFilters', function(){ it('initializes selectedGUIDS', function(){ expect(AspectFilters.selectedGUIDS).toEqual([]); }); - it('initializes requests', function(){ - expect(AspectFilters.requests).toEqual(0); + it('initializes activeRequest', function(){ + expect(AspectFilters.activeRequest).toEqual(null); }); }); diff --git a/spec/lib/rake_helper_spec.rb b/spec/lib/rake_helper_spec.rb index 6f2b6d40b..f511065d6 100644 --- a/spec/lib/rake_helper_spec.rb +++ b/spec/lib/rake_helper_spec.rb @@ -14,8 +14,9 @@ describe RakeHelpers do Devise.mailer.deliveries = [] end it 'should send emails to each backer' do - Invitation.should_receive(:create_invitee).exactly(3).times - process_emails(@csv, 100, 1, false) + expect{ + process_emails(@csv, 100, 1, false) + }.to change(User, :count).by(3) end it 'should not send the email to the same email twice' do diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb index 71c27b7a8..329df2971 100644 --- a/spec/models/invitation_spec.rb +++ b/spec/models/invitation_spec.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2010, Diaspora Inc. This file is +# Copyright (c) 2011, Diaspora Inc. This file is # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. @@ -6,7 +6,6 @@ require 'spec_helper' describe Invitation do let(:user) { alice } - let(:aspect) { user.aspects.first } before do @email = 'maggie@example.com' @@ -14,292 +13,130 @@ describe Invitation do end describe 'validations' do before do - aspect - @invitation = Invitation.new(:sender => user, :recipient => eve, :aspect => aspect) + @invitation = Factory.build(:invitation, :sender => user, :recipient => eve, :aspect => user.aspects.first) end + it 'is valid' do @invitation.sender.should == user @invitation.recipient.should == eve - @invitation.aspect.should == aspect + @invitation.aspect.should == user.aspects.first @invitation.should be_valid end - it 'is from a user' do - @invitation.sender = nil - @invitation.should_not be_valid - end - it 'is to a user' do - @invitation.recipient = nil - @invitation.should_not be_valid - end - it 'is into an aspect' do - @invitation.aspect = nil + + it 'ensures the sender is placing the recipient into one of his aspects' do + @invitation.aspect = Factory(:aspect) @invitation.should_not be_valid end end it 'has a message' do - @invitation = Invitation.new(:sender => user, :recipient => eve, :aspect => aspect) + @invitation = Factory.build(:invitation, :sender => user, :recipient => eve, :aspect => user.aspects.first) @invitation.message = "!" @invitation.message.should == "!" end - describe '.find_existing_user' do - let(:inv) { Invitation.find_existing_user(@type, @identifier) } - context 'send a request to an existing' do - context 'active user' do - it 'by email' do - @identifier = alice.email - @type = 'email' - inv.should == alice - end - it 'by service' do - uid = '123324234' - alice.services << Services::Facebook.new(:uid => uid) - alice.save - - @type = 'facebook' - @identifier = uid - - inv.should == alice - end - end - - context 'invited user' do - it 'by email' do - @identifier = alice.email - @type = 'email' - - alice.invitation_identifier = @identifier - alice.invitation_service = @type - alice.save - inv.should == alice - end - - it 'by service' do - fb_id = 'abc123' - alice.invitation_service = 'facebook' - alice.invitation_identifier = fb_id - alice.save - - @identifier = fb_id - @type = 'facebook' - inv.should == alice - end - end + describe 'the invite process' do + before do end - end - describe '.invite' do - it 'creates an invitation' do + it 'works for a new user' do + invite = Invitation.new(:sender => alice, :aspect => alice.aspects.first, :service => 'email', :identifier => 'foo@bar.com') lambda { - Invitation.invite(:service => 'email', :identifier => @email, :from => user, :into => aspect) - }.should change(Invitation, :count).by(1) - end - - it 'associates the invitation with the inviter' do - lambda { - Invitation.invite(:service => 'email', :identifier => @email, :from => user, :into => aspect) - }.should change { user.reload.invitations_from_me.count }.by(1) - end - - it 'associates the invitation with the invitee' do - new_user = Invitation.invite(:service => 'email', :identifier => @email, :from => user, :into => aspect) - new_user.invitations_to_me.count.should == 1 - end - - it 'creates a user' do - lambda { - Invitation.invite(:from => user, :service => 'email', :identifier => @email, :into => aspect) + invite.save + invite.send! }.should change(User, :count).by(1) end - it 'returns the new user' do - new_user = Invitation.invite(:from => user, :service => 'email', :identifier => @email, :into => aspect) - new_user.is_a?(User).should be_true - new_user.email.should == @email + 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) + lambda { + invite.send! + }.should_not change(User, :count) end - it 'adds the inviter to the invited_user' do - new_user = Invitation.invite(:from => user, :service => 'email', :identifier => @email, :into => aspect) - new_user.invitations_to_me.first.sender.should == user + 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 - it 'adds an optional message' do - message = "How've you been?" - new_user = Invitation.invite(:from => user, :service => 'email', :identifier => @email, :into => aspect, :message => message) - new_user.invitations_to_me.first.message.should == message + it 'handles the case when that user has an invite but not a user' do + pending end - it 'sends a contact request to a user with that email into the aspect' do - user.should_receive(:share_with).with(eve.person, aspect) - Invitation.invite(:from => user, :service => 'email', :identifier => eve.email, :into => aspect) + it 'handles the case where that user has an invite but has not yet accepted' do + pending end - context 'invalid email' do - it 'return a user with errors' do - new_user = Invitation.invite(:service => 'email', :identifier => "fkjlsdf", :from => user, :into => aspect) - new_user.should have(1).errors_on(:email) - new_user.should_not be_persisted - end + it 'generate the invitation token and pass it to the user' do + end end - - describe '.create_invitee' do - context "when we're resending an invitation" do - before do - @valid_params = {:from => user, - :service => 'email', - :identifier => @email, - :into => aspect, - :message => @message} - @invitee = Invitation.create_invitee(:service => 'email', :identifier => @email) - @valid_params[:existing_user] = @invitee - end - - it "does not create a user" do - expect { Invitation.create_invitee(@valid_params) }.should_not change(User, :count) - end - - it "sends mail" do - expect { - Invitation.create_invitee(@valid_params) - }.should change { Devise.mailer.deliveries.size }.by(1) - end - - it "does not set the key" do - expect { - Invitation.create_invitee(@valid_params) - }.should_not change { @invitee.reload.serialized_private_key } - end - - it "does not change the invitation token" do - old_token = @invitee.invitation_token - Invitation.create_invitee(@valid_params) - @invitee.reload.invitation_token.should == old_token - end - end - context 'with an inviter' do - before do - @message = "whatever" - @valid_params = {:from => user, :service => 'email', :identifier => @email, :into => aspect, :message => @message} - end - - it "sends mail" do - expect { - Invitation.create_invitee(@valid_params) - }.should change { Devise.mailer.deliveries.size }.by(1) - end - - it "includes the message in the email" do - Invitation.create_invitee(@valid_params) - Devise.mailer.deliveries.last.to_s.should include(@message) - end - - it "has no translation missing" do - Invitation.create_invitee(@valid_params) - Devise.mailer.deliveries.last.body.raw_source.should_not match(/(translation_missing.+)/) - end - - it "doesn't create a user if the email is invalid" do - new_user = Invitation.create_invitee(@valid_params.merge(:identifier => 'fdfdfdfdf')) - new_user.should_not be_persisted - new_user.should have(1).error_on(:email) - end - - it "does not save a user with an empty string email" do - @valid_params[:service] = 'facebook' - @valid_params[:identifier] = '3423423' - Invitation.create_invitee(@valid_params) - @valid_params[:identifier] = 'dfadsfdas' - expect { Invitation.create_invitee(@valid_params) }.should_not raise_error - end - end - - context 'with no inviter' do - it 'sends an email that includes the right things' do - Invitation.create_invitee(:service => 'email', :identifier => @email) - Devise.mailer.deliveries.first.to_s.should include("Email not displaying correctly?") - end - it 'creates a user' do - expect { - Invitation.create_invitee(:service => 'email', :identifier => @email) - }.should change(User, :count).by(1) - end - it 'sends email to the invited user' do - expect { - Invitation.create_invitee(:service => 'email', :identifier => @email) - }.should change { Devise.mailer.deliveries.size }.by(1) - end - it 'does not create an invitation' do - expect { - Invitation.create_invitee(:service => 'email', :identifier => @email) - }.should_not change(Invitation, :count) - end - end - end - - describe '.resend' do + + describe '.batch_invite' do before do - aspect - user.invite_user(aspect.id, 'email', "a@a.com", "") - @invitation = user.reload.invitations_from_me.first + @emails = ['max@foo.com', 'bob@mom.com'] + @opts = {:aspect => eve.aspects.first, :sender => eve, :service => 'email'} + end + + it 'returns an array of invites based on the emails passed in' do + invites = Invitation.batch_invite(@emails, @opts) + invites.count.should be 2 + invites.all?{|x| x.persisted?}.should be_true + end + + it 'shares with people who are already on the pod and does not create an invite for them' do + Factory(:user, :email => @emails.first) + invites = nil + expect{ + invites = Invitation.batch_invite(@emails, @opts) + }.to change(eve.contacts, :count).by(1) + invites.count.should be 1 + + end + end + + describe '#resend' do + before do + @invitation = Factory(:invitation, :sender => alice, :aspect => alice.aspects.first, :service => 'email', :identifier => 'a@a.com') end it 'sends another email' do - lambda { @invitation.resend }.should change(Devise.mailer.deliveries, :count).by(1) - end - end - - describe '#share_with!' do - before do - @new_user = Invitation.invite(:from => user, :service => 'email', :identifier => @email, :into => aspect) - acceptance_params = {:invitation_token => "abc", - :username => "user", - :email => @email, - :password => "secret", - :password_confirmation => "secret", - :person => {:profile => {:first_name => "Bob", :last_name => "Smith"}}} - @new_user.setup(acceptance_params) - @new_user.person.save - @new_user.save - @invitation = @new_user.invitations_to_me.first - end - - it 'destroys the invitation' do lambda { - @invitation.share_with! - }.should change(Invitation, :count).by(-1) - end - - it 'creates a contact for the inviter and invitee' do - lambda { - @invitation.share_with! - }.should change(Contact, :count).by(2) + @invitation.resend + }.should change(Devise.mailer.deliveries, :count).by(1) end end describe '#recipient_identifier' do it 'calls email if the invitation_service is email' do - alice.invite_user(aspect.id, 'email', "a@a.com", "") - invitation = alice.reload.invitations_from_me.first - invitation.recipient_identifier.should == 'a@a.com' + email = 'abc@abc.com' + invitation = Factory(:invitation, :sender => alice, :service => 'email', :identifier => email, :aspect => alice.aspects.first) + invitation.recipient_identifier.should == email end - it 'gets the name if the invitation_service is facebook' do - alice.services << Services::Facebook.new(:uid => "13234895") - alice.reload.services(true).first.service_users.create(:uid => "23526464", :photo_url => 'url', :name => "Remote User") - alice.invite_user(aspect.id, 'facebook', "23526464", '') - invitation = alice.reload.invitations_from_me.first - invitation.recipient_identifier.should == "Remote User" - end - it 'does not error if the facebook user is not recorded' do - alice.services << Services::Facebook.new(:uid => "13234895") - alice.reload.services(true).first.service_users.create(:uid => "23526464", :photo_url => 'url', :name => "Remote User") - alice.invite_user(aspect.id, 'facebook', "23526464", '') - alice.services.first.service_users.delete_all - invitation = alice.reload.invitations_from_me.first - invitation.recipient_identifier.should == "A Facebook user" + + 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 diff --git a/spec/models/jobs/mail/invite_user_by_email_spec.rb b/spec/models/jobs/mail/invite_user_by_email_spec.rb index 8f3181737..0bc8beb0e 100644 --- a/spec/models/jobs/mail/invite_user_by_email_spec.rb +++ b/spec/models/jobs/mail/invite_user_by_email_spec.rb @@ -4,20 +4,14 @@ describe Job::Mail::InviteUserByEmail do before do @sender = alice @email = 'bob@bob.com' - @aspect_id = alice.aspects.first.id + @aspect = alice.aspects.first @message = 'invite message' - - User.stub(:find){ |id| - if id == @sender.id - @sender - else - nil - end - } end it 'calls invite_user with email param' do - @sender.should_receive(:invite_user).with(@aspect_id, 'email', @email, @message) - Job::Mail::InviteUserByEmail.perform(@sender.id, @email, @aspect_id, @message) + invitation = Invitation.create(:sender => @sender, :identifier => @email, :service => "email", :aspect => @aspect, :message => @message) + invitation.should_receive(:send!) + Invitation.stub(:find).and_return(invitation) + Job::Mail::InviteUserByEmail.perform(invitation.id) end end diff --git a/spec/models/jobs/resend_invitation_spec.rb b/spec/models/jobs/resend_invitation_spec.rb index de7f54074..25969047e 100644 --- a/spec/models/jobs/resend_invitation_spec.rb +++ b/spec/models/jobs/resend_invitation_spec.rb @@ -5,17 +5,13 @@ require 'spec_helper' describe Job::ResendInvitation do - describe '#perfom_delegate' do + describe '#perfom' do it 'should call .resend on the object' do - user = alice - aspect = user.aspects.create(:name => "cats") - user.invite_user(aspect.id, 'email', "a@a.com", "") - invitation = user.reload.invitations_from_me.first + invite = Factory(:invitation, :service => 'email', :identifier => 'foo@bar.com') - #Notification.should_receive(:notify).with(instance_of(User), instance_of(StatusMessage), instance_of(Person)) - Invitation.stub(:where).with(:id => invitation.id ).and_return([invitation]) - invitation.should_receive(:resend) - Job::ResendInvitation.perform(invitation.id) + Invitation.stub(:find).and_return(invite) + invite.should_receive(:resend) + Job::ResendInvitation.perform(invite.id) end end end diff --git a/spec/models/person_spec.rb b/spec/models/person_spec.rb index 63a8861b3..9706335c9 100644 --- a/spec/models/person_spec.rb +++ b/spec/models/person_spec.rb @@ -11,6 +11,15 @@ describe Person do @person = Factory.create(:person) end + it 'always has a profile' do + Person.new.profile.should_not be_nil + end + + it 'does not save automatically' do + Person.new.persisted?.should be_false + Person.new.profile.persisted?.should be_false + end + context 'scopes' do describe '.for_json' do it 'does not select public keys' do @@ -144,7 +153,7 @@ describe Person do end end - describe 'xml' do + describe 'XML' do before do @xml = @person.to_xml.to_s end diff --git a/spec/models/services/facebook_spec.rb b/spec/models/services/facebook_spec.rb index fcb1be916..fc6d59e73 100644 --- a/spec/models/services/facebook_spec.rb +++ b/spec/models/services/facebook_spec.rb @@ -69,12 +69,12 @@ JSON it 'attaches local models' do @service.save_friends - @service.service_users.first.person.should == @user2.person + @service.service_users.where(:uid => @user2_fb_id).first.person.should == @user2.person end it 'overwrites local model information' do @service.save_friends - su = @service.service_users.first + su = @service.service_users.where(:uid => @user2_fb_id).first su.person.should == @user2.person su.contact.should == nil diff --git a/spec/models/user/invite_spec.rb b/spec/models/user/invite_spec.rb deleted file mode 100644 index 41ce0e6c7..000000000 --- a/spec/models/user/invite_spec.rb +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2010, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -require 'spec_helper' - -describe User do - context "creating invites" do - before do - @aspect = eve.aspects.first - @email = "bob@bob.com" - end - - it 'requires your aspect' do - lambda { - eve.invite_user(alice.aspects.first.id, "email", "maggie@example.com") - }.should raise_error ActiveRecord::RecordNotFound - end - - it 'takes a service parameter' do - @invite_params = {:service => 'email'} - Invitation.should_receive(:invite).with(hash_including(@invite_params)) - eve.invite_user(@aspect.id, 'email', @email) - end - - it 'takes an indentifier parameter' do - @invite_params = {:identifier => @email} - Invitation.should_receive(:invite).with(hash_including(@invite_params)) - eve.invite_user(@aspect.id, 'email', @email) - end - - it 'calls Invitation.invite' do - Invitation.should_receive(:invite) - eve.invite_user(@aspect.id, 'email', @email) - end - - it 'has an invitation' do - eve.invite_user(@aspect.id, 'email', @email).invitations_to_me.count.should == 1 - end - - it 'creates it with an email' do - eve.invite_user(@aspect.id, 'email', @email).email.should == @email - end - - it "throws if you try to add someone you're connected to" do - connect_users(eve, @aspect, alice, alice.aspects.first) - lambda { - eve.invite_user(@aspect.id, 'email', alice.email) - }.should raise_error ActiveRecord::RecordNotUnique - end - - it 'does not invite people I already invited' do - eve.invite_user(@aspect.id, 'email', "email1@example.com") - lambda { - eve.invite_user(@aspect.id, 'email', "email1@example.com") - }.should raise_error /You already invited this person/ - end - end - - describe "#accept_invitation!" do - before do - invite_pre = Invitation.invite(:from => eve, :service => 'email', :identifier => 'invitee@example.org', :into => eve.aspects.first).reload - @person_count = Person.count - @invited_user = invite_pre.accept_invitation!(: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 - @invited_user.invitations_to_me.count.should == 0 - end - - it "should create the person with the passed in params" do - Person.count.should == @person_count + 1 - @invited_user.person.profile.first_name.should == "Bob" - end - - it 'resolves incoming invitations into contact requests' do - eve.contacts.where(:person_id => @invited_user.person.id).count.should == 1 - end - end - end -end - diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f34193fd5..283470736 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -318,6 +318,76 @@ 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' do + + end + + 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 + end + + end + describe 'update_user_preferences' do before do @pref_count = UserPreference::VALID_EMAIL_TYPES.count @@ -484,14 +554,14 @@ describe User do describe '#destroy' do it 'removes invitations from the user' do - alice.invite_user alice.aspects.first.id, 'email', 'blah@blah.blah' + Factory(:invitation, :sender => alice) lambda { alice.destroy }.should change {alice.invitations_from_me(true).count }.by(-1) end it 'removes invitations to the user' do - Invitation.create(:sender_id => eve.id, :recipient_id => alice.id, :aspect_id => eve.aspects.first.id) + Invitation.create!(:sender => eve, :recipient => alice, :identifier => alice.email, :aspect => eve.aspects.first) lambda { alice.destroy }.should change {alice.invitations_to_me(true).count }.by(-1) @@ -810,6 +880,52 @@ describe User do end end + describe "#accept_invitation!" do + before do + fantasy_resque do + @invitation = Factory.create(: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 @retraction = mock