diff --git a/Gemfile.lock b/Gemfile.lock index 38a4fbbbf..2f85205a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -119,14 +119,14 @@ GEM erubis extlib highline - json (>= 1.4.4, <= 1.4.6) + json (<= 1.4.6, >= 1.4.4) mixlib-authentication (>= 1.1.0) mixlib-cli (>= 1.1.0) mixlib-config (>= 1.1.2) mixlib-log (>= 1.2.0) moneta ohai (>= 0.5.7) - rest-client (>= 1.0.4, < 1.7.0) + rest-client (< 1.7.0, >= 1.0.4) uuidtools childprocess (0.1.7) ffi (~> 0.6.3) @@ -163,7 +163,7 @@ GEM faraday (0.5.4) addressable (~> 2.2.2) multipart-post (~> 1.1.0) - rack (>= 1.1.0, < 2) + rack (< 2, >= 1.1.0) faraday_middleware (0.3.2) faraday (~> 0.5.4) fastercsv (1.5.4) @@ -271,7 +271,7 @@ GEM multi_json (~> 0.0.4) ohai (0.5.8) extlib - json (>= 1.4.4, <= 1.4.6) + json (<= 1.4.6, >= 1.4.4) mixlib-cli mixlib-config mixlib-log @@ -344,7 +344,7 @@ GEM rubyntlm (0.1.1) rubyzip (0.9.4) selenium-client (1.2.18) - selenium-rc (2.3.1) + selenium-rc (2.3.2) selenium-client (>= 1.2.18) selenium-webdriver (0.1.3) childprocess (~> 0.1.5) @@ -352,9 +352,9 @@ GEM json_pure rubyzip simple_oauth (0.1.4) - sinatra (1.1.3) + sinatra (1.2.0) rack (~> 1.1) - tilt (>= 1.2.2, < 2.0) + tilt (< 2.0, >= 1.2.2) subexec (0.0.4) systemu (1.2.0) term-ansicolor (1.0.5) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 64940e9da..64b9a6970 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,7 +7,7 @@ class ApplicationController < ActionController::Base protect_from_forgery :except => :receive before_filter :ensure_http_referer_is_set - before_filter :set_contacts_notifications_and_status, :except => [:create, :update] + before_filter :set_contacts_notifications_unread_count_and_status, :except => [:create, :update] before_filter :count_requests before_filter :set_invites before_filter :set_locale @@ -22,12 +22,13 @@ class ApplicationController < ActionController::Base request.env['HTTP_REFERER'] ||= '/aspects' end - def set_contacts_notifications_and_status + def set_contacts_notifications_unread_count_and_status if user_signed_in? @aspect = nil @object_aspect_ids = [] @all_aspects = current_user.aspects.includes(:aspect_memberships, :post_visibilities) @notification_count = Notification.for(current_user, :unread =>true).count + @unread_message_count = ConversationVisibility.sum(:unread, :conditions => "person_id = #{current_user.person.id}") @user_id = current_user.id end end diff --git a/app/controllers/aspect_memberships_controller.rb b/app/controllers/aspect_memberships_controller.rb index c7be3a84f..9d5c26a1c 100644 --- a/app/controllers/aspect_memberships_controller.rb +++ b/app/controllers/aspect_memberships_controller.rb @@ -3,20 +3,10 @@ # the COPYRIGHT file. # -class AspectMembershipsController < ApplicationController +class AspectMembershipsController < ApplicationController before_filter :authenticate_user! - def new - render :nothing => true - end - - def index - raise - end - - - - def destroy + def destroy #note :id is garbage @person_id = params[:person_id] @@ -58,7 +48,6 @@ class AspectMembershipsController < ApplicationController @aspect = current_user.aspects.where(:id => params[:aspect_id]).first @contact = current_user.contact_for(@person) - current_user.add_contact_to_aspect(@contact, @aspect) flash.now[:notice] = I18n.t 'aspects.add_to_aspect.success' diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 0409f31ff..6e2ac8f5e 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -18,8 +18,7 @@ class CommentsController < ApplicationController if @comment.save Rails.logger.info("event=create type=comment user=#{current_user.diaspora_handle} status=success comment=#{@comment.id} chars=#{params[:text].length}") - - current_user.dispatch_comment(@comment) + Postzord::Dispatch.new(current_user, @comment).post respond_to do |format| format.js{ diff --git a/app/controllers/conversation_visibilities_controller.rb b/app/controllers/conversation_visibilities_controller.rb new file mode 100644 index 000000000..b136e38be --- /dev/null +++ b/app/controllers/conversation_visibilities_controller.rb @@ -0,0 +1,19 @@ +# Copyright (c) 2010, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. +# + +class ConversationVisibilitiesController < ApplicationController + before_filter :authenticate_user! + + def destroy + @vis = ConversationVisibility.where(:person_id => current_user.person.id, + :conversation_id => params[:conversation_id]).first + if @vis + if @vis.destroy + flash[:notice] = "Conversation successfully removed" + end + end + redirect_to conversations_path + end +end diff --git a/app/controllers/conversations_controller.rb b/app/controllers/conversations_controller.rb new file mode 100644 index 000000000..5806f4358 --- /dev/null +++ b/app/controllers/conversations_controller.rb @@ -0,0 +1,66 @@ +class ConversationsController < ApplicationController + before_filter :authenticate_user! + + respond_to :html, :json + + def index + @conversations = Conversation.joins(:conversation_visibilities).where( + :conversation_visibilities => {:person_id => current_user.person.id}).paginate( + :page => params[:page], :per_page => 15, :order => 'updated_at DESC') + + @visibilities = ConversationVisibility.where( :person_id => current_user.person.id ).paginate( + :page => params[:page], :per_page => 15, :order => 'updated_at DESC') + + @unread_counts = {} + @visibilities.each{|v| @unread_counts[v.conversation_id] = v.unread} + + @authors = {} + @conversations.each{|c| @authors[c.id] = c.last_author} + + @conversation = Conversation.joins(:conversation_visibilities).where( + :conversation_visibilities => {:person_id => current_user.person.id, :conversation_id => params[:conversation_id]}).first + end + + def create + person_ids = Contact.where(:id => params[:contact_ids].split(',')).map! do |contact| + contact.person_id + end + + params[:conversation][:participant_ids] = person_ids | [current_user.person.id] + params[:conversation][:author] = current_user.person + + if @conversation = Conversation.create(params[:conversation]) + Postzord::Dispatch.new(current_user, @conversation).post + + flash[:notice] = "Message sent" + if params[:profile] + redirect_to person_path(params[:profile]) + else + redirect_to conversations_path(:conversation_id => @conversation.id) + end + end + end + + def show + @conversation = Conversation.joins(:conversation_visibilities).where(:id => params[:id], + :conversation_visibilities => {:person_id => current_user.person.id}).first + + if @visibility = ConversationVisibility.where(:conversation_id => params[:id], :person_id => current_user.person.id).first + @visibility.unread = 0 + @visibility.save + end + + if @conversation + render :layout => false + else + redirect_to conversations_path + end + end + + def new + @all_contacts_and_ids = current_user.contacts.map{|c| {:value => c.id, :name => c.person.name}} + @contact = current_user.contacts.find(params[:contact_id]) if params[:contact_id] + render :layout => false + end + +end diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb new file mode 100644 index 000000000..2dac50dda --- /dev/null +++ b/app/controllers/messages_controller.rb @@ -0,0 +1,32 @@ +# Copyright (c) 2010, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +class MessagesController < ApplicationController + include ApplicationHelper + before_filter :authenticate_user! + + respond_to :html, :mobile + respond_to :json, :only => :show + + def create + cnv = Conversation.joins(:conversation_visibilities).where(:id => params[:conversation_id], + :conversation_visibilities => {:person_id => current_user.person.id}).first + + if cnv + message = Message.new(:conversation_id => cnv.id, :text => params[:message][:text], :author => current_user.person) + + 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 => 406 + end + else + render :nothing => true, :status => 406 + end + end + +end diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index e426d4c60..baf48d0a7 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -30,7 +30,7 @@ class PhotosController < ApplicationController end @posts = current_user.visible_photos.where( - :person_id => @person.id + :author_id => @person.id ).paginate(:page => params[:page]) render 'people/show' @@ -94,8 +94,8 @@ class PhotosController < ApplicationController end def make_profile_photo - person_id = current_user.person.id - @photo = Photo.where(:id => params[:photo_id], :person_id => person_id).first + author_id = current_user.person.id + @photo = Photo.where(:id => params[:photo_id], :author_id => author_id).first if @photo profile_hash = {:image_url => @photo.url(:thumb_large), @@ -108,7 +108,7 @@ class PhotosController < ApplicationController :image_url => @photo.url(:thumb_large), :image_url_medium => @photo.url(:thumb_medium), :image_url_small => @photo.url(:thumb_small), - :person_id => person_id}, + :author_id => author_id}, :status => 201} end else @@ -139,8 +139,8 @@ class PhotosController < ApplicationController end def show - @photo = current_user.visible_photos.where(:id => params[:id]).includes(:person, :status_message => :photos).first - @photo ||= Photo.where(:public => true, :id => params[:id]).includes(:person, :status_message => :photos).first + @photo = current_user.visible_photos.where(:id => params[:id]).includes(:author, :status_message => :photos).first + @photo ||= Photo.where(:public => true, :id => params[:id]).includes(:author, :status_message => :photos).first if @photo @parent = @photo.status_message diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 0ee777a83..9ab139566 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -3,7 +3,7 @@ # the COPYRIGHT file. class PostsController < ApplicationController - skip_before_filter :set_contacts_notifications_and_status + skip_before_filter :set_contacts_notifications_unread_count_and_status skip_before_filter :count_requests skip_before_filter :set_invites skip_before_filter :set_locale @@ -11,11 +11,11 @@ class PostsController < ApplicationController skip_before_filter :set_grammatical_gender def show - @post = Post.where(:id => params[:id], :public => true).includes(:person, :comments => :person).first + @post = Post.where(:id => params[:id], :public => true).includes(:author, :comments => :author).first if @post @landing_page = true - @person = @post.person + @person = @post.author if @person.owner_id I18n.locale = @person.owner.language render "posts/#{@post.class.to_s.underscore}", :layout => true diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 54c892a9a..79267c8cc 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -14,7 +14,7 @@ class ProfilesController < ApplicationController # upload and set new profile photo params[:profile] ||= {} params[:profile][:searchable] ||= false - params[:profile][:photo] = Photo.where(:person_id => current_user.person.id, + params[:profile][:photo] = Photo.where(:author_id => current_user.person.id, :id => params[:photo_id]).first if params[:photo_id] if current_user.update_profile params[:profile] diff --git a/app/controllers/publics_controller.rb b/app/controllers/publics_controller.rb index 64db524f6..85806c76d 100644 --- a/app/controllers/publics_controller.rb +++ b/app/controllers/publics_controller.rb @@ -6,7 +6,7 @@ class PublicsController < ApplicationController require File.join(Rails.root, '/lib/diaspora/parser') include Diaspora::Parser - skip_before_filter :set_contacts_notifications_and_status, :except => [:create, :update] + skip_before_filter :set_contacts_notifications_unread_count_and_status, :except => [:create, :update] skip_before_filter :count_requests skip_before_filter :set_invites skip_before_filter :set_locale diff --git a/app/controllers/status_messages_controller.rb b/app/controllers/status_messages_controller.rb index 7333389b8..33bc0d975 100644 --- a/app/controllers/status_messages_controller.rb +++ b/app/controllers/status_messages_controller.rb @@ -52,7 +52,7 @@ class StatusMessagesController < ApplicationController photos.update_all(:status_message_id => nil) end respond_to do |format| - format.js { render :json =>{:errors => @status_message.errors.full_messages}, :status => 406 } + format.js { render :json =>{:errors => @status_message.errors.full_messages}, :status => 406 } format.html {redirect_to :back} end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 81e013ebf..e3927d237 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -13,11 +13,10 @@ module ApplicationHelper def page_title text=nil title = "" if text.blank? - title = "#{current_user.name} | " if current_user + title = "#{current_user.name}" if current_user else - title = "#{text} | " + title = "#{text}" end - title += "DIASPORA*" end def aspects_with_post aspects, post @@ -133,8 +132,8 @@ module ApplicationHelper "\"#{h(person.name)}\"".html_safe end - def person_link(person) - " + def person_link(person, opts={}) + " #{h(person.name)} ".html_safe end diff --git a/app/helpers/conversations_helper.rb b/app/helpers/conversations_helper.rb new file mode 100644 index 000000000..4c7e59115 --- /dev/null +++ b/app/helpers/conversations_helper.rb @@ -0,0 +1,9 @@ +module ConversationsHelper + def new_message_text(count) + if count > 0 + t('new_messages', :count => count) + else + t('no_new_messages') + end + end +end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 788d7ab03..44740644a 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -22,7 +22,7 @@ module NotificationsHelper elsif note.instance_of?(Notifications::AlsoCommented) post = Post.where(:id => note.target_id).first if post - "#{translation(target_type, post.person.name)} #{link_to t('notifications.post'), object_path(post)}".html_safe + "#{translation(target_type, post.author.name)} #{link_to t('notifications.post'), object_path(post)}".html_safe else t('notifications.also_commented_deleted') end diff --git a/app/helpers/private_messages_helper.rb b/app/helpers/private_messages_helper.rb new file mode 100644 index 000000000..1c849b6ab --- /dev/null +++ b/app/helpers/private_messages_helper.rb @@ -0,0 +1,2 @@ +module PrivateMessagesHelper +end diff --git a/app/helpers/sockets_helper.rb b/app/helpers/sockets_helper.rb index 8010d699b..8202ee9f3 100644 --- a/app/helpers/sockets_helper.rb +++ b/app/helpers/sockets_helper.rb @@ -26,11 +26,11 @@ module SocketsHelper if object.is_a? StatusMessage post_hash = {:post => object, - :person => object.person, + :author => object.author, :photos => object.photos, :comments => object.comments.map{|c| {:comment => c, - :person => c.person + :author => c.author } }, :current_user => user, @@ -48,7 +48,7 @@ module SocketsHelper v = render_to_string(:partial => 'people/person', :locals => person_hash) elsif object.is_a? Comment - v = render_to_string(:partial => 'comments/comment', :locals => {:comment => object, :person => object.person}) + v = render_to_string(:partial => 'comments/comment', :locals => {:comment => object, :person => object.author}) elsif object.is_a? Notification v = render_to_string(:partial => 'notifications/popup', :locals => {:note => object, :person => opts[:actor]}) @@ -69,12 +69,12 @@ module SocketsHelper if object.is_a? Comment post = object.post action_hash[:comment_id] = object.id - action_hash[:my_post?] = (post.person.owner_id == uid) + action_hash[:my_post?] = (post.author.owner_id == uid) action_hash[:post_guid] = post.guid end - action_hash[:mine?] = object.person && (object.person.owner_id == uid) if object.respond_to?(:person) + action_hash[:mine?] = object.author && (object.author.owner_id == uid) if object.respond_to?(:author) I18n.locale = old_locale unless user.nil? diff --git a/app/mailers/notifier.rb b/app/mailers/notifier.rb index 13b90c43e..ce10e1825 100644 --- a/app/mailers/notifier.rb +++ b/app/mailers/notifier.rb @@ -84,7 +84,7 @@ class Notifier < ActionMailer::Base @receiver = User.find_by_id(recipient_id) @sender = Person.find_by_id(sender_id) @comment = Comment.find_by_id(comment_id) - @post_author_name = @comment.post.person.name + @post_author_name = @comment.post.author.name log_mail(recipient_id, sender_id, 'comment_on_post') @@ -97,6 +97,24 @@ class Notifier < ActionMailer::Base end end + def private_message(recipient_id, sender_id, message_id) + @receiver = User.find_by_id(recipient_id) + @sender = Person.find_by_id(sender_id) + @message = Message.find_by_id(message_id) + @conversation = @message.conversation + @participants = @conversation.participants + + + log_mail(recipient_id, sender_id, 'private_message') + + attachments.inline['logo_caps.png'] = ATTACHMENT + + I18n.with_locale(@receiver.language) do + mail(:to => "\"#{@receiver.name}\" <#{@receiver.email}>", + :subject => I18n.t('notifier.private_message.subject', :name => @sender.name), :host => AppConfig[:pod_uri].host) + end + end + private def log_mail recipient_id, sender_id, type log_string = "event=mail mail_type=#{type} recipient_id=#{recipient_id} sender_id=#{sender_id}" diff --git a/app/models/comment.rb b/app/models/comment.rb index 334890632..08ec011e7 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -7,19 +7,18 @@ class Comment < ActiveRecord::Base require File.join(Rails.root, 'lib/youtube_titles') include YoutubeTitles include ROXML + include Diaspora::Webhooks - include Encryptable - include Diaspora::Socketable + include Diaspora::Relayable include Diaspora::Guid + include Diaspora::Socketable + xml_attr :text xml_attr :diaspora_handle - xml_attr :post_guid - xml_attr :creator_signature - xml_attr :post_creator_signature belongs_to :post, :touch => true - belongs_to :person + belongs_to :author, :class_name => 'Person' validates_presence_of :text, :post validates_length_of :text, :maximum => 2500 @@ -30,85 +29,31 @@ class Comment < ActiveRecord::Base self.text.strip! unless self.text.nil? end def diaspora_handle - person.diaspora_handle + self.author.diaspora_handle end def diaspora_handle= nh - self.person = Webfinger.new(nh).fetch - end - def post_guid - self.post.guid - end - def post_guid= new_post_guid - self.post = Post.where(:guid => new_post_guid).first + self.author = Webfinger.new(nh).fetch end def notification_type(user, person) - if self.post.person == user.person + if self.post.author == user.person return Notifications::CommentOnPost - elsif self.post.comments.where(:person_id => user.person.id) != [] && self.person_id != user.person.id + elsif self.post.comments.where(:author_id => user.person.id) != [] && self.author_id != user.person.id return Notifications::AlsoCommented else return false end end - def subscribers(user) - if user.owns?(self.post) - p = self.post.subscribers(user) - elsif user.owns?(self) - p = [self.post.person] - end - p + def parent_class + Post end - def receive(user, person) - local_comment = Comment.where(:guid => self.guid).first - comment = local_comment || self - - unless comment.post.person == user.person || comment.verify_post_creator_signature - Rails.logger.info("event=receive status=abort reason='comment signature not valid' recipient=#{user.diaspora_handle} sender=#{self.post.person.diaspora_handle} payload_type=#{self.class} post_id=#{self.post_id}") - return - end - - #sign comment as the post creator if you've been hit UPSTREAM - if user.owns? comment.post - comment.post_creator_signature = comment.sign_with_key(user.encryption_key) - comment.save - end - - #dispatch comment DOWNSTREAM, received it via UPSTREAM - unless user.owns?(comment) - comment.save - user.dispatch_comment(comment) - end - - comment.socket_to_user(user, :aspect_ids => comment.post.aspect_ids) - comment + def parent + self.post end - #ENCRYPTION - - - def signable_accessors - accessors = self.class.roxml_attrs.collect{|definition| - definition.accessor} - accessors.delete 'person' - accessors.delete 'creator_signature' - accessors.delete 'post_creator_signature' - accessors + def parent= parent + self.post = parent end - - def signable_string - signable_accessors.collect{|accessor| - (self.send accessor.to_sym).to_s}.join ';' - end - - def verify_post_creator_signature - verify_signature(post_creator_signature, post.person) - end - - def signature_valid? - verify_signature(creator_signature, person) - end - end diff --git a/app/models/conversation.rb b/app/models/conversation.rb new file mode 100644 index 000000000..785e80628 --- /dev/null +++ b/app/models/conversation.rb @@ -0,0 +1,72 @@ +class Conversation < ActiveRecord::Base + include ROXML + include Diaspora::Guid + include Diaspora::Webhooks + + xml_attr :subject + xml_attr :created_at + xml_attr :messages, :as => [Message] + xml_reader :diaspora_handle + xml_reader :participant_handles + + has_many :conversation_visibilities, :dependent => :destroy + has_many :participants, :class_name => 'Person', :through => :conversation_visibilities, :source => :person + has_many :messages, :order => 'created_at ASC' + + belongs_to :author, :class_name => 'Person' + + def self.create(opts={}) + opts = opts.dup + msg_opts = {:author => opts[:author], :text => opts.delete(:text)} + + cnv = super(opts) + message = Message.new(msg_opts.merge({:conversation_id => cnv.id})) + message.save + cnv + end + + def recipients + self.participants - [self.author] + end + + def diaspora_handle + self.author.diaspora_handle + end + def diaspora_handle= nh + self.author = Webfinger.new(nh).fetch + end + + def participant_handles + self.participants.map{|p| p.diaspora_handle}.join(";") + end + def participant_handles= handles + handles.split(';').each do |handle| + self.participants << Webfinger.new(handle).fetch + end + end + + def last_author + self.messages.last.author if self.messages.size > 0 + end + + def subject + self[:subject].blank? ? "no subject" : self[:subject] + end + + def subscribers(user) + self.recipients + end + + def receive(user, person) + cnv = Conversation.find_or_create_by_guid(self.attributes) + + self.participants.each do |participant| + ConversationVisibility.find_or_create_by_conversation_id_and_person_id(cnv.id, participant.id) + end + self.messages.each do |msg| + msg.conversation_id = cnv.id + received_msg = msg.receive(user, person) + Notification.notify(user, received_msg, person) if msg.respond_to?(:notification_type) + end + end +end diff --git a/app/models/conversation_visibility.rb b/app/models/conversation_visibility.rb new file mode 100644 index 000000000..da398c724 --- /dev/null +++ b/app/models/conversation_visibility.rb @@ -0,0 +1,6 @@ +class ConversationVisibility < ActiveRecord::Base + + belongs_to :conversation + belongs_to :person + +end diff --git a/app/models/jobs/mail_private_message.rb b/app/models/jobs/mail_private_message.rb new file mode 100644 index 000000000..d372d990a --- /dev/null +++ b/app/models/jobs/mail_private_message.rb @@ -0,0 +1,13 @@ +# Copyright (c) 2010, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + + +module Job + class MailPrivateMessage < Base + @queue = :mail + def self.perform_delegate(recipient_id, actor_id, target_id) + Notifier.private_message( recipient_id, actor_id, target_id).deliver + end + end +end diff --git a/app/models/mention.rb b/app/models/mention.rb index 8dbc37a3f..fcf2bbaef 100644 --- a/app/models/mention.rb +++ b/app/models/mention.rb @@ -8,14 +8,14 @@ class Mention < ActiveRecord::Base validates_presence_of :post validates_presence_of :person + after_create :notify_recipient after_destroy :delete_notification def notify_recipient - Rails.logger.info "event=mention_sent id=#{self.id} to=#{person.diaspora_handle} from=#{post.person.diaspora_handle}" - Notification.notify(person.owner, self, post.person) unless person.remote? + Rails.logger.info "event=mention_sent id=#{self.id} to=#{person.diaspora_handle} from=#{post.author.diaspora_handle}" + Notification.notify(person.owner, self, post.author) unless person.remote? end - def notification_type(*args) Notifications::Mentioned end diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 000000000..abebfc04d --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,82 @@ +class Message < ActiveRecord::Base + include ROXML + + include Diaspora::Guid + include Diaspora::Webhooks + include Diaspora::Relayable + + xml_attr :text + xml_attr :created_at + xml_reader :diaspora_handle + xml_reader :conversation_guid + + belongs_to :author, :class_name => 'Person' + belongs_to :conversation, :touch => true + + after_create do + #sign comment as commenter + self.author_signature = self.sign_with_key(self.author.owner.encryption_key) if self.author.owner + + if !self.parent.blank? && self.author.owns?(self.parent) + #sign comment as post owner + self.parent_author_signature = self.sign_with_key( self.parent.author.owner.encryption_key) if self.parent.author.owner + end + self.save! + self + end + + validate :participant_of_parent_conversation + + def diaspora_handle + self.author.diaspora_handle + end + + def diaspora_handle= nh + self.author = Webfinger.new(nh).fetch + end + + def conversation_guid + self.conversation.guid + end + + def conversation_guid= guid + if cnv = Conversation.find_by_guid(guid) + self.conversation_id = cnv.id + end + end + + def parent_class + Conversation + end + + def parent + self.conversation + end + + def parent= parent + self.conversation = parent + end + + def after_receive(user, person) + if vis = ConversationVisibility.where(:conversation_id => self.conversation_id, :person_id => user.person.id).first + vis.unread += 1 + vis.save + self + else + raise NotVisibileException("Attempting to access a ConversationVisibility that does not exist!") + end + end + + def notification_type(user, person) + Notifications::PrivateMessage unless user.person == person + end + + private + def participant_of_parent_conversation + if self.parent && !self.parent.participants.include?(self.author) + errors[:base] << "Author is not participating in the conversation" + else + true + end + end +end diff --git a/app/models/notification.rb b/app/models/notification.rb index 6af9d9b4b..4b1c33f18 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -19,9 +19,9 @@ class Notification < ActiveRecord::Base if target.respond_to? :notification_type if note_type = target.notification_type(recipient, actor) if target.is_a? Comment - n = concatenate_or_create(recipient, target.post, actor, note_type) + n = note_type.concatenate_or_create(recipient, target.post, actor, note_type) else - n = make_notification(recipient, target, actor, note_type) + n = note_type.make_notification(recipient, target, actor, note_type) end n.email_the_user(target, actor) if n n.socket_to_user(recipient, :actor => actor) if n diff --git a/app/models/notifications/private_message.rb b/app/models/notifications/private_message.rb new file mode 100644 index 000000000..3044816c8 --- /dev/null +++ b/app/models/notifications/private_message.rb @@ -0,0 +1,15 @@ +class Notifications::PrivateMessage < Notification + def mail_job + Job::MailPrivateMessage + end + def translation_key + 'private_message' + end + def self.make_notification(recipient, target, actor, notification_type) + n = notification_type.new(:target => target, + :recipient_id => recipient.id) + + n.actors << actor + n + end +end diff --git a/app/models/person.rb b/app/models/person.rb index d448f2911..94f0bee34 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -26,7 +26,7 @@ class Person < ActiveRecord::Base end has_many :contacts #Other people's contacts for this person - has_many :posts #his own posts + has_many :posts, :foreign_key => :author_id #his own posts belongs_to :owner, :class_name => 'User' @@ -93,8 +93,8 @@ class Person < ActiveRecord::Base end end - def owns?(post) - self == post.person + def owns?(obj) + self == obj.author end def url diff --git a/app/models/post.rb b/app/models/post.rb index 5e925e721..dd103edc0 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -3,7 +3,6 @@ # the COPYRIGHT file. class Post < ActiveRecord::Base - require File.join(Rails.root, 'lib/encryptable') require File.join(Rails.root, 'lib/diaspora/web_socket') include ApplicationHelper include ROXML @@ -18,7 +17,7 @@ class Post < ActiveRecord::Base has_many :post_visibilities has_many :aspects, :through => :post_visibilities has_many :mentions, :dependent => :destroy - belongs_to :person + belongs_to :author, :class_name => 'Person' cattr_reader :per_page @@per_page = 10 @@ -30,16 +29,16 @@ class Post < ActiveRecord::Base end def diaspora_handle= nd - self.person = Person.where(:diaspora_handle => nd).first + self.author = Person.where(:diaspora_handle => nd).first write_attribute(:diaspora_handle, nd) end def self.diaspora_initialize params new_post = self.new params.to_hash - new_post.person = params[:person] + new_post.author = params[:author] new_post.public = params[:public] if params[:public] new_post.pending = params[:pending] if params[:pending] - new_post.diaspora_handle = new_post.person.diaspora_handle + new_post.diaspora_handle = new_post.author.diaspora_handle new_post end @@ -47,7 +46,7 @@ class Post < ActiveRecord::Base { :post => { :id => self.id, - :person => self.person.as_json, + :author => self.author.as_json, } } end @@ -68,7 +67,7 @@ class Post < ActiveRecord::Base #you know about it, and it is not mutable local_post = Post.where(:guid => self.guid).first - if local_post && local_post.person_id == self.person_id + if local_post && local_post.author_id == self.author_id known_post = user.visible_posts(:guid => self.guid).first if known_post if known_post.mutable? @@ -95,7 +94,7 @@ class Post < ActiveRecord::Base protected def propogate_retraction - self.person.owner.retract(self) if self.person.owner + self.author.owner.retract(self) if self.author.owner end end diff --git a/app/models/post_visibility.rb b/app/models/post_visibility.rb index 99b520de6..d92205172 100644 --- a/app/models/post_visibility.rb +++ b/app/models/post_visibility.rb @@ -10,6 +10,6 @@ class PostVisibility < ActiveRecord::Base belongs_to :post validates_presence_of :post has_one :user, :through => :aspect - has_one :person, :through => :post + has_one :person, :through => :post, :foreign_key => :author_id end diff --git a/app/models/retraction.rb b/app/models/retraction.rb index c1717e4b0..fa8e9e0b5 100644 --- a/app/models/retraction.rb +++ b/app/models/retraction.rb @@ -54,7 +54,7 @@ class Retraction return end user.disconnected_by(self.target) - elsif self.target.nil? || self.target.person != self.person + elsif self.target.nil? || self.target.author != self.person Rails.logger.info("event=retraction status=abort reason='no post found authored by retractor' sender=#{person.diaspora_handle} post_guid=#{post_guid}") else self.perform(user) diff --git a/app/models/status_message.rb b/app/models/status_message.rb index 3c6d1778c..1e4d3dea0 100644 --- a/app/models/status_message.rb +++ b/app/models/status_message.rb @@ -88,7 +88,7 @@ class StatusMessage < Post <<-XML #{x(self.formatted_message(:plain_text => true))} - + #{person.url}posts/#{self.id} #{self.created_at.xmlschema} #{self.updated_at.xmlschema} diff --git a/app/models/user.rb b/app/models/user.rb index ed09e77af..dc366fc60 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -87,8 +87,8 @@ class User < ActiveRecord::Base ######## Posting ######## def build_post(class_name, opts = {}) - opts[:person] = self.person - opts[:diaspora_handle] = opts[:person].diaspora_handle + opts[:author] = self.person + opts[:diaspora_handle] = opts[:author].diaspora_handle model_class = class_name.to_s.camelize.constantize model_class.diaspora_initialize(opts) @@ -107,16 +107,16 @@ class User < ActiveRecord::Base end def notify_if_mentioned(post) - return unless self.contact_for(post.person) && post.respond_to?(:mentions?) + return unless self.contact_for(post.author) && post.respond_to?(:mentions?) post.notify_person(self.person) if post.mentions? self.person end def add_post_to_aspects(post) - return unless self.contact_for(post.person) + return unless self.contact_for(post.author) Rails.logger.debug("event=add_post_to_aspects user_id=#{self.id} post_id=#{post.id}") - add_to_streams(post, self.aspects_with_person(post.person)) + add_to_streams(post, self.aspects_with_person(post.author)) post end @@ -136,32 +136,26 @@ class User < ActiveRecord::Base end def salmon(post) - created_salmon = Salmon::SalmonSlap.create(self, post.to_diaspora_xml) - created_salmon + Salmon::SalmonSlap.create(self, post.to_diaspora_xml) end ######## Commenting ######## def build_comment(text, options = {}) - comment = Comment.new(:person_id => self.person.id, + comment = Comment.new(:author_id => self.person.id, :text => text, :post => options[:on]) comment.set_guid #sign comment as commenter - comment.creator_signature = comment.sign_with_key(self.encryption_key) + comment.author_signature = comment.sign_with_key(self.encryption_key) - if !comment.post_id.blank? && person.owns?(comment.post) + if !comment.post_id.blank? && person.owns?(comment.parent) #sign comment as post owner - comment.post_creator_signature = comment.sign_with_key(self.encryption_key) + comment.parent_author_signature = comment.sign_with_key(self.encryption_key) end comment end - def dispatch_comment(comment) - mailman = Postzord::Dispatch.new(self, comment) - mailman.post - end - ######### Mailer ####################### def mail(job, *args) unless self.disable_mail diff --git a/app/views/comments/_comment.html.haml b/app/views/comments/_comment.html.haml index 66bb9e906..ffe727553 100644 --- a/app/views/comments/_comment.html.haml +++ b/app/views/comments/_comment.html.haml @@ -3,13 +3,12 @@ -# the COPYRIGHT file. %li.comment{:data=>{:guid => comment.id}, :class => ("hidden" if(defined? hidden))} - = person_image_link(comment.person) + = person_image_link(comment.author) .content - %strong - = person_link(comment.person) - - = markdownify(comment.text, :youtube_maps => comment.youtube_titles) - - .info - %span.time + .from + = person_link(comment.author) + %time.timeago{:datetime => comment.created_at} = comment.created_at ? timeago(comment.created_at) : timeago(Time.now) + + %p + = markdownify(comment.text, :youtube_maps => comment.youtube_titles) diff --git a/app/views/conversations/_conversation.haml b/app/views/conversations/_conversation.haml new file mode 100644 index 000000000..35657566e --- /dev/null +++ b/app/views/conversations/_conversation.haml @@ -0,0 +1,21 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + +.stream_element.conversation{:data=>{:guid=>conversation.id}, :class => ('unread' if unread_counts[conversation.id].to_i > 0)} + = person_image_tag(authors[conversation.id]) + + .subject + .message_count + = conversation.messages.size + + = conversation.subject[0..30] + + .last_author + .timestamp + = time_ago_in_words(conversation.updated_at) + = authors[conversation.id].name + + - if conversation.participants.size > 2 + %span.participant_count + = "(+#{conversation.participants.size - 1})" diff --git a/app/views/conversations/_show.haml b/app/views/conversations/_show.haml new file mode 100644 index 000000000..9e71f45b7 --- /dev/null +++ b/app/views/conversations/_show.haml @@ -0,0 +1,37 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + + +.span-16.last + .conversation_participants + .span-9 + %h3 + = conversation.subject + + .conversation_controls + = link_to (image_tag('reply.png', :height => 14, :width => 14) + ' ' + t('.reply')), '#', :id => 'reply_to_conversation' + = link_to (image_tag('deletelabel.png') + ' ' + t('delete').downcase), conversation_conversation_visibility_path(conversation), :method => 'delete', :confirm => t('are_you_sure') + + .span-6.avatars.last + - for participant in conversation.participants + = person_image_link(participant) + +%br +%br +%br +%br +%br +.span-16.last + .stream + = render :partial => 'messages/message', :collection => conversation.messages + + .stream_element.new_message + = owner_image_tag + + .content + = form_for [conversation, Message.new] do |message| + = message.text_area :text, :rows => 5 + .right + = message.submit t('.reply').capitalize, :class => 'button' + = link_to t('cancel'), '#' diff --git a/app/views/conversations/index.haml b/app/views/conversations/index.haml new file mode 100644 index 000000000..1214816b5 --- /dev/null +++ b/app/views/conversations/index.haml @@ -0,0 +1,43 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + + +- content_for :head do + = include_javascripts :inbox + +- content_for :page_title do + = t('.message_inbox') + +:css + footer{ display:none;} + +#left_pane + #left_pane_header + %h3 + .right + = link_to t('.new_message'), new_conversation_path, :class => 'button', :rel => 'facebox' + Inbox + + #conversation_inbox + - if @conversations.count > 0 + .stream.conversations + = render :partial => 'conversations/conversation', :collection => @conversations, :locals => {:authors => @authors, :unread_counts => @unread_counts} + = will_paginate @conversations + - else + %br + %br + %br + %br + %div{:style => 'text-align:center;'} + %i + = t('.no_messages') + +#conversation_show.span-16.prepend-8.last + - if @conversation + = render 'conversations/show', :conversation => @conversation + - else + #no_conversation_text + = t('.no_conversation_selected') + #no_conversation_controls + = link_to t('.create_a_new_message'), new_conversation_path, :rel => 'facebox' diff --git a/app/views/conversations/new.haml b/app/views/conversations/new.haml new file mode 100644 index 000000000..84c343fb0 --- /dev/null +++ b/app/views/conversations/new.haml @@ -0,0 +1,51 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + +:javascript + $(document).ready(function () { + var data = $.parseJSON( $('#contact_json').val() ), + autocompleteInput = $("#contact_autocomplete"); + + autocompleteInput.autoSuggest(data, { + selectedItemProp: "name", + searchObjProps: "name", + asHtmlID: "contact_ids", + keyDelay: 0, + startText: '', + preFill: [{ 'name' : "#{params[:name]}", + 'value' : "#{params[:contact_id]}"}] + }); + + autocompleteInput.focus(); + }); + += hidden_field_tag :contact_json, @all_contacts_and_ids.to_json + +#new_message_pane + .span-12.last + #facebox_header + %h4 + = t('conversations.index.new_message') + + = form_for Conversation.new do |conversation| + %br + + .span-2 + %h4 + = t('.to') + .span-10.last + = text_field_tag "contact_autocomplete" + + .span-2 + %h4 + = t('.subject') + .span-10.last + = conversation.text_field :subject + + .span-10.prepend-2.last + = text_area_tag "conversation[text]", '', :rows => 5 + + .text-right + = conversation.submit t('.send'), :class => 'button' + = link_to t('cancel'), conversations_path diff --git a/app/views/conversations/show.haml b/app/views/conversations/show.haml new file mode 100644 index 000000000..b5d871dfc --- /dev/null +++ b/app/views/conversations/show.haml @@ -0,0 +1,5 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + += render 'show', :conversation => @conversation diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 6b255de63..07cac5d00 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -24,9 +24,14 @@ - if @notification_count #notification_badge = link_to "", notifications_path, :title => new_notification_text(@notification_count) - = image_tag 'icons/mail_grey.png' - #notification_badge_number{:class => ("hidden" if @notification_count == 0)} + = image_tag 'icons/monotone_flag.png', :height => 20, :width => 20 + .badge_count{:class => ("hidden" if @notification_count == 0)} = @notification_count + #message_inbox_badge + = link_to "", conversations_path , :title => new_message_text(@unread_message_count) + = image_tag 'icons/mail_grey.png', :height => 16, :width => 16 + .badge_count{:class => ("hidden" if @unread_message_count == 0)} + = @unread_message_count %ul#user_menu .right diff --git a/app/views/messages/_message.haml b/app/views/messages/_message.haml new file mode 100644 index 000000000..d7cd6ab75 --- /dev/null +++ b/app/views/messages/_message.haml @@ -0,0 +1,16 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + +.stream_element{:data=>{:guid=>message.id}} + = person_image_link(message.author, :size => :thumb_small) + + .content + .from + = person_link(message.author, :class => 'author') + %time.timeago{:datetime => message.created_at} + = how_long_ago(message) + + %p + = message.text + diff --git a/app/views/notifier/private_message.html.haml b/app/views/notifier/private_message.html.haml new file mode 100644 index 000000000..18c1b335f --- /dev/null +++ b/app/views/notifier/private_message.html.haml @@ -0,0 +1,18 @@ +%p + = t('notifier.hello', :name => @receiver.profile.first_name) +%p + = "#{@sender.name} (#{@sender.diaspora_handle})" + = t('.private_message') +%p + = t('.message_subject', :subject => @conversation.subject) +%p + = @message.text +%p + + %br + = link_to t('.sign_in'), conversation_url(@conversation) + + %br + = t('notifier.love') + %br + = t('notifier.diaspora') diff --git a/app/views/notifier/private_message.text.haml b/app/views/notifier/private_message.text.haml new file mode 100644 index 000000000..58419ab2c --- /dev/null +++ b/app/views/notifier/private_message.text.haml @@ -0,0 +1,9 @@ += t('notifier.hello', :name => @receiver.profile.first_name) += "#{@sender.name} (#{@sender.diaspora_handle})" += t('notifier.private_message.private_message') + += t('notifier.private_message.message_subject', :subject => @conversation.subject) += @message.text + += "#{t('notifier.love')} \n" += t('notifier.diaspora') diff --git a/app/views/people/show.html.haml b/app/views/people/show.html.haml index 928c90a08..d37df6b7c 100644 --- a/app/views/people/show.html.haml +++ b/app/views/people/show.html.haml @@ -2,6 +2,10 @@ -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. + +- content_for :head do + = include_javascripts :people + - content_for :page_title do = @person.name @@ -41,11 +45,14 @@ - else - .right - - if @post_type == :photos - = link_to t('layouts.header.view_profile'), person_path(@person) - - else - = link_to t('_photos'), person_photos_path(@person) + - if @contact.person + .right + = link_to 'Message', new_conversation_path(:contact_id => @contact.id, :name => @contact.person.name, :contact_id => @contact.id), :class => 'button', :rel => 'facebox' + + /- if @post_type == :photos + / = link_to t('layouts.header.view_profile'), person_path(@person) + /- else + / = link_to t('_photos'), person_photos_path(@person) %h3 = @person.name diff --git a/app/views/photos/_photo.haml b/app/views/photos/_photo.haml index a601735ae..9d8b8ed18 100644 --- a/app/views/photos/_photo.haml +++ b/app/views/photos/_photo.haml @@ -10,5 +10,5 @@ %p.photo_description = post.caption -= link_to t('.view_all', :name => post.person.name), person_photos_path(post.person), :class => "small_text" += link_to t('.view_all', :name => post.author.name), person_photos_path(post.author), :class => "small_text" diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 4b825fee3..b2075e701 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -14,7 +14,7 @@ =link_to "#{t('next')} →", @next_photo, :rel => 'prefetch', :id => 'photo_show_right' #original_post_info - = render 'shared/author_info', :person => @photo.person, :post => @photo + = render 'shared/author_info', :person => @photo.author, :post => @photo #photo_container #show_photo{:data=>{:guid=>@photo.id}} @@ -28,7 +28,7 @@ = @photo.caption - if @ownership - .photo_options{:data=>{:actor=>"#{@photo.person.owner.id}",:actor_person=>"#{@photo.person.id}",:image_url=>"#{@photo.url(:thumb_large)}"}} + .photo_options{:data=>{:actor=>"#{@photo.author.owner.id}", :actor_person => "#{@photo.author.id}", :image_url => "#{@photo.url(:thumb_large)}"}} = link_to t('.make_profile_photo'), {:controller => "photos", :action => "make_profile_photo", :photo_id => @photo.id}, :remote => true, :class => 'make_profile_photo' | = link_to t('.edit'), '#', :id => "edit_photo_toggle" diff --git a/app/views/shared/_stream_element.html.haml b/app/views/shared/_stream_element.html.haml index e70d96a28..36a28cbc7 100644 --- a/app/views/shared/_stream_element.html.haml +++ b/app/views/shared/_stream_element.html.haml @@ -3,31 +3,34 @@ -# the COPYRIGHT file. .stream_element{:data=>{:guid=>post.id}} - - if post.person.owner_id == current_user.id + - if post.author.owner_id == current_user.id .right.hidden.controls - reshare_aspects = aspects_without_post(all_aspects, post) - unless reshare_aspects.empty? = render 'shared/reshare', :aspects => reshare_aspects, :post => post = link_to image_tag('deletelabel.png'), status_message_path(post), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete", :title => t('delete') - = person_image_link(post.person, :size => :thumb_small) + = person_image_link(post.author, :size => :thumb_small) .content - %strong - = person_link(post.person) - - = render 'status_messages/status_message', :post => post, :photos => post.photos + .from + = person_link(post.author, :class => 'author') + %time.timeago{:datetime => post.created_at} + %p + = render 'status_messages/status_message', :post => post, :photos => post.photos .info - if post.public? %span.aspect_badges %span.aspect_badge.public = t('the_world') - - elsif post.person.owner_id == current_user.id + - elsif post.author.owner_id == current_user.id %span.aspect_badges = aspect_badges(aspects_with_post(all_aspects, post)) - %span.timeago= link_to(how_long_ago(post), status_message_path(post)) + %span.timeago + = link_to(how_long_ago(post), status_message_path(post)) + = link_to t('comments.new_comment.comment').downcase, '#', :class => 'focus_comment_textarea' = render "comments/comments", :post_id => post.id, :comments => post.comments, :current_user => current_user, :condensed => true, :commenting_disabled => defined?(@commenting_disabled) diff --git a/app/views/shared/_stream_element.mobile.haml b/app/views/shared/_stream_element.mobile.haml index 00746b0a9..b313860d1 100644 --- a/app/views/shared/_stream_element.mobile.haml +++ b/app/views/shared/_stream_element.mobile.haml @@ -7,11 +7,11 @@ %span.time = time_ago_in_words(post.created_at) - = person_image_link(post.person, :size => :thumb_small) + = person_image_link(post.author, :size => :thumb_small) .content .from - = person_link(post.person) + = person_link(post.author) = render 'status_messages/status_message', :post => post, :photos => post.photos diff --git a/app/views/status_messages/_new_status_message.haml b/app/views/status_messages/_new_status_message.haml deleted file mode 100644 index 00db8eefe..000000000 --- a/app/views/status_messages/_new_status_message.haml +++ /dev/null @@ -1,9 +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. - -= form_for StatusMessage.new, :remote => true do |f| - = f.error_messages - %p - = f.text_field :message, :value => t('.tell_me_something_good') - = f.submit t('.oh_yeah'), :class => 'button' diff --git a/app/views/status_messages/create.js.erb b/app/views/status_messages/create.js.erb index 8eac17206..cfd9a5ef0 100644 --- a/app/views/status_messages/create.js.erb +++ b/app/views/status_messages/create.js.erb @@ -2,7 +2,7 @@ :partial => 'shared/stream_element', :locals => { :post => @status_message, - :person => @status_message.person, + :author => @status_message.author, :photos => @status_message.photos, :comments => [], :all_aspects => current_user.aspects diff --git a/app/views/status_messages/show.html.haml b/app/views/status_messages/show.html.haml index a5dc196c4..e79b89471 100644 --- a/app/views/status_messages/show.html.haml +++ b/app/views/status_messages/show.html.haml @@ -5,7 +5,7 @@ .span-16.append-4.prepend-4.last #original_post_info - = render 'shared/author_info', :person => @status_message.person, :post => @status_message + = render 'shared/author_info', :person => @status_message.author, :post => @status_message #show_text %p diff --git a/app/views/status_messages/show.mobile.haml b/app/views/status_messages/show.mobile.haml index a5c871976..6961ae3d4 100644 --- a/app/views/status_messages/show.mobile.haml +++ b/app/views/status_messages/show.mobile.haml @@ -3,7 +3,7 @@ -# the COPYRIGHT file. #show_content{:data=>{:guid=>@status_message.id}} - = render 'shared/author_info', :person => @status_message.person, :post => @status_message + = render 'shared/author_info', :person => @status_message.author, :post => @status_message %p = markdownify(@status_message.message, :youtube_maps => @status_message[:youtube_titles]) diff --git a/config/assets.yml b/config/assets.yml index b240d33b7..6dc20d3db 100644 --- a/config/assets.yml +++ b/config/assets.yml @@ -53,9 +53,12 @@ javascripts: - public/javascripts/aspect-filters.js - public/javascripts/contact-list.js people: - - public/javascripts/contact-list.js + - public/javascripts/vendor/jquery.autoSuggest.js photos: - public/javascripts/photo-show.js + inbox: + - public/javascripts/vendor/jquery.autoSuggest.js + - public/javascripts/inbox.js stylesheets: default: @@ -65,4 +68,5 @@ stylesheets: - public/stylesheets/vendor/facebox.css - public/stylesheets/vendor/fileuploader.css - public/stylesheets/vendor/tipsy.css + - public/stylesheets/vendor/autoSuggest.css diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 4a91ed354..dafc4743b 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -27,6 +27,8 @@ en: search: "Search" new_notifications: "%{count} new notifications" no_new_notifications: "no new notifications" + new_messages: "%{count} new messages" + no_new_messages: "no new messages" _home: "Home" _more: "More" _comments: "Comments" @@ -489,6 +491,11 @@ en: subject: "%{name} has mentioned you on Diaspora*" mentioned: "mentioned you in a post:" sign_in: "Sign in to view it." + private_message: + subject: "%{name} has sent you a private message yon Diaspora*" + private_message: "has sent you a private message:" + message_subject: "Subject: %{subject}" + sign_in: "Sign in to view it." home: show: share_what_you_want: "Share what you want, with whom you want." @@ -520,3 +527,17 @@ en: fullmonth_day: "%B %d" birthday: "%B %d" birthday_with_year: "%B %d %Y" + + conversations: + index: + message_inbox: "Message Inbox" + new_message: "New Message" + no_conversation_selected: "no conversation selected" + create_a_new_message: "create a new message" + no_messages: "no messages" + show: + reply: 'reply' + new: + to: 'to' + subject: 'subject' + send: 'Send' diff --git a/config/routes.rb b/config/routes.rb index d2bdf9101..793ac7a9c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,8 @@ # the COPYRIGHT file. Diaspora::Application.routes.draw do + + resources :status_messages, :only => [:create, :destroy, :show] resources :comments, :only => [:create] resources :requests, :only => [:destroy, :create] @@ -20,7 +22,13 @@ Diaspora::Application.routes.draw do resources :posts, :only => [:show], :path => '/p/' resources :contacts - resources :aspect_memberships + resources :aspect_memberships, :only => [:destroy, :create] + + + resources :conversations do + resources :messages, :only => [:create, :show] + resource :conversation_visibility, :only => [:destroy], :path => '/visibility/' + end resources :people, :except => [:edit, :update] do resources :status_messages diff --git a/db/migrate/20110225190919_create_conversations_and_messages_and_visibilities.rb b/db/migrate/20110225190919_create_conversations_and_messages_and_visibilities.rb new file mode 100644 index 000000000..7c3f880d7 --- /dev/null +++ b/db/migrate/20110225190919_create_conversations_and_messages_and_visibilities.rb @@ -0,0 +1,39 @@ +class CreateConversationsAndMessagesAndVisibilities < ActiveRecord::Migration + def self.up + create_table :messages do |t| + t.integer :conversation_id, :null => false + t.integer :author_id, :null => false + t.string :guid, :null => false + t.text :text, :null => false + + t.timestamps + end + + create_table :conversation_visibilities do |t| + t.integer :conversation_id, :null => false + t.integer :person_id, :null => false + t.integer :unread, :null => false, :default => 0 + + t.timestamps + end + + create_table :conversations do |t| + t.string :subject + t.string :guid, :null => false + t.integer :author_id, :null => false + + t.timestamps + end + + add_index :conversation_visibilities, :person_id + add_index :conversation_visibilities, :conversation_id + add_index :conversation_visibilities, [:conversation_id, :person_id], :unique => true + add_index :messages, :author_id + end + + def self.down + drop_table :messages + drop_table :conversations + drop_table :conversation_visibilities + end +end diff --git a/db/migrate/20110228220810_rename_post_to_parent_and_creator_to_author.rb b/db/migrate/20110228220810_rename_post_to_parent_and_creator_to_author.rb new file mode 100644 index 000000000..ef43ad57a --- /dev/null +++ b/db/migrate/20110228220810_rename_post_to_parent_and_creator_to_author.rb @@ -0,0 +1,11 @@ +class RenamePostToParentAndCreatorToAuthor < ActiveRecord::Migration + def self.up + rename_column :comments, :creator_signature, :author_signature + rename_column :comments, :post_creator_signature, :parent_author_signature + end + + def self.down + rename_column :comments, :author_signature, :creator_signature + rename_column :comments, :parent_author_signature, :post_creator_signature + end +end diff --git a/db/migrate/20110228233419_add_signatures_to_message.rb b/db/migrate/20110228233419_add_signatures_to_message.rb new file mode 100644 index 000000000..e6c72e761 --- /dev/null +++ b/db/migrate/20110228233419_add_signatures_to_message.rb @@ -0,0 +1,11 @@ +class AddSignaturesToMessage < ActiveRecord::Migration + def self.up + add_column(:messages, :author_signature, :text) + add_column(:messages, :parent_author_signature, :text) + end + + def self.down + remove_column(:messages, :author_signature) + remove_column(:messages, :parent_author_signature) + end +end diff --git a/db/migrate/20110301014507_rename_person_to_author.rb b/db/migrate/20110301014507_rename_person_to_author.rb new file mode 100644 index 000000000..123392445 --- /dev/null +++ b/db/migrate/20110301014507_rename_person_to_author.rb @@ -0,0 +1,19 @@ +class RenamePersonToAuthor < ActiveRecord::Migration + def self.up + remove_foreign_key(:comments, :people) + remove_foreign_key(:posts, :people) + rename_column :comments, :person_id, :author_id + rename_column :posts, :person_id, :author_id + add_foreign_key(:comments, :people, :column => :author_id, :dependent => :delete) + add_foreign_key(:posts, :people, :column => :author_id, :dependent => :delete) + end + + def self.down + remove_foreign_key(:comments, :people, :column => :author_id) + remove_foreign_key(:posts, :people, :column => :author_id) + rename_column :comments, :author_id, :person_id + rename_column :posts, :author_id, :person_id + add_foreign_key(:comments, :people, :dependent => :delete) + add_foreign_key(:posts, :people, :dependent => :delete) + end +end diff --git a/db/schema.rb b/db/schema.rb index 197acb51f..051abe6f7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -39,21 +39,21 @@ ActiveRecord::Schema.define(:version => 20110301202619) do add_index "aspects", ["user_id"], :name => "index_aspects_on_user_id" create_table "comments", :force => true do |t| - t.text "text", :null => false - t.integer "post_id", :null => false - t.integer "person_id", :null => false - t.string "guid", :null => false - t.text "creator_signature" - t.text "post_creator_signature" + t.text "text", :null => false + t.integer "post_id", :null => false + t.integer "author_id", :null => false + t.string "guid", :null => false + t.text "author_signature" + t.text "parent_author_signature" t.text "youtube_titles" t.datetime "created_at" t.datetime "updated_at" t.string "mongo_id" end + add_index "comments", ["author_id"], :name => "index_comments_on_person_id" add_index "comments", ["guid"], :name => "index_comments_on_guid", :unique => true add_index "comments", ["mongo_id"], :name => "index_comments_on_mongo_id" - add_index "comments", ["person_id"], :name => "index_comments_on_person_id" add_index "comments", ["post_id"], :name => "index_comments_on_post_id" create_table "contacts", :force => true do |t| @@ -70,6 +70,26 @@ ActiveRecord::Schema.define(:version => 20110301202619) do add_index "contacts", ["user_id", "pending"], :name => "index_contacts_on_user_id_and_pending" add_index "contacts", ["user_id", "person_id"], :name => "index_contacts_on_user_id_and_person_id", :unique => true + create_table "conversation_visibilities", :force => true do |t| + t.integer "conversation_id", :null => false + t.integer "person_id", :null => false + t.integer "unread", :default => 0, :null => false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "conversation_visibilities", ["conversation_id", "person_id"], :name => "index_conversation_visibilities_on_conversation_id_and_person_id", :unique => true + add_index "conversation_visibilities", ["conversation_id"], :name => "index_conversation_visibilities_on_conversation_id" + add_index "conversation_visibilities", ["person_id"], :name => "index_conversation_visibilities_on_person_id" + + create_table "conversations", :force => true do |t| + t.string "subject" + t.string "guid", :null => false + t.integer "author_id", :null => false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "invitations", :force => true do |t| t.text "message" t.integer "sender_id", :null => false @@ -94,6 +114,19 @@ ActiveRecord::Schema.define(:version => 20110301202619) do add_index "mentions", ["person_id"], :name => "index_mentions_on_person_id" add_index "mentions", ["post_id"], :name => "index_mentions_on_post_id" + create_table "messages", :force => true do |t| + t.integer "conversation_id", :null => false + t.integer "author_id", :null => false + t.string "guid", :null => false + t.text "text", :null => false + t.datetime "created_at" + t.datetime "updated_at" + t.text "author_signature" + t.text "parent_author_signature" + end + + add_index "messages", ["author_id"], :name => "index_messages_on_author_id" + create_table "mongo_aspect_memberships", :force => true do |t| t.string "aspect_mongo_id" t.string "contact_mongo_id" @@ -346,7 +379,7 @@ ActiveRecord::Schema.define(:version => 20110301202619) do add_index "post_visibilities", ["post_id"], :name => "index_post_visibilities_on_post_id" create_table "posts", :force => true do |t| - t.integer "person_id", :null => false + t.integer "author_id", :null => false t.boolean "public", :default => false, :null => false t.string "diaspora_handle" t.string "guid", :null => false @@ -365,9 +398,9 @@ ActiveRecord::Schema.define(:version => 20110301202619) do t.string "mongo_id" end + add_index "posts", ["author_id"], :name => "index_posts_on_person_id" add_index "posts", ["guid"], :name => "index_posts_on_guid" add_index "posts", ["mongo_id"], :name => "index_posts_on_mongo_id" - add_index "posts", ["person_id"], :name => "index_posts_on_person_id" add_index "posts", ["status_message_id", "pending"], :name => "index_posts_on_status_message_id_and_pending" add_index "posts", ["status_message_id"], :name => "index_posts_on_status_message_id" add_index "posts", ["type", "pending", "id"], :name => "index_posts_on_type_and_pending_and_id" @@ -462,7 +495,7 @@ ActiveRecord::Schema.define(:version => 20110301202619) do add_foreign_key "aspect_memberships", "aspects", :name => "aspect_memberships_aspect_id_fk" add_foreign_key "aspect_memberships", "contacts", :name => "aspect_memberships_contact_id_fk", :dependent => :delete - add_foreign_key "comments", "people", :name => "comments_person_id_fk", :dependent => :delete + add_foreign_key "comments", "people", :name => "comments_author_id_fk", :column => "author_id", :dependent => :delete add_foreign_key "comments", "posts", :name => "comments_post_id_fk", :dependent => :delete add_foreign_key "contacts", "people", :name => "contacts_person_id_fk", :dependent => :delete @@ -472,7 +505,7 @@ ActiveRecord::Schema.define(:version => 20110301202619) do add_foreign_key "notification_actors", "notifications", :name => "notification_actors_notification_id_fk", :dependent => :delete - add_foreign_key "posts", "people", :name => "posts_person_id_fk", :dependent => :delete + add_foreign_key "posts", "people", :name => "posts_author_id_fk", :column => "author_id", :dependent => :delete add_foreign_key "profiles", "people", :name => "profiles_person_id_fk", :dependent => :delete diff --git a/lib/diaspora/exporter.rb b/lib/diaspora/exporter.rb index f18151413..adb33d26d 100644 --- a/lib/diaspora/exporter.rb +++ b/lib/diaspora/exporter.rb @@ -37,7 +37,7 @@ module Diaspora #} xml.post_ids { - aspect.posts.find_all_by_person_id(user_person_id).each do |post| + aspect.posts.find_all_by_author_id(user_person_id).each do |post| xml.post_id post.id end } @@ -64,7 +64,7 @@ module Diaspora } xml.posts { - user.raw_visible_posts.find_all_by_person_id(user_person_id).each do |post| + user.raw_visible_posts.find_all_by_author_id(user_person_id).each do |post| #post.comments.each do |comment| # post_doc << comment.to_xml #end diff --git a/lib/diaspora/relayable.rb b/lib/diaspora/relayable.rb new file mode 100644 index 000000000..43a4c44c2 --- /dev/null +++ b/lib/diaspora/relayable.rb @@ -0,0 +1,135 @@ +# Copyright (c) 2010, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +module Diaspora + module Relayable + + def self.included(model) + model.class_eval do + #these fields must be in the schema for a relayable model + xml_attr :parent_guid + xml_attr :parent_author_signature + xml_attr :author_signature + end + end + + def relayable + true + end + + def parent_guid + self.parent.guid + end + def parent_guid= new_parent_guid + self.parent = parent_class.where(:guid => new_parent_guid).first + end + + def subscribers(user) + if user.owns?(self.parent) + self.parent.subscribers(user) + elsif user.owns?(self) + [self.parent.author] + end + end + + def receive(user, person) + object = self.class.where(:guid => self.guid).first || self + + unless object.parent.author == user.person || object.verify_parent_author_signature + Rails.logger.info("event=receive status=abort reason='object signature not valid' recipient=#{user.diaspora_handle} sender=#{self.parent.author.diaspora_handle} payload_type=#{self.class} parent_id=#{self.parent.id}") + return + end + + #sign object as the parent creator if you've been hit UPSTREAM + if user.owns? object.parent + object.parent_author_signature = object.sign_with_key(user.encryption_key) + object.save! + end + + #dispatch object DOWNSTREAM, received it via UPSTREAM + unless user.owns?(object) + object.save! + Postzord::Dispatch.new(user, object).post + end + + object.socket_to_user(user, :aspect_ids => object.parent.aspect_ids) if object.respond_to? :socket_to_user + if object.after_receive(user, person) + object + end + end + + def after_receive(user, person) + self + end + + def signable_string + raise NotImplementedException("Override this in your encryptable class") + end + + def signature_valid? + verify_signature(creator_signature, self.author) + end + + def verify_signature(signature, person) + if person.nil? + Rails.logger.info("event=verify_signature status=abort reason=no_person guid=#{self.guid} model_id=#{self.id}") + return false + elsif person.public_key.nil? + Rails.logger.info("event=verify_signature status=abort reason=no_key guid=#{self.guid} model_id=#{self.id}") + return false + elsif signature.nil? + Rails.logger.info("event=verify_signature status=abort reason=no_signature guid=#{self.guid} model_id=#{self.id}") + return false + end + log_string = "event=verify_signature status=complete model_id=#{id}" + validity = person.public_key.verify "SHA", Base64.decode64(signature), signable_string + log_string += " validity=#{validity}" + Rails.logger.info(log_string) + validity + end + + def sign_with_key(key) + sig = Base64.encode64(key.sign "SHA", signable_string) + Rails.logger.info("event=sign_with_key status=complete model_id=#{id}") + sig + end + + def signable_accessors + accessors = self.class.roxml_attrs.collect do |definition| + definition.accessor + end + ['author_signature', 'parent_author_signature'].each do |acc| + accessors.delete acc + end + accessors + end + + def signable_string + signable_accessors.collect{ |accessor| + (self.send accessor.to_sym).to_s + }.join(';') + end + + def verify_parent_author_signature + verify_signature(self.parent_author_signature, self.parent.author) + end + + def signature_valid? + verify_signature(self.author_signature, self.author) + end + + + def parent_class + raise NotImplementedError.new('you must override parent_class in order to enable relayable on this model') + end + + def parent + raise NotImplementedError.new('you must override parent in order to enable relayable on this model') + end + + def parent= parent + raise NotImplementedError.new('you must override parent= in order to enable relayable on this model') + end + end +end diff --git a/lib/diaspora/user/connecting.rb b/lib/diaspora/user/connecting.rb index e3a0c35fe..a21014365 100644 --- a/lib/diaspora/user/connecting.rb +++ b/lib/diaspora/user/connecting.rb @@ -84,9 +84,9 @@ module Diaspora def remove_contact(contact) bad_person_id = contact.person_id - posts = raw_visible_posts.where(:person_id => bad_person_id).all + posts = raw_visible_posts.where(:author_id => bad_person_id).all visibilities = PostVisibility.joins(:post, :aspect).where( - :posts => {:person_id => bad_person_id}, + :posts => {:author_id => bad_person_id}, :aspects => {:user_id => self.id} ) visibility_ids = visibilities.map{|v| v.id} diff --git a/lib/diaspora/user/querying.rb b/lib/diaspora/user/querying.rb index 3a23a31c4..25c90d0f2 100644 --- a/lib/diaspora/user/querying.rb +++ b/lib/diaspora/user/querying.rb @@ -7,7 +7,7 @@ module Diaspora module Querying def find_visible_post_by_id( id ) - self.raw_visible_posts.where(:id => id).includes({:person => :profile}, {:comments => {:person => :profile}}, :photos).first + self.raw_visible_posts.where(:id => id).includes({:author => :profile}, {:comments => {:author => :profile}}, :photos).first end def raw_visible_posts diff --git a/lib/encryptable.rb b/lib/encryptable.rb deleted file mode 100644 index 06cb31014..000000000 --- a/lib/encryptable.rb +++ /dev/null @@ -1,39 +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. - -module Encryptable - def signable_string - raise NotImplementedException("Override this in your encryptable class") - end - - def signature_valid? - verify_signature(creator_signature, person) - end - - def verify_signature(signature, person) - if person.nil? - Rails.logger.info("event=verify_signature status=abort reason=no_person guid=#{self.guid} model_id=#{self.id}") - return false - elsif person.public_key.nil? - Rails.logger.info("event=verify_signature status=abort reason=no_key guid=#{self.guid} model_id=#{self.id}") - return false - elsif signature.nil? - Rails.logger.info("event=verify_signature status=abort reason=no_signature guid=#{self.guid} model_id=#{self.id}") - return false - end - log_string = "event=verify_signature status=complete model_id=#{id}" - validity = person.public_key.verify "SHA", Base64.decode64(signature), signable_string - log_string += " validity=#{validity}" - Rails.logger.info(log_string) - validity - end - - def sign_with_key(key) - sig = Base64.encode64(key.sign "SHA", signable_string) - Rails.logger.info("event=sign_with_key status=complete model_id=#{id}") - sig - end - -end - diff --git a/lib/fake.rb b/lib/fake.rb index 27a502de6..099688315 100644 --- a/lib/fake.rb +++ b/lib/fake.rb @@ -3,15 +3,15 @@ class PostsFake delegate :length, :each, :to_ary, :to => :post_fakes def initialize(posts) - person_ids = [] + author_ids = [] posts.each do |p| - person_ids << p.person_id + author_ids << p.author_id p.comments.each do |c| - person_ids << c.person_id + author_ids << c.author_id end end - people = Person.where(:id => person_ids).includes(:profile) + people = Person.where(:id => author_ids).includes(:profile) @people_hash = {} people.each{|person| @people_hash[person.id] = person} diff --git a/lib/postzord/dispatch.rb b/lib/postzord/dispatch.rb index 244f1bb5d..4b3833032 100644 --- a/lib/postzord/dispatch.rb +++ b/lib/postzord/dispatch.rb @@ -22,11 +22,11 @@ class Postzord::Dispatch unless @subscribers == nil remote_people, local_people = @subscribers.partition{ |person| person.owner_id.nil? } - if @object.is_a?(Comment) && @sender.owns?(@object.post) + if @object.respond_to?(:relayable) && @sender.owns?(@object.parent) user_ids = [*local_people].map{|x| x.owner_id } local_users = User.where(:id => user_ids) self.notify_users(local_users) - local_users << @sender if @object.person.local? + local_users << @sender if @object.author.local? self.socket_to_users(local_users) else self.deliver_to_local(local_people) @@ -73,7 +73,7 @@ class Postzord::Dispatch def notify_users(users) users.each do |user| - Resque.enqueue(Job::NotifyLocalUsers, user.id, @object.class.to_s, @object.id, @object.person_id) + Resque.enqueue(Job::NotifyLocalUsers, user.id, @object.class.to_s, @object.id, @object.author.id) end end def socket_to_users(users) diff --git a/lib/postzord/receiver.rb b/lib/postzord/receiver.rb index bfc56ab11..c61039bf0 100644 --- a/lib/postzord/receiver.rb +++ b/lib/postzord/receiver.rb @@ -49,9 +49,9 @@ module Postzord end def xml_author - if @object.is_a?(Comment) + if @object.respond_to?(:relayable) #if A and B are friends, and A sends B a comment from C, we delegate the validation to the owner of the post being commented on - xml_author = @user.owns?(@object.post) ? @object.diaspora_handle : @object.post.person.diaspora_handle + xml_author = @user.owns?(@object.parent) ? @object.diaspora_handle : @object.parent.author.diaspora_handle @author = Webfinger.new(@object.diaspora_handle).fetch else xml_author = @object.diaspora_handle @@ -71,7 +71,7 @@ module Postzord end # abort if we haven't received the post to a comment - if @object.is_a?(Comment) && @object.post.nil? + if @object.respond_to?(:relayable) && @object.parent.nil? Rails.logger.info("event=receive status=abort reason='received a comment but no corresponding post' recipient=#{@user_person.diaspora_handle} sender=#{@sender.diaspora_handle} payload_type=#{@object.class})") return false end @@ -82,6 +82,7 @@ module Postzord end if @author + @object.author = @author if @object.respond_to? :author= @object.person = @author if @object.respond_to? :person= end diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 4da04ce86..77e4b4022 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -38,9 +38,7 @@ namespace :db do puts "Purging the database for #{Rails.env}..." - # Specifiy what models to remove - # No! Drop the fucking database. - MongoMapper::connection.drop_database(MongoMapper::database.name) + Rake::Task['db:rebuild'].invoke puts 'Deleting tmp folder...' `rm -rf #{File.dirname(__FILE__)}/../../public/uploads/*` @@ -51,17 +49,10 @@ namespace :db do puts "Resetting the database for #{Rails.env}".upcase Rake::Task['db:purge'].invoke - Rake::Task['db:seed:dev'].invoke + Rake::Task['db:seed'].invoke puts "Success!" end - task :reset_dev do - puts "making a new base user" - Rake::Task['db:purge'].invoke - Rake::Task['db:seed:dev'].invoke - puts "you did it!" - end - desc "Purge database and then add the first user" task :first_user, :username, :password, :email do |t, args| Rake::Task['db:purge'].invoke diff --git a/public/images/icons/monotone_flag.png b/public/images/icons/monotone_flag.png new file mode 100644 index 000000000..baea94936 Binary files /dev/null and b/public/images/icons/monotone_flag.png differ diff --git a/public/images/reply.png b/public/images/reply.png new file mode 100644 index 000000000..2356dc779 Binary files /dev/null and b/public/images/reply.png differ diff --git a/public/javascripts/inbox.js b/public/javascripts/inbox.js new file mode 100644 index 000000000..1172b61c7 --- /dev/null +++ b/public/javascripts/inbox.js @@ -0,0 +1,79 @@ +/* Copyright (c) 2010, Diaspora Inc. This file is + * licensed under the Affero General Public License version 3 or later. See + * the COPYRIGHT file. + */ + +$(document).ready(function(){ + + var bindIt = function(element){ + var conversationSummary = element, + conversationGuid = conversationSummary.attr('data-guid'); + $.get("conversations/"+conversationGuid, function(data){ + + $('.conversation', '.stream').removeClass('selected'); + conversationSummary.addClass('selected').removeClass('unread'); + $('#conversation_show').html(data); + Diaspora.widgets.timeago.updateTimeAgo(); + }); + + if (typeof(history.pushState) == 'function') { + history.pushState(null, document.title, '?conversation_id='+conversationGuid); + } + } + + $('.conversation', '.stream').bind('mousedown', function(){ + bindIt($(this)); + }); + + resize(); + $(window).resize(function(){ + resize(); + }); + + $('#conversation_inbox .stream').infinitescroll({ + navSelector : ".pagination", + // selector for the paged navigation (it will be hidden) + nextSelector : ".pagination a.next_page", + // selector for the NEXT link (to page 2) + itemSelector : "#conversation_inbox .conversation", + // selector for all items you'll retrieve + localMode: true, + debug: false, + donetext: "no more.", + loadingText: "", + loadingImg: '/images/ajax-loader.gif' + }, function(){ + $('.conversation', '.stream').bind('mousedown', function(){ + bindIt($(this)); + }); + }); + + // kill scroll binding + $(window).unbind('.infscr'); + + // hook up the manual click guy. + $('a.next_page').click(function(){ + $(document).trigger('retrieve.infscr'); + return false; + }); + + // remove the paginator when we're done. + $(document).ajaxError(function(e,xhr,opt){ + if (xhr.status == 404) $('a.next_page').remove(); + }); + + $('#reply_to_conversation').live('click', function(evt) { + evt.preventDefault(); + $('html, body').animate({scrollTop:$(window).height()}, 'medium', function(){ + $('#message_text').focus(); + }); + }); +}); + +var resize = function(){ + var inboxSidebar = $('#conversation_inbox'); + inboxSidebarOffset = inboxSidebar.offset().top, + windowHeight = $(window).height(); + + inboxSidebar.css('height', windowHeight - inboxSidebarOffset); +}; diff --git a/public/javascripts/vendor/jquery.autoSuggest.js b/public/javascripts/vendor/jquery.autoSuggest.js new file mode 100644 index 000000000..bafd35cbc --- /dev/null +++ b/public/javascripts/vendor/jquery.autoSuggest.js @@ -0,0 +1,372 @@ + /* + * AutoSuggest + * Copyright 2009-2010 Drew Wilson + * www.drewwilson.com + * code.drewwilson.com/entry/autosuggest-jquery-plugin + * + * Version 1.4 - Updated: Mar. 23, 2010 + * + * This Plug-In will auto-complete or auto-suggest completed search queries + * for you as you type. You can add multiple selections and remove them on + * the fly. It supports keybord navigation (UP + DOWN + RETURN), as well + * as multiple AutoSuggest fields on the same page. + * + * Inspied by the Autocomplete plugin by: Jšrn Zaefferer + * and the Facelist plugin by: Ian Tearle (iantearle.com) + * + * This AutoSuggest jQuery plug-in is dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +(function($){ + $.fn.autoSuggest = function(data, options) { + var defaults = { + asHtmlID: false, + startText: "Enter Name Here", + emptyText: "No Results Found", + preFill: {}, + limitText: "No More Selections Are Allowed", + selectedItemProp: "value", //name of object property + selectedValuesProp: "value", //name of object property + searchObjProps: "value", //comma separated list of object property names + queryParam: "q", + retrieveLimit: false, //number for 'limit' param on ajax request + extraParams: "", + matchCase: false, + minChars: 1, + keyDelay: 400, + resultsHighlight: true, + neverSubmit: false, + selectionLimit: false, + showResultList: true, + start: function(){}, + selectionClick: function(elem){}, + selectionAdded: function(elem){}, + selectionRemoved: function(elem){ elem.remove(); }, + formatList: false, //callback function + beforeRetrieve: function(string){ return string; }, + retrieveComplete: function(data){ return data; }, + resultClick: function(data){}, + resultsComplete: function(){} + }; + var opts = $.extend(defaults, options); + + var d_type = "object"; + var d_count = 0; + if(typeof data == "string") { + d_type = "string"; + var req_string = data; + } else { + var org_data = data; + for (k in data) if (data.hasOwnProperty(k)) d_count++; + } + if((d_type == "object" && d_count > 0) || d_type == "string"){ + return this.each(function(x){ + if(!opts.asHtmlID){ + x = x+""+Math.floor(Math.random()*100); //this ensures there will be unique IDs on the page if autoSuggest() is called multiple times + var x_id = "as-input-"+x; + } else { + x = opts.asHtmlID; + var x_id = x; + } + opts.start.call(this); + var input = $(this); + input.attr("autocomplete","off").addClass("as-input").attr("id",x_id).val(opts.startText); + var input_focus = false; + + // Setup basic elements and render them to the DOM + input.wrap('').wrap('
  • '); + var selections_holder = $("#as-selections-"+x); + var org_li = $("#as-original-"+x); + var results_holder = $('
    ').hide(); + var results_ul = $(''); + var values_input = $(''); + var prefill_value = ""; + if(typeof opts.preFill == "string"){ + var vals = opts.preFill.split(","); + for(var i=0; i < vals.length; i++){ + var v_data = {}; + v_data[opts.selectedValuesProp] = vals[i]; + if(vals[i] != ""){ + add_selected_item(v_data, "000"+i); + } + } + prefill_value = opts.preFill; + } else { + prefill_value = ""; + var prefill_count = 0; + for (k in opts.preFill) if (opts.preFill.hasOwnProperty(k)) prefill_count++; + if(prefill_count > 0){ + for(var i=0; i < prefill_count; i++){ + var new_v = opts.preFill[i][opts.selectedValuesProp]; + if(new_v == undefined){ new_v = ""; } + prefill_value = prefill_value+new_v+","; + if(new_v != ""){ + add_selected_item(opts.preFill[i], "000"+i); + } + } + } + } + if(prefill_value != ""){ + input.val(""); + var lastChar = prefill_value.substring(prefill_value.length-1); + if(lastChar != ","){ prefill_value = prefill_value+","; } + values_input.val(","+prefill_value); + $("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected"); + } + input.after(values_input); + selections_holder.click(function(){ + input_focus = true; + input.focus(); + }).mousedown(function(){ input_focus = false; }).after(results_holder); + + var timeout = null; + var prev = ""; + var totalSelections = 0; + var tab_press = false; + + // Handle input field events + input.focus(function(){ + if($(this).val() == opts.startText && values_input.val() == ""){ + $(this).val(""); + } else if(input_focus){ + $("li.as-selection-item", selections_holder).removeClass("blur"); + if($(this).val() != ""){ + results_ul.css("width",selections_holder.outerWidth()); + results_holder.show(); + } + } + input_focus = true; + return true; + }).blur(function(){ + if($(this).val() == "" && values_input.val() == "" && prefill_value == ""){ + $(this).val(opts.startText); + } else if(input_focus){ + $("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected"); + results_holder.hide(); + } + }).keydown(function(e) { + // track last key pressed + lastKeyPressCode = e.keyCode; + first_focus = false; + switch(e.keyCode) { + case 38: // up + e.preventDefault(); + moveSelection("up"); + break; + case 40: // down + e.preventDefault(); + moveSelection("down"); + break; + case 8: // delete + if(input.val() == ""){ + var last = values_input.val().split(","); + last = last[last.length - 2]; + selections_holder.children().not(org_li.prev()).removeClass("selected"); + if(org_li.prev().hasClass("selected")){ + values_input.val(values_input.val().replace(","+last+",",",")); + opts.selectionRemoved.call(this, org_li.prev()); + } else { + opts.selectionClick.call(this, org_li.prev()); + org_li.prev().addClass("selected"); + } + } + if(input.val().length == 1){ + results_holder.hide(); + prev = ""; + } + if($(":visible",results_holder).length > 0){ + if (timeout){ clearTimeout(timeout); } + timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay); + } + break; + /*case 9: case 188: // tab or comma + tab_press = true; + var i_input = input.val().replace(/(,)/g, ""); + if(i_input != "" && values_input.val().search(","+i_input+",") < 0 && i_input.length >= opts.minChars){ + e.preventDefault(); + var n_data = {}; + n_data[opts.selectedItemProp] = i_input; + n_data[opts.selectedValuesProp] = i_input; + var lis = $("li", selections_holder).length; + add_selected_item(n_data, "00"+(lis+1)); + input.val(""); + }*/ + case 9: // tab + if(input.val() == ''){ + break; + } + case 13: case 188: // return, comma + tab_press = false; + var active = $("li.active:first", results_holder); + if(active.length > 0){ + active.click(); + results_holder.hide(); + } + if(opts.neverSubmit || active.length > 0){ + e.preventDefault(); + } + break; + default: + if(opts.showResultList){ + if(opts.selectionLimit && $("li.as-selection-item", selections_holder).length >= opts.selectionLimit){ + results_ul.html('
  • '+opts.limitText+'
  • '); + results_holder.show(); + } else { + if (timeout){ clearTimeout(timeout); } + timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay); + } + } + break; + } + }); + + function keyChange() { + // ignore if the following keys are pressed: [del] [shift] [capslock] + if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ){ return results_holder.hide(); } + var string = input.val().replace(/[\\]+|[\/]+/g,""); + if (string == prev) return; + prev = string; + if (string.length >= opts.minChars) { + selections_holder.addClass("loading"); + if(d_type == "string"){ + var limit = ""; + if(opts.retrieveLimit){ + limit = "&limit="+encodeURIComponent(opts.retrieveLimit); + } + if(opts.beforeRetrieve){ + string = opts.beforeRetrieve.call(this, string); + } + $.getJSON(req_string+"?"+opts.queryParam+"="+encodeURIComponent(string)+limit+opts.extraParams, function(data){ + d_count = 0; + var new_data = opts.retrieveComplete.call(this, data); + for (k in new_data) if (new_data.hasOwnProperty(k)) d_count++; + processData(new_data, string); + }); + } else { + if(opts.beforeRetrieve){ + string = opts.beforeRetrieve.call(this, string); + } + processData(org_data, string); + } + } else { + selections_holder.removeClass("loading"); + results_holder.hide(); + } + } + var num_count = 0; + function processData(data, query){ + if (!opts.matchCase){ query = query.toLowerCase(); } + var matchCount = 0; + results_holder.html(results_ul.html("")).hide(); + for(var i=0;i').click(function(){ + var raw_data = $(this).data("data"); + var number = raw_data.num; + if($("#as-selection-"+number, selections_holder).length <= 0 && !tab_press){ + var data = raw_data.attributes; + input.val("").focus(); + prev = ""; + add_selected_item(data, number); + opts.resultClick.call(this, raw_data); + results_holder.hide(); + } + tab_press = false; + }).mousedown(function(){ input_focus = false; }).mouseover(function(){ + $("li", results_ul).removeClass("active"); + $(this).addClass("active"); + }).data("data",{attributes: data[num], num: num_count}); + var this_data = $.extend({},data[num]); + if (!opts.matchCase){ + var regx = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "gi"); + } else { + var regx = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "g"); + } + + if(opts.resultsHighlight){ + this_data[opts.selectedItemProp] = this_data[opts.selectedItemProp].replace(regx,"$1"); + } + if(!opts.formatList){ + formatted = formatted.html(this_data[opts.selectedItemProp]); + } else { + formatted = opts.formatList.call(this, this_data, formatted); + } + results_ul.append(formatted); + delete this_data; + matchCount++; + if(opts.retrieveLimit && opts.retrieveLimit == matchCount ){ break; } + } + } + selections_holder.removeClass("loading"); + if(matchCount <= 0){ + results_ul.html('
  • '+opts.emptyText+'
  • '); + } + results_ul.css("width", selections_holder.outerWidth()); + results_holder.show(); + opts.resultsComplete.call(this); + } + + function add_selected_item(data, num){ + values_input.val(values_input.val()+data[opts.selectedValuesProp]+","); + var item = $('
  • ').click(function(){ + opts.selectionClick.call(this, $(this)); + selections_holder.children().removeClass("selected"); + $(this).addClass("selected"); + }).mousedown(function(){ input_focus = false; }); + var close = $('×').click(function(){ + values_input.val(values_input.val().replace(","+data[opts.selectedValuesProp]+",",",")); + opts.selectionRemoved.call(this, item); + input_focus = true; + input.focus(); + return false; + }); + org_li.before(item.html(data[opts.selectedItemProp]).prepend(close)); + opts.selectionAdded.call(this, org_li.prev()); + } + + function moveSelection(direction){ + if($(":visible",results_holder).length > 0){ + var lis = $("li", results_holder); + if(direction == "down"){ + var start = lis.eq(0); + } else { + var start = lis.filter(":last"); + } + var active = $("li.active:first", results_holder); + if(active.length > 0){ + if(direction == "down"){ + start = active.next(); + } else { + start = active.prev(); + } + } + lis.removeClass("active"); + start.addClass("active"); + } + } + + }); + } + } +})(jQuery); diff --git a/public/javascripts/view.js b/public/javascripts/view.js index 198268ccc..6f79f0d63 100644 --- a/public/javascripts/view.js +++ b/public/javascripts/view.js @@ -165,6 +165,14 @@ var View = { } }, + conversation_participants: { + bind: function() { + $(".conversation_participants img").tipsy({ + live: true + }); + } + }, + whatIsThis: { bind: function() { $(".what_is_this").tipsy({ diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass index 550f05083..1016c158b 100644 --- a/public/stylesheets/sass/application.sass +++ b/public/stylesheets/sass/application.sass @@ -9,7 +9,7 @@ $background: rgb(252,252,252) body :padding 2em :margin 0 - :top 60px + :top 50px :background-color $background a :color #107FC9 @@ -52,6 +52,12 @@ form :width 50px :height 50px +#content + :background + :color #fff + :border 1px solid #ccc + :height 100% + #flash_notice, #flash_error, #flash_alert @@ -127,8 +133,8 @@ header :color #111 :color rgba(15,15,15,0.90) - :background -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(30,30,30,0.85)), to(rgba(20,20,20,1))) - :background -moz-linear-gradient(top, rgba(30,30,30,0.85), rgba(20,20,20,0.98)) + :background -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(20,20,20,0.85)), to(rgba(20,20,20,1))) + :background -moz-linear-gradient(top, rgba(20,20,20,0.85), rgba(20,20,20,0.98)) :-webkit-box-shadow 0 1px 3px #111 :-moz-box-shadow 0 1px 2px #111 @@ -247,35 +253,33 @@ header .stream .stream_element - :font - :family 'arial', 'helvetica', sans-serif - :padding 10px 14px - :right 75px + :padding 20px 20px + :right 70px :min-height 50px :border :bottom 1px solid #ddd :top 1px solid #fff - :font - :size 13px - :line - :height 19px - - &:hover - :border - :bottom 1px solid #ddd .right :display inline + .from + a + :color $blue .youtube-player, .vimeo-player :border none :height 370px :width 500px + time + :font + :weight normal + :size smaller + :position absolute + :right 20px + .from - :text - :shadow 0 1px #fff a :font :weight bold @@ -334,11 +338,15 @@ header .stream_element :position relative :word-wrap break-word - :color #777 + :color #888 - .from - h5 - :display inline + :font + :size 14px + + a.author + :font + :weight bold + :color #444 .content :margin @@ -346,17 +354,17 @@ header :padding :left 60px - :color #444 + :color #666 :font :weight normal p :margin - :bottom 6px - :padding - :right 1em + :bottom 0px :font - :family 'arial', 'helvetica', 'sans-serif' + :size 13px + :line + :height 18px .photo_attachments :margin @@ -389,7 +397,7 @@ header :margin :top 2px :color #999 - :font-size smaller + :font-size 11px .comments .info @@ -406,8 +414,9 @@ header .time, .timeago + :color #999 a - :color #bbb + :color #ccc :margin :right 1px :text @@ -554,6 +563,8 @@ ul.show_comments :line :height 18px + :position relative + textarea :width 100% :height 1.4em @@ -588,9 +599,6 @@ ul.show_comments a :color #444 - div.time - :color #bbb - form :margin :top -5px @@ -629,6 +637,12 @@ ul.show_comments textarea :min-height 2.4em +.comments + time + :margin + :right -15px + .timeago + :color #999 .stream.show ul.comments @@ -688,11 +702,14 @@ a.paginate, #infscr-loading :right 12px &.controls + :z-index 6 :background :color $background :font :size 12px :color #999 + :padding + :left 100px a :color #999 :font @@ -1663,15 +1680,17 @@ h3 span.current_gs_step :background :color #22AAE0 -#notification_badge +#notification_badge, +#message_inbox_badge :position relative :top 5px :display inline - :margin 0 1em + :margin 0 10px + :right -5px :font :weight bold :size smaller - :width 30px + :width 28px a :z-index 5 @@ -1680,7 +1699,17 @@ h3 span.current_gs_step :width 20px :height 20px -#notification_badge_number + &:hover + .badge_count + :background + :color lighten(#A40802, 5%) + +#notification_badge + img + :position relative + :top 2px + +.badge_count :z-index 3 :position absolute :top -10px @@ -2270,6 +2299,7 @@ ul.show_comments :margin :left 0.5em :right 0.5em + .mark_all_read :position relative :top 10px @@ -2298,6 +2328,17 @@ ul.show_comments &.larger :width 600px +#new_message_pane + input:not([type='submit']), + textarea + :width 378px + :margin + :right 0 + + .as-selections + input + :width 200px + #facebox_header :padding 1em :background @@ -2483,3 +2524,188 @@ ul.show_comments .public_icon, .service_icon :cursor pointer + +.stream_element + .subject + :font + :size 13px + :weight bold + :color #444 + :overflow hidden + :white-space nowrap + + .last_author + :font + :size 12px + :color #777 + + +.conversation_participants + :z-index 3 + :background + :color $background + :position fixed + :margin + :bottom 10px + + :-webkit-box-shadow 0 3px 3px -3px #333 + :-moz-box-shadow 0 3px 3px -3px #333 + + h3 + :margin 0 + :top 6px + :bottom 0px + .avatar + :height 30px + :width 30px + + :line + :height 0 + + .conversation_controls + a + :margin + :right 10px + + :margin + :bottom 10px + + :border + :bottom 1px solid #666 + :padding 20px + :top 110px + :bottom 10px + :margin + :top -100px + + .avatars + :text + :align right + :margin + :top 9px + +.stream_element.new_message + :border + :top 1px solid #999 + :bottom none + &:hover + :border + :bottom none + textarea + :margin 0 + :bottom 0.5em + :width 100% + .right + :right -11px + +.stream_element.conversation + :padding 10px + :min-height 0px + + .message_count + :right 10px + :background + :color #999 + :color #eee + :position absolute + :padding 0 5px + :font + :size 12px + :weight normal + :-webkit-border-radius 3px + :-moz-border-radius 3px + :border-radius 3px + + .participant_count + :font + :weight normal + + .timestamp + :position absolute + :right 10px + :font + :weight normal + :color $blue + + .avatar + :display inline + :width 35px + :height 35px + :margin + :right 10px + + &:hover:not(.selected) + :background + :color #f0f0f0 + &:hover + :cursor pointer + +.conversation.unread + :background + :color lighten($background,5%) + +.conversation.selected + :background + :color $blue + .subject + :color #fff + .last_author + :color #fff + .timestamp + :color #eee + :border + :bottom 1px solid darken($blue, 10%) + :top 1px solid darken($blue, 10%) + +#conversation_inbox + :height 100% + :overflow-y auto + :overflow-x none + :background + :color #f3f3f3 + +#left_pane + :position fixed + :width 320px + :background + :color #ddd + + :-webkit-box-shadow 2px 2px 5px -1px #999 + + :border + :right 1px solid #999 + :z-index 4 + + h3 + :padding + :bottom 0 + :margin + :bottom 15px + + #left_pane_header + :padding 10px + :height 22px + :border + :bottom 1px solid #ddd + +#no_conversation_text + :font + :size 20px + :weight bold + :color #ccc + :text + :align center + :margin + :top 100px + +#no_conversation_controls + :text + :align center + :font + :size 12px + +.text-right + :text + :align right + :margin + :right 5px diff --git a/public/stylesheets/vendor/autoSuggest.css b/public/stylesheets/vendor/autoSuggest.css new file mode 100644 index 000000000..225d3c410 --- /dev/null +++ b/public/stylesheets/vendor/autoSuggest.css @@ -0,0 +1,216 @@ +/* AutoSuggest CSS - Version 1.2 */ + +ul.as-selections { + list-style-type: none; + border: 1px solid #ccc; + padding: 4px 0 4px 4px; + margin: 0; + overflow: auto; + background-color: #fff; + + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + + width: 383px; +} + +ul.as-selections.loading { + background-color: #eee; +} + +ul.as-selections li { + float: left; + margin: 1px 4px 1px 0; +} + +ul.as-selections li.as-selection-item { + color: #2b3840; + font-size: 13px; + text-shadow: 0 1px 1px #fff; + background-color: #ddeefe; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ddeefe), to(#bfe0f1)); + border: 1px solid #acc3ec; + padding: 0; + padding-left: 6px; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + box-shadow: 0 1px 1px #e4edf2; + -webkit-box-shadow: 0 1px 1px #e4edf2; + -moz-box-shadow: 0 1px 1px #e4edf2; + line-height: 10px; + margin-top: -1px; + margin-bottom: -1px; +} + +ul.as-selections li.as-selection-item:last-child { + margin-left: 30px; +} + +ul.as-selections li.as-selection-item a.as-close { + float: right; + margin: 0px 3px 0 0px; + padding: 0 3px; + cursor: pointer; + color: #5491be; + font-family: "Helvetica", helvetica, arial, sans-serif; + font-size: 14px; + font-weight: bold; + text-shadow: 0 1px 1px #fff; + -webkit-transition: color .1s ease-in; +} + +ul.as-selections li.as-selection-item.blur { + color: #666666; + background-color: #f4f4f4; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f4f4f4), to(#d5d5d5)); + border-color: #bbb; + border-top-color: #ccc; + box-shadow: 0 1px 1px #e9e9e9; + -webkit-box-shadow: 0 1px 1px #e9e9e9; + -moz-box-shadow: 0 1px 1px #e9e9e9; +} + +ul.as-selections li.as-selection-item.blur a.as-close { + color: #999; +} + +ul.as-selections li:hover.as-selection-item { + color: #2b3840; + background-color: #bbd4f1; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#bbd4f1), to(#a3c2e5)); + border-color: #6da0e0; + border-top-color: #8bb7ed; +} + +ul.as-selections li:hover.as-selection-item a.as-close { + color: #4d70b0; +} + +ul.as-selections li.as-selection-item.selected { + border-color: #1f30e4; +} + +ul.as-selections li.as-selection-item a:hover.as-close { + color: #1b3c65; +} + +ul.as-selections li.as-selection-item a:active.as-close { + color: #4d70b0; +} + +ul.as-selections li.as-original { + margin-left: 0; +} + +ul.as-selections li.as-original input { + border: none; + outline: none; + font-size: 13px; + width: 120px; + height: 14px; + padding: 0; + margin: 0; +} + +ul.as-list { + position: absolute; + list-style-type: none; + margin: 2px 0 0 0; + padding: 0; + font-size: 13px; + color: #000; + background-color: #fff; + background-color: rgba(255,255,255,0.95); + z-index: 2; + box-shadow: 0 2px 12px #222; + -webkit-box-shadow: 0 2px 12px #222; + -moz-box-shadow: 0 2px 12px #222; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; +} + +li.as-result-item, li.as-message { + margin: 0 0 0 0; + padding: 5px; + background-color: transparent; + border: 1px solid #fff; + border-bottom: 1px solid #ddd; + cursor: pointer; + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; +} + +li:first-child.as-result-item { + margin: 0; +} + +li.as-message { + margin: 0; + cursor: default; +} + +li.as-result-item.active { + background-color: #3668d9; + background-image: -webkit-gradient(linear, 0% 0%, 0% 64%, from(rgb(110, 129, 245)), to(rgb(62, 82, 242))); + border-color: #3342e8; + color: #fff; + text-shadow: 0 1px 2px #122042; +} + +li.as-result-item em { + font-style: normal; + background: #444; + padding: 0 2px; + color: #fff; +} + +li.as-result-item.active em { + background: #253f7a; + color: #fff; +} + +/* Webkit Hacks */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + ul.as-selections li.as-selection-item { + padding-top: 3px; + padding-bottom: 3px; + } + ul.as-selections li.as-selection-item a.as-close { + margin-top: -1px; + } + ul.as-selections li.as-original input { + height: 15px; + } +} + +/* Opera Hacks */ +@media all and (-webkit-min-device-pixel-ratio:10000), not all and (-webkit-min-device-pixel-ratio:0) { + ul.as-list { + border: 1px solid #ccc; + } + ul.as-selections li.as-selection-item a.as-close { + margin-left: 4px; + margin-top: 0; + } +} + +/* IE Hacks */ +ul.as-list { + border: 1px solid #ccc\9; +} +ul.as-selections li.as-selection-item a.as-close { + margin-left: 4px\9; + margin-top: 0\9; +} + +/* Firefox 3.0 Hacks */ +ul.as-list, x:-moz-any-link, x:default { + border: 1px solid #ccc; +} +BODY:first-of-type ul.as-list, x:-moz-any-link, x:default { /* Target FF 3.5+ */ + border: none; +} diff --git a/spec/controllers/aspect_memberships_controller_spec.rb b/spec/controllers/aspect_memberships_controller_spec.rb index 007d914b9..f79429dc2 100644 --- a/spec/controllers/aspect_memberships_controller_spec.rb +++ b/spec/controllers/aspect_memberships_controller_spec.rb @@ -23,13 +23,6 @@ describe AspectMembershipsController do request.env["HTTP_REFERER"] = 'http://' + request.host end - describe "#new" do - it 'succeeds' do - get :new - response.should be_success - end - end - describe '#create' do it 'creates an aspect membership' do @user.should_receive(:add_contact_to_aspect) diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index 80325a028..f003ae71c 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -41,11 +41,11 @@ describe CommentsController do post :create, comment_hash response.code.should == '201' end - it "doesn't overwrite person_id" do + it "doesn't overwrite author_id" do new_user = Factory.create(:user) - comment_hash[:person_id] = new_user.person.id.to_s + comment_hash[:author_id] = new_user.person.id.to_s post :create, comment_hash - Comment.find_by_text(comment_hash[:text]).person_id.should == @user1.person.id + Comment.find_by_text(comment_hash[:text]).author_id.should == @user1.person.id end it "doesn't overwrite id" do old_comment = @user1.comment("hello", :on => @post) diff --git a/spec/controllers/conversation_visibilities_controller_spec.rb b/spec/controllers/conversation_visibilities_controller_spec.rb new file mode 100644 index 000000000..f2002cdd9 --- /dev/null +++ b/spec/controllers/conversation_visibilities_controller_spec.rb @@ -0,0 +1,35 @@ +# 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 ConversationVisibilitiesController do + render_views + + before do + @user1 = alice + sign_in :user, @user1 + + hash = { :author => @user1.person, :participant_ids => [@user1.contacts.first.person.id, @user1.person.id], + :subject => 'not spam', :text => 'cool stuff'} + @conversation = Conversation.create(hash) + end + + describe '#destroy' do + it 'deletes the visibility' do + lambda { + delete :destroy, :conversation_id => @conversation.id + }.should change(ConversationVisibility, :count).by(-1) + end + + it 'does not let a user destroy a visibility that is not theirs' do + user2 = eve + sign_in :user, user2 + + lambda { + delete :destroy, :conversation_id => @conversation.id + }.should_not change(ConversationVisibility, :count) + end + end +end diff --git a/spec/controllers/conversations_controller_spec.rb b/spec/controllers/conversations_controller_spec.rb new file mode 100644 index 000000000..50f6ed14a --- /dev/null +++ b/spec/controllers/conversations_controller_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe ConversationsController do + render_views + + before do + @user1 = alice + sign_in :user, @user1 + end + + describe '#new' do + it 'succeeds' do + get :new + response.should be_success + end + end + + describe '#index' do + it 'succeeds' do + get :index + response.should be_success + end + + it 'retrieves all conversations for a user' do + hash = { :author => @user1.person, :participant_ids => [@user1.contacts.first.person.id, @user1.person.id], + :subject => 'not spam', :text => 'cool stuff'} + + 3.times do + cnv = Conversation.create(hash) + end + + get :index + assigns[:conversations].count.should == 3 + end + end + + describe '#create' do + before do + @hash = {:conversation => { + :subject => "secret stuff", + :text => 'text'}, + :contact_ids => '@user1.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 + pending + @hash[:author] = Factory.create(:user) + post :create, @hash + Message.first.author.should == @user1.person + Conversation.first.author.should == @user1.person + end + + it 'dispatches the conversation' do + cnv = Conversation.create(@hash[:conversation].merge({ + :author => @user1.person, + :participant_ids => [@user1.contacts.first.person.id]})) + + p = Postzord::Dispatch.new(@user1, cnv) + Postzord::Dispatch.stub!(:new).and_return(p) + p.should_receive(:post) + post :create, @hash + end + end + + describe '#show' do + before do + hash = { :author => @user1.person, :participant_ids => [@user1.contacts.first.person.id, @user1.person.id], + :subject => 'not spam', :text => 'cool stuff'} + @conversation = Conversation.create(hash) + end + + it 'succeeds' do + get :show, :id => @conversation.id + response.should be_success + assigns[:conversation].should == @conversation + end + + it 'does not let you access conversations where you are not a recipient' do + user2 = eve + sign_in :user, user2 + + get :show, :id => @conversation.id + response.code.should == '302' + end + end +end diff --git a/spec/controllers/messages_controller_spec.rb b/spec/controllers/messages_controller_spec.rb new file mode 100644 index 000000000..8c9b777bb --- /dev/null +++ b/spec/controllers/messages_controller_spec.rb @@ -0,0 +1,76 @@ +# 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 MessagesController do + render_views + + before do + @user1 = alice + @user2 = bob + + @aspect1 = @user1.aspects.first + @aspect2 = @user2.aspects.first + + sign_in :user, @user1 + end + + describe '#create' do + before do + @create_hash = { :author => @user1.person, :participant_ids => [@user1.contacts.first.person.id, @user1.person.id], + :subject => "cool stuff", :text => "stuff"} + end + 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)) + end + end + + context "on a post from a contact" do + before do + @create_hash[:author] = @user2.person + @cnv = Conversation.create(@create_hash) + @message_hash = {:conversation_id => @cnv.id, :message => {:text => "here is something else"}} + end + it 'comments' do + post :create, @message_hash + response.code.should == '302' + response.should redirect_to(conversations_path(:conversation_id => @cnv)) + end + it "doesn't overwrite author_id" do + new_user = Factory.create(:user) + @message_hash[:author_id] = new_user.person.id.to_s + post :create, @message_hash + Message.find_by_text(@message_hash[:message][:text]).author_id.should == @user1.person.id + end + it "doesn't overwrite id" do + old_message = Message.create(:text => "hello", :author_id => @user1.person.id, :conversation_id => @cnv.id) + @message_hash[:id] = old_message.id + post :create, @message_hash + old_message.reload.text.should == 'hello' + end + end + context 'on a post from a stranger' do + before do + @create_hash[:author] = eve.person + @create_hash[:participant_ids] = [eve.person.id, bob.person.id] + @cnv = Conversation.create(@create_hash) + @message_hash = {:conversation_id => @cnv.id, :message => {:text => "here is something else"}} + end + it 'posts no comment' do + post :create, @message_hash + response.code.should == '406' + end + end + end +end diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index e2e442332..d7f0ceaf5 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -18,7 +18,7 @@ describe PhotosController do @photo2 = @user2.post(:photo, :user_file => uploaded_photo, :to => @aspect2.id, :public => true) @controller.stub!(:current_user).and_return(@user1) - sign_in :user, @user1 + sign_in :user, @user1 request.env["HTTP_REFERER"] = '' end @@ -128,9 +128,9 @@ describe PhotosController do it "doesn't overwrite random attributes" do new_user = Factory.create(:user) - params = { :caption => "now with lasers!", :person_id => new_user.id } + params = { :caption => "now with lasers!", :author_id => new_user.id } put :update, :id => @photo1.id, :photo => params - @photo1.reload.person_id.should == @user1.person.id + @photo1.reload.author_id.should == @user1.person.id end it 'redirects if you do not have access to the post' do diff --git a/spec/controllers/status_messages_controller_spec.rb b/spec/controllers/status_messages_controller_spec.rb index d51fb2ef3..305656dc2 100644 --- a/spec/controllers/status_messages_controller_spec.rb +++ b/spec/controllers/status_messages_controller_spec.rb @@ -87,11 +87,11 @@ describe StatusMessagesController do post :create, status_message_hash end - it "doesn't overwrite person_id" do - status_message_hash[:status_message][:person_id] = @user2.person.id + it "doesn't overwrite author_id" do + status_message_hash[:status_message][:author_id] = @user2.person.id post :create, status_message_hash new_message = StatusMessage.find_by_message(status_message_hash[:status_message][:message]) - new_message.person_id.should == @user1.person.id + new_message.author_id.should == @user1.person.id end it "doesn't overwrite id" do diff --git a/spec/factories.rb b/spec/factories.rb index 1e784e761..72cc39a05 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -62,11 +62,11 @@ Factory.define :aspect do |aspect| aspect.association :user end -Factory.define :status_message do |m| +Factory.define(:status_message) do |m| m.sequence(:message) { |n| "jimmy's #{n} whales" } - m.association :person + m.association :author, :factory => :person m.after_build do|m| - m.diaspora_handle = m.person.diaspora_handle + m.diaspora_handle = m.author.diaspora_handle end end @@ -85,8 +85,8 @@ end Factory.define(:comment) do |comment| comment.sequence(:text) {|n| "#{n} cats"} - comment.association(:person) - comment.association :post, :factory => :status_message + comment.association(:author, :factory => :person) + comment.association(:post, :factory => :status_message) end Factory.define(:notification) do |n| diff --git a/spec/helpers/private_messages_helper_spec.rb b/spec/helpers/private_messages_helper_spec.rb new file mode 100644 index 000000000..a946519dd --- /dev/null +++ b/spec/helpers/private_messages_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the PrivateMessagesHelper. For example: +# +# describe PrivateMessagesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe PrivateMessagesHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/integration/receiving_spec.rb b/spec/integration/receiving_spec.rb index 9f81f0371..2070b5427 100644 --- a/spec/integration/receiving_spec.rb +++ b/spec/integration/receiving_spec.rb @@ -68,8 +68,9 @@ describe 'a user receives a post' do alice.visible_posts.count.should == 1 end - context 'mentions' do - it 'adds the notifications for the mentioned users regardless of the order they are received' do + context 'mentions' do + it 'adds the notifications for the mentioned users reguardless of the order they are received' do + pending 'this is for mnutt' Notification.should_receive(:notify).with(@user1, anything(), @user2.person) Notification.should_receive(:notify).with(@user3, anything(), @user2.person) @@ -84,32 +85,6 @@ describe 'a user receives a post' do zord = Postzord::Receiver.new(@user3, :object => @sm, :person => @user2.person) zord.receive_object end - - it 'notifies users when receiving a mention in a post from a remote user' do - @remote_person = Factory.create(:person, :diaspora_handle => "foobar@foobar.com") - Contact.create!(:user => @user1, :person => @remote_person, :aspects => [@aspect], :pending => false) - - Notification.should_receive(:notify).with(@user1, anything(), @remote_person) - - @sm = Factory.build(:status_message, :message => "hello @{#{@user1.name}; #{@user1.diaspora_handle}}", :diaspora_handle => @remote_person.diaspora_handle, :person => @remote_person) - @sm.stub!(:socket_to_user) - @sm.save - - zord = Postzord::Receiver.new(@user1, :object => @sm, :person => @user2.person) - zord.receive_object - end - - it 'does not notify the mentioned user if the mentioned user is not friends with the post author' do - Notification.should_not_receive(:notify).with(@user1, anything(), @user3.person) - - @sm = @user3.build_post(:status_message, :message => "should not notify @{#{@user1.name}; #{@user1.diaspora_handle}}") - @sm.stub!(:socket_to_user) - @user3.add_to_streams(@sm, [@user3.aspects.first]) - @sm.save - - zord = Postzord::Receiver.new(@user1, :object => @sm, :person => @user2.person) - zord.receive_object - end end context 'update posts' do @@ -153,35 +128,29 @@ describe 'a user receives a post' do @user1.raw_visible_posts.should_not include @status_message end - it 'deletes a post if the noone links to it' do - person = Factory(:person) - @user1.activate_contact(person, @aspect) - post = Factory.create(:status_message, :person => person) - post.post_visibilities.should be_empty - receive_with_zord(@user1, person, post.to_diaspora_xml) - @aspect.post_visibilities.reset - @aspect.posts(true).should include(post) - post.post_visibilities.reset - post.post_visibilities.length.should == 1 + context 'dependant delete' do + before do + @person = Factory(:person) + @user1.activate_contact(@person, @aspect) + @post = Factory.create(:status_message, :author => @person) + @post.post_visibilities.should be_empty + receive_with_zord(@user1, @person, @post.to_diaspora_xml) + @aspect.post_visibilities.reset + @aspect.posts(true).should include(@post) + @post.post_visibilities.reset + end - lambda { - @user1.disconnected_by(person) - }.should change(Post, :count).by(-1) - end - it 'deletes post_visibilities on disconnected by' do - person = Factory(:person) - @user1.activate_contact(person, @aspect) - post = Factory.create(:status_message, :person => person) - post.post_visibilities.should be_empty - receive_with_zord(@user1, person, post.to_diaspora_xml) - @aspect.post_visibilities.reset - @aspect.posts(true).should include(post) - post.post_visibilities.reset - post.post_visibilities.length.should == 1 + it 'deletes a post if the noone links to it' do + lambda { + @user1.disconnected_by(@person) + }.should change(Post, :count).by(-1) + end - lambda { - @user1.disconnected_by(person) - }.should change{post.post_visibilities(true).count}.by(-1) + it 'deletes post_visibilities on disconnected by' do + lambda { + @user1.disconnected_by(@person) + }.should change{@post.post_visibilities(true).count}.by(-1) + end end it 'should keep track of user references for one person ' do @status_message.reload @@ -224,7 +193,7 @@ describe 'a user receives a post' do receive_with_zord(@user3, @user1.person, xml) @comment = @user3.comment('tada',:on => @post) - @comment.post_creator_signature = @comment.sign_with_key(@user1.encryption_key) + @comment.parent_author_signature = @comment.sign_with_key(@user1.encryption_key) @xml = @comment.to_diaspora_xml @comment.delete end @@ -235,7 +204,7 @@ describe 'a user receives a post' do post_in_db.comments.should == [] receive_with_zord(@user2, @user1.person, @xml) - post_in_db.comments(true).first.person.should == @user3.person + post_in_db.comments(true).first.author.should == @user3.person end it 'should correctly marshal a stranger for the downstream user' do @@ -263,7 +232,7 @@ describe 'a user receives a post' do receive_with_zord(@user2, @user1.person, @xml) - post_in_db.comments(true).first.person.should == remote_person + post_in_db.comments(true).first.author.should == remote_person end end @@ -292,11 +261,11 @@ describe 'a user receives a post' do describe 'receiving mulitple versions of the same post from a remote pod' do before do @local_luke, @local_leia, @remote_raphael = set_up_friends - @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :person => @remote_raphael, :created_at => 5.days.ago, :updated_at => 5.days.ago) + @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :author=> @remote_raphael, :created_at => 5.days.ago, :updated_at => 5.days.ago) end it 'does not update created_at or updated_at when two people save the same post' do - @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :person => @remote_raphael, :created_at => 5.days.ago, :updated_at => 5.days.ago) + @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :author=> @remote_raphael, :created_at => 5.days.ago, :updated_at => 5.days.ago) xml = @post.to_diaspora_xml receive_with_zord(@local_luke, @remote_raphael, xml) sleep(2) @@ -307,11 +276,11 @@ describe 'a user receives a post' do end it 'does not update the post if a new one is sent with a new created_at' do - @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :person => @remote_raphael, :created_at => 5.days.ago) + @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :author => @remote_raphael, :created_at => 5.days.ago) old_time = @post.created_at xml = @post.to_diaspora_xml receive_with_zord(@local_luke, @remote_raphael, xml) - @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :person => @remote_raphael, :created_at => 2.days.ago) + @post = Factory.build(:status_message, :message => 'hey', :guid => 12313123, :author => @remote_raphael, :created_at => 2.days.ago) receive_with_zord(@local_luke, @remote_raphael, xml) (Post.find_by_guid @post.guid).created_at.day.should == old_time.day end diff --git a/spec/lib/data_conversion/import_to_mysql_spec.rb b/spec/lib/data_conversion/import_to_mysql_spec.rb index 639ff7efb..840206905 100644 --- a/spec/lib/data_conversion/import_to_mysql_spec.rb +++ b/spec/lib/data_conversion/import_to_mysql_spec.rb @@ -357,8 +357,8 @@ describe DataConversion::ImportToMysql do post.image.should be_nil post.mongo_id.should == "4d2b6ebecc8cb43cc2000027" post.guid.should == post.mongo_id - post.person_id.should == Person.where(:mongo_id => mongo_post.person_mongo_id).first.id - post.diaspora_handle.should == post.person.diaspora_handle + post.author_id.should == Person.where(:mongo_id => mongo_post.person_mongo_id).first.id + post.diaspora_handle.should == post.author.diaspora_handle post.message.should == "User2 can see this" post.created_at.should == mongo_post.created_at post.updated_at.should == mongo_post.updated_at @@ -379,8 +379,8 @@ describe DataConversion::ImportToMysql do post.image.file.file.should =~ /mUKUIxkYlV4d2b6ebfcc8cb43cc200002d\.png/ post.mongo_id.should == "4d2b6ebfcc8cb43cc200002d" post.guid.should == post.mongo_id - post.person_id.should == Person.where(:mongo_id => mongo_post.person_mongo_id).first.id - post.diaspora_handle.should == post.person.diaspora_handle + post.author_id.should == Person.where(:mongo_id => mongo_post.person_mongo_id).first.id + post.diaspora_handle.should == post.author.diaspora_handle post.message.should be_nil post.created_at.should == mongo_post.created_at post.updated_at.should == mongo_post.updated_at @@ -412,7 +412,7 @@ describe DataConversion::ImportToMysql do @migrator.process_raw_comments comment = Comment.first comment.post_id.should == Post.where(:mongo_id => "4d2b6ebecc8cb43cc2000029").first.id - comment.person_id.should == Person.where(:mongo_id => "4d2b6eb7cc8cb43cc2000017").first.id + comment.author_id.should == Person.where(:mongo_id => "4d2b6eb7cc8cb43cc2000017").first.id end end describe "notifications" do diff --git a/spec/lib/diaspora/parser_spec.rb b/spec/lib/diaspora/parser_spec.rb index 5645726dc..b680c5b37 100644 --- a/spec/lib/diaspora/parser_spec.rb +++ b/spec/lib/diaspora/parser_spec.rb @@ -20,7 +20,7 @@ describe Diaspora::Parser do describe "parsing compliant XML object" do it 'should be able to correctly parse comment fields' do post = @user1.post :status_message, :message => "hello", :to => @aspect1.id - comment = Factory.create(:comment, :post => post, :person => @person, :diaspora_handle => @person.diaspora_handle, :text => "Freedom!") + comment = Factory.create(:comment, :post => post, :author => @person, :diaspora_handle => @person.diaspora_handle, :text => "Freedom!") comment.delete xml = comment.to_diaspora_xml comment_from_xml = Diaspora::Parser.from_xml(xml) diff --git a/spec/lib/fake_spec.rb b/spec/lib/fake_spec.rb index c8d8c1f98..0cf749568 100644 --- a/spec/lib/fake_spec.rb +++ b/spec/lib/fake_spec.rb @@ -5,10 +5,10 @@ describe PostsFake do @people = [] 4.times do post = Factory(:status_message) - @people << post.person + @people << post.author 4.times do comment = Factory(:comment, :post => post) - @people << comment.person + @people << comment.author end @posts << post end @@ -30,7 +30,7 @@ describe PostsFake do end end describe PostsFake::Fake do - include Rails.application.routes.url_helpers + include Rails.application.routes.url_helpers before do @post = mock() @fakes = mock() diff --git a/spec/lib/postzord/dispatch_spec.rb b/spec/lib/postzord/dispatch_spec.rb index 6886311d7..f46391f27 100644 --- a/spec/lib/postzord/dispatch_spec.rb +++ b/spec/lib/postzord/dispatch_spec.rb @@ -134,7 +134,7 @@ describe Postzord::Dispatch do end context "remote raphael" do before do - @comment = Factory.build(:comment, :person => @remote_raphael, :post => @post) + @comment = Factory.build(:comment, :author => @remote_raphael, :post => @post) @comment.save @mailman = Postzord::Dispatch.new(@local_luke, @comment) end @@ -181,7 +181,7 @@ describe Postzord::Dispatch do end context "remote raphael's post is commented on by local luke" do before do - @post = Factory(:status_message, :person => @remote_raphael) + @post = Factory(:status_message, :author => @remote_raphael) @comment = @local_luke.build_comment "yo", :on => @post @comment.save @mailman = Postzord::Dispatch.new(@local_luke, @comment) @@ -304,7 +304,7 @@ describe Postzord::Dispatch do it 'queues Job::NotifyLocalUsers jobs' do @zord.instance_variable_get(:@object).should_receive(:socket_to_user).and_return(false) - Resque.should_receive(:enqueue).with(Job::NotifyLocalUsers, @local_user.id, @sm.class.to_s, @sm.id, @sm.person.id) + Resque.should_receive(:enqueue).with(Job::NotifyLocalUsers, @local_user.id, @sm.class.to_s, @sm.id, @sm.author.id) @zord.send(:socket_and_notify_users, [@local_user]) end end diff --git a/spec/lib/web_hooks_spec.rb b/spec/lib/web_hooks_spec.rb index 359ced22f..9ee6d4c38 100644 --- a/spec/lib/web_hooks_spec.rb +++ b/spec/lib/web_hooks_spec.rb @@ -5,12 +5,10 @@ require 'spec_helper' describe Diaspora::Webhooks do - before do - @user = Factory.build(:user) - @post = Factory.build(:status_message, :person => @user.person) - end - it "should add the following methods to Post on inclusion" do - @post.respond_to?(:to_diaspora_xml).should be true + user = Factory.build(:user) + post = Factory.build(:status_message, :author => user.person) + + post.respond_to?(:to_diaspora_xml).should be true end end diff --git a/spec/mailers/notifier_spec.rb b/spec/mailers/notifier_spec.rb index f3db5ab38..b2ed38bb3 100644 --- a/spec/mailers/notifier_spec.rb +++ b/spec/mailers/notifier_spec.rb @@ -87,7 +87,7 @@ describe Notifier do @sm = Factory(:status_message) @m = Mention.create(:person => @user.person, :post=> @sm) - @mail = Notifier.mentioned(@user.id, @sm.person.id, @m.id) + @mail = Notifier.mentioned(@user.id, @sm.author.id, @m.id) end it 'goes to the right person' do @mail.to.should == [@user.email] @@ -98,7 +98,7 @@ describe Notifier do end it 'has the name of person mentioning in the body' do - @mail.body.encoded.include?(@sm.person.name).should be true + @mail.body.encoded.include?(@sm.author.name).should be true end it 'has the post text in the body' do @@ -110,7 +110,42 @@ describe Notifier do end end + describe ".private_message" do + before do + @user2 = bob + @participant_ids = @user2.contacts.map{|c| c.person.id} + [ @user2.person.id] + @create_hash = { :author => @user2.person, :participant_ids => @participant_ids , + :subject => "cool stuff", :text => 'hey'} + + @cnv = Conversation.create(@create_hash) + + @mail = Notifier.private_message(user.id, @cnv.author.id, @cnv.messages.first.id) + end + it 'goes to the right person' do + @mail.to.should == [user.email] + end + + it 'has the recipients in the body' do + @mail.body.encoded.include?(user.person.first_name).should be true + end + + it 'has the name of the sender in the body' do + @mail.body.encoded.include?(@cnv.author.name).should be true + end + + it 'has the conversation subject in the body' do + @mail.body.encoded.should include(@cnv.subject) + end + + it 'has the post text in the body' do + @mail.body.encoded.should include(@cnv.messages.first.text) + end + + it 'should not include translation missing' do + @mail.body.encoded.should_not include("missing") + end + end context "comments" do let!(:connect) { connect_users(user, aspect, user2, aspect2)} let!(:sm) {user.post(:status_message, :message => "Sunny outside", :to => :all)} diff --git a/spec/misc_spec.rb b/spec/misc_spec.rb index 3ea7bbc6c..f95f443ae 100644 --- a/spec/misc_spec.rb +++ b/spec/misc_spec.rb @@ -49,4 +49,17 @@ describe 'making sure the spec runner works' do @user2.reload.visible_posts.should include message end end + + describe '#comment' do + it "should send a user's comment on a person's post to that person" do + person = Factory.create(:person) + person_status = Factory.create(:status_message, :author => person) + m = mock() + m.stub!(:post) + Postzord::Dispatch.should_receive(:new).and_return(m) + + alice.comment "yo", :on => person_status + end + end + end diff --git a/spec/models/aspect_spec.rb b/spec/models/aspect_spec.rb index bf7a2548c..9c8f81939 100644 --- a/spec/models/aspect_spec.rb +++ b/spec/models/aspect_spec.rb @@ -114,7 +114,7 @@ describe Aspect do aspect = user.aspects.create(:name => 'losers') contact = aspect.contacts.create(:person => connected_person) - status_message = user.post( :status_message, :message => "hey", :to => aspect.id ) + status_message = user.post(:status_message, :message => "hey", :to => aspect.id) aspect.reload aspect.posts.include?(status_message).should be true diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index e80cfcf3d..030755e3b 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -3,53 +3,47 @@ # the COPYRIGHT file. require 'spec_helper' +require File.join(Rails.root, "spec", "shared_behaviors", "relayable") describe Comment do before do @alices_aspect = alice.aspects.first @bobs_aspect = bob.aspects.first + + @bob = bob + @eve = eve + @status = alice.post(:status_message, :message => "hello", :to => @alices_aspect.id) end describe 'comment#notification_type' do - before do - @sam = Factory(:user_with_aspect) - connect_users(alice, @alices_aspect, @sam, @sam.aspects.first) - - @alices_post = alice.post(:status_message, :message => "hello", :to => @alices_aspect.id) - end - it "returns 'comment_on_post' if the comment is on a post you own" do - comment = bob.comment("why so formal?", :on => @alices_post) + comment = bob.comment("why so formal?", :on => @status) comment.notification_type(alice, bob.person).should == Notifications::CommentOnPost end it 'returns false if the comment is not on a post you own and no one "also_commented"' do - comment = alice.comment("I simply felt like issuing a greeting. Do step off.", :on => @alices_post) - comment.notification_type(@sam, alice.person).should == false + comment = alice.comment("I simply felt like issuing a greeting. Do step off.", :on => @status) + comment.notification_type(@bob, alice.person).should == false end context "also commented" do before do - bob.comment("a-commenta commenta", :on => @alices_post) - @comment = @sam.comment("I also commented on the first user's post", :on => @alices_post) + @bob.comment("a-commenta commenta", :on => @status) + @comment = @eve.comment("I also commented on the first user's post", :on => @status) end it 'does not return also commented if the user commented' do - @comment.notification_type(@sam, alice.person).should == false + @comment.notification_type(@eve, alice.person).should == false end it "returns 'also_commented' if another person commented on a post you commented on" do - @comment.notification_type(bob, alice.person).should == Notifications::AlsoCommented + @comment.notification_type(@bob, alice.person).should == Notifications::AlsoCommented end end end describe 'User#comment' do - before do - @status = alice.post(:status_message, :message => "hello", :to => @alices_aspect.id) - end - it "should be able to comment on one's own status" do alice.comment("Yeah, it was great", :on => @status) @status.reload.comments.first.text.should == "Yeah, it was great" @@ -59,66 +53,13 @@ describe Comment do bob.comment("sup dog", :on => @status) @status.reload.comments.first.text.should == "sup dog" end - end - - context 'comment propagation' do - before do - @person = Factory.create(:person) - alice.activate_contact(@person, @alices_aspect) - - @person2 = Factory.create(:person) - @person3 = Factory.create(:person) - alice.activate_contact(@person3, @alices_aspect) - - @person_status = Factory.create(:status_message, :person => @person) - - alice.reload - @user_status = alice.post :status_message, :message => "hi", :to => @alices_aspect.id - - @alices_aspect.reload - alice.reload - end - - it 'should send the comment to the postman' do - m = mock() - m.stub!(:post) - Postzord::Dispatch.should_receive(:new).and_return(m) - alice.comment "yo", :on => @person_status - end - - describe '#subscribers' do - it 'returns the posts original audience, if the post is owned by the user' do - comment = alice.build_comment "yo", :on => @person_status - comment.subscribers(alice).should =~ [@person] - end - - it 'returns the owner of the original post, if the user owns the comment' do - comment = alice.build_comment "yo", :on => @user_status - comment.subscribers(alice).map { |s| s.id }.should =~ [@person, @person3, bob.person].map { |s| s.id } - end - end - - context 'testing a method only used for testing' do - it "should send a user's comment on a person's post to that person" do - m = mock() - m.stub!(:post) - Postzord::Dispatch.should_receive(:new).and_return(m) - - alice.comment "yo", :on => @person_status - end - end - - it 'should not clear the aspect post array on receiving a comment' do - @alices_aspect.post_ids.include?(@user_status.id).should be_true - comment = Comment.new(:person_id => @person.id, :text => "cats", :post => @user_status) - - zord = Postzord::Receiver.new(alice, :person => @person) - zord.parse_and_receive(comment.to_diaspora_xml) - - @alices_aspect.reload - @alices_aspect.post_ids.include?(@user_status.id).should be_true + it 'does not multi-post a comment' do + lambda { + alice.comment 'hello', :on => @status + }.should change { Comment.count }.by(1) end end + describe 'xml' do before do @commenter = Factory.create(:user) @@ -139,66 +80,13 @@ describe Comment do @marshalled_comment = Comment.from_xml(@xml) end it 'marshals the author' do - @marshalled_comment.person.should == @commenter.person + @marshalled_comment.author.should == @commenter.person end it 'marshals the post' do @marshalled_comment.post.should == @post end end end - describe 'local commenting' do - before do - @status = alice.post(:status_message, :message => "hello", :to => @alices_aspect.id) - end - it 'does not multi-post a comment' do - lambda { - alice.comment 'hello', :on => @status - }.should change { Comment.count }.by(1) - end - end - describe 'comments' do - before do - @remote_message = bob.post :status_message, :message => "hello", :to => @bobs_aspect.id - @message = alice.post :status_message, :message => "hi", :to => @alices_aspect.id - end - - it 'should attach the creator signature if the user is commenting' do - comment = alice.comment "Yeah, it was great", :on => @remote_message - @remote_message.comments.reset - @remote_message.comments.first.signature_valid?.should be_true - end - - it 'should sign the comment if the user is the post creator' do - message = alice.post :status_message, :message => "hi", :to => @alices_aspect.id - alice.comment "Yeah, it was great", :on => message - message.comments.reset - message.comments.first.signature_valid?.should be_true - message.comments.first.verify_post_creator_signature.should be_true - end - - it 'should verify a comment made on a remote post by a different contact' do - comment = Comment.new(:person => bob.person, :text => "cats", :post => @remote_message) - comment.creator_signature = comment.send(:sign_with_key, bob.encryption_key) - comment.signature_valid?.should be_true - comment.verify_post_creator_signature.should be_false - comment.post_creator_signature = comment.send(:sign_with_key, alice.encryption_key) - comment.verify_post_creator_signature.should be_true - end - - it 'should reject comments on a remote post with only a creator sig' do - comment = Comment.new(:person => bob.person, :text => "cats", :post => @remote_message) - comment.creator_signature = comment.send(:sign_with_key, bob.encryption_key) - comment.signature_valid?.should be_true - comment.verify_post_creator_signature.should be_false - end - - it 'should receive remote comments on a user post with a creator sig' do - comment = Comment.new(:person => bob.person, :text => "cats", :post => @message) - comment.creator_signature = comment.send(:sign_with_key, bob.encryption_key) - comment.signature_valid?.should be_true - comment.verify_post_creator_signature.should be_false - end - end describe 'youtube' do before do @@ -220,4 +108,20 @@ describe Comment do Comment.find(comment.id).youtube_titles.should == {video_id => CGI::escape(expected_title)} end end + + describe 'it is relayable' do + before do + @local_luke, @local_leia, @remote_raphael = set_up_friends + @remote_parent = Factory.create(:status_message, :author => @remote_raphael) + @local_parent = @local_luke.post :status_message, :message => "hi", :to => @local_luke.aspects.first + + @object_by_parent_author = @local_luke.comment("yo", :on => @local_parent) + @object_by_recipient = @local_leia.build_comment("yo", :on => @local_parent) + @dup_object_by_parent_author = @object_by_parent_author.dup + + @object_on_remote_parent = @local_luke.comment("Yeah, it was great", :on => @remote_parent) + end + it_should_behave_like 'it is relayable' + end + end diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb new file mode 100644 index 000000000..89ac00a30 --- /dev/null +++ b/spec/models/conversation_spec.rb @@ -0,0 +1,92 @@ +# 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 Conversation do + before do + @user1 = alice + @user2 = bob + @participant_ids = [@user1.contacts.first.person.id, @user1.person.id] + + @create_hash = { :author => @user1.person, :participant_ids => @participant_ids , + :subject => "cool stuff", :text => 'hey'} + end + + it 'creates a message on create' do + lambda{ + Conversation.create(@create_hash) + }.should change(Message, :count).by(1) + end + + describe '#last_author' do + it 'returns the last author to a conversation' do + time = Time.now + cnv = Conversation.create(@create_hash) + Message.create(:author => @user2.person, :created_at => time + 1.second, :text => "last", :conversation_id => cnv.id) + cnv.reload.last_author.id.should == @user2.person.id + end + end + + + context 'transport' do + before do + @cnv = Conversation.create(@create_hash) + @message = @cnv.messages.first + @xml = @cnv.to_diaspora_xml + end + + describe 'serialization' do + it 'serializes the message' do + @xml.gsub(/\s/, '').should include(@message.to_xml.to_s.gsub(/\s/, '')) + end + + it 'serializes the participants' do + @create_hash[:participant_ids].each{|id| + @xml.should include(Person.find(id).diaspora_handle) + } + end + + it 'serializes the created_at time' do + @xml.should include(@message.created_at.to_s) + end + end + + describe '#subscribers' do + it 'returns the recipients for the post owner' do + @cnv.subscribers(@user1).should == @user1.contacts.map{|c| c.person} + end + end + + describe '#receive' do + before do + Conversation.destroy_all + Message.destroy_all + end + + it 'creates a message' do + lambda{ + Diaspora::Parser.from_xml(@xml).receive(@user1, @user2.person) + }.should change(Message, :count).by(1) + end + it 'creates a conversation' do + lambda{ + Diaspora::Parser.from_xml(@xml).receive(@user1, @user2.person) + }.should change(Conversation, :count).by(1) + end + it 'creates appropriate visibilities' do + lambda{ + Diaspora::Parser.from_xml(@xml).receive(@user1, @user2.person) + }.should change(ConversationVisibility, :count).by(@participant_ids.size) + end + it 'does not save before receive' do + Diaspora::Parser.from_xml(@xml).persisted?.should be_false + end + it 'notifies for the message' do + Notification.should_receive(:notify).once + Diaspora::Parser.from_xml(@xml).receive(@user1, @user2.person) + end + end + end +end diff --git a/spec/models/conversation_visibility_spec.rb b/spec/models/conversation_visibility_spec.rb new file mode 100644 index 000000000..fff04d7e1 --- /dev/null +++ b/spec/models/conversation_visibility_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe ConversationVisibility do + before do + @user = alice + @aspect = @user.aspects.create(:name => 'Boozers') + + @person = Factory(:person) + @post = Factory(:status_message, :author => @person) + end + it 'has an aspect' do + pv = PostVisibility.new(:aspect => @aspect) + pv.aspect.should == @aspect + end + it 'has a post' do + pv = PostVisibility.new(:post => @post) + pv.post.should == @post + end +end diff --git a/spec/models/jobs/mail_mentioned_spec.rb b/spec/models/jobs/mail_mentioned_spec.rb index a186ee623..5680a3f3f 100644 --- a/spec/models/jobs/mail_mentioned_spec.rb +++ b/spec/models/jobs/mail_mentioned_spec.rb @@ -13,9 +13,9 @@ describe Job::MailMentioned do mail_mock = mock() mail_mock.should_receive(:deliver) - Notifier.should_receive(:mentioned).with(user.id, sm.person.id, m.id).and_return(mail_mock) + Notifier.should_receive(:mentioned).with(user.id, sm.author.id, m.id).and_return(mail_mock) - Job::MailMentioned.perform_delegate(user.id, sm.person.id, m.id) + Job::MailMentioned.perform_delegate(user.id, sm.author.id, m.id) end end end diff --git a/spec/models/jobs/mail_private_message.rb b/spec/models/jobs/mail_private_message.rb new file mode 100644 index 000000000..dc1c12914 --- /dev/null +++ b/spec/models/jobs/mail_private_message.rb @@ -0,0 +1,27 @@ +# 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 Job::MailPrivateMessage do + describe '#perfom_delegate' do + it 'should call .deliver on the notifier object' do + user1 = alice + user2 = bob + participant_ids = [user1.contacts.first.person.id, user1.person.id] + + create_hash = { :author => user1.person, :participant_ids => participant_ids , + :subject => "cool stuff", :text => 'hey'} + + cnv = Conversation.create(create_hash) + message = cnv.messages.first + + mail_mock = mock() + mail_mock.should_receive(:deliver) + Notifier.should_receive(:mentioned).with(user2.id, user1.person.id, message.id).and_return(mail_mock) + + Job::MailMentioned.perform_delegate(user2.id, user1.person.id, message.id) + end + end +end diff --git a/spec/models/mention_spec.rb b/spec/models/mention_spec.rb index 9310869d5..77428a541 100644 --- a/spec/models/mention_spec.rb +++ b/spec/models/mention_spec.rb @@ -12,12 +12,13 @@ describe Mention do @mentioned_user = bob @non_friend = eve - @sm = @user.build_post(:status_message, :message => "hi @{#{@mentioned_user.name}; #{@mentioned_user.diaspora_handle}}", :to => @user.aspects.first) - end + @sm = Factory(:status_message) + @m = Mention.new(:person => @user.person, :post=> @sm) + end it 'notifies the person being mentioned' do - Notification.should_receive(:notify).with(@mentioned_user, anything(), @sm.person) - @sm.receive(@mentioned_user, @mentioned_user.person) + Notification.should_receive(:notify).with(@user, anything(), @sm.author) + @m.save end it 'should not notify a user if they do not see the message' do @@ -39,11 +40,8 @@ describe Mention do describe 'after destroy' do it 'destroys a notification' do @user = alice - @mentioned_user = bob - - @sm = @user.post(:status_message, :message => "hi", :to => @user.aspects.first) - @m = Mention.create!(:person => @mentioned_user.person, :post => @sm) - @m.notify_recipient + @sm = Factory(:status_message) + @m = Mention.create(:person => @user.person, :post=> @sm) lambda{ @m.destroy diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb new file mode 100644 index 000000000..859900f4b --- /dev/null +++ b/spec/models/message_spec.rb @@ -0,0 +1,102 @@ +# 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' +require File.join(Rails.root, "spec", "shared_behaviors", "relayable") + +describe Message do + before do + @user1 = alice + @user2 = bob + + @create_hash = { :author => @user1.person, :participant_ids => [@user1.contacts.first.person.id, @user1.person.id], + :subject => "cool stuff", :text => "stuff"} + + @cnv = Conversation.create(@create_hash) + @message = @cnv.messages.first + @xml = @message.to_diaspora_xml + end + + it 'validates that the author is a participant in the conversation' do + msg = Message.new(:text => 'yo', :author => eve.person, :conversation_id => @cnv.id) + end + + describe '#notification_type' do + it 'does not return anything for the author' do + @message.notification_type(@user1, @user1.person).should be_nil + end + + it 'returns private mesage for an actual receiver' do + @message.notification_type(@user2, @user1.person).should == Notifications::PrivateMessage + end + end + + describe '#before_create' do + it 'signs the message' do + @message.author_signature.should_not be_blank + end + + it 'signs the message author if author of conversation' do + @message.parent_author_signature.should_not be_blank + end + end + + describe 'serialization' do + it 'serializes the text' do + @xml.should include(@message.text) + end + + it 'serializes the author_handle' do + @xml.should include(@message.author.diaspora_handle) + end + + it 'serializes the created_at time' do + @xml.should include(@message.created_at.to_s) + end + + it 'serializes the conversation_guid time' do + @xml.should include(@message.conversation.guid) + end + end + + describe 'it is relayable' do + before do + @local_luke, @local_leia, @remote_raphael = set_up_friends + + cnv_hash = {:author => @remote_raphael, :participant_ids => [@local_luke.person, @local_leia.person, @remote_raphael].map(&:id), + :subject => 'cool story, bro', :text => 'hey'} + + @remote_parent = Conversation.create(cnv_hash.dup) + + cnv_hash[:author] = @local_luke.person + @local_parent = Conversation.create(cnv_hash) + + msg_hash = {:author => @local_luke.person, :text => 'yo', :conversation => @local_parent} + @object_by_parent_author = Message.create(msg_hash.dup) + Postzord::Dispatch.new(@local_luke, @object_by_parent_author).post + + msg_hash[:author] = @local_leia.person + @object_by_recipient = Message.create(msg_hash.dup) + + @dup_object_by_parent_author = @object_by_parent_author.dup + + msg_hash[:author] = @local_luke.person + msg_hash[:conversation] = @remote_parent + @object_on_remote_parent = Message.create(msg_hash) + Postzord::Dispatch.new(@local_luke, @object_on_remote_parent).post + end + it_should_behave_like 'it is relayable' + + describe '#after_receive' do + it 'increments the conversation visiblity for the conversation' do + ConversationVisibility.where(:conversation_id => @object_by_recipient.reload.conversation.id, + :person_id => @local_luke.person.id).first.unread.should == 0 + + @object_by_recipient.receive(@local_luke, @local_leia.person) + ConversationVisibility.where(:conversation_id => @object_by_recipient.reload.conversation.id, + :person_id => @local_luke.person.id).first.unread.should == 1 + end + end + end +end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 21b3ceaf4..eb6ce5f09 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -44,7 +44,8 @@ describe Notification do Notification.should_not_receive(:make_notificatin) Notification.notify(@user, @sm, @person) end - context 'with a request' do + + context 'with a request' do before do @request = Request.diaspora_initialize(:from => @user.person, :to => @user2.person, :into => @aspect) end diff --git a/spec/models/notifications/private_message_spec.rb b/spec/models/notifications/private_message_spec.rb new file mode 100644 index 000000000..42b05f9ec --- /dev/null +++ b/spec/models/notifications/private_message_spec.rb @@ -0,0 +1,41 @@ +# 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 Notifications::PrivateMessage do + before do + @user1 = alice + @user2 = bob + + @create_hash = { :author => @user1.person, :participant_ids => [@user1.contacts.first.person.id, @user1.person.id], + :subject => "cool stuff", :text => "stuff"} + + @cnv = Conversation.create(@create_hash) + @msg = @cnv.messages.first + end + + describe '#make_notifiaction' do + it 'does not save the notification' do + lambda{ + Notification.notify(@user2, @msg, @user1.person) + }.should_not change(Notification, :count) + end + + it 'does email the user' do + opts = { + :actors => [@user1.person], + :recipient_id => @user2.id} + + n = Notifications::PrivateMessage.new(opts) + Notifications::PrivateMessage.stub!(:make_notification).and_return(n) + Notification.notify(@user2, @msg, @user1.person) + n.stub!(:recipient).and_return @user2 + + @user2.should_receive(:mail) + n.email_the_user(@msg, @user1.person) + end + end +end + diff --git a/spec/models/person_spec.rb b/spec/models/person_spec.rb index 0d799e207..15b5375e0 100644 --- a/spec/models/person_spec.rb +++ b/spec/models/person_spec.rb @@ -101,8 +101,8 @@ describe Person do end it '#owns? posts' do - person_message = Factory.create(:status_message, :person => @person) - person_two = Factory.create(:person) + person_message = Factory.create(:status_message, :author => @person) + person_two = Factory.create(:person) @person.owns?(person_message).should be true person_two.owns?(person_message).should be false @@ -111,8 +111,8 @@ describe Person do describe '#remove_all_traces' do before do @deleter = Factory(:person) - @status = Factory.create(:status_message, :person => @deleter) - @other_status = Factory.create(:status_message, :person => @person) + @status = Factory.create(:status_message, :author => @deleter) + @other_status = Factory.create(:status_message, :author => @person) end it "deletes all notifications from a person's actions" do @@ -154,8 +154,8 @@ describe Person do end it "deletes a person's comments on person deletion" do - Factory.create(:comment, :person_id => @deleter.id, :diaspora_handle => @deleter.diaspora_handle, :text => "i love you", :post => @other_status) - Factory.create(:comment, :person_id => @person.id,:diaspora_handle => @person.diaspora_handle, :text => "you are creepy", :post => @other_status) + Factory.create(:comment, :author_id => @deleter.id, :diaspora_handle => @deleter.diaspora_handle, :text => "i love you", :post => @other_status) + Factory.create(:comment, :author_id => @person.id,:diaspora_handle => @person.diaspora_handle, :text => "you are creepy", :post => @other_status) lambda {@deleter.destroy}.should change(Comment, :count).by(-1) end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb index 3b19fd145..692ffb267 100644 --- a/spec/models/photo_spec.rb +++ b/spec/models/photo_spec.rb @@ -20,13 +20,13 @@ describe Photo do describe "protected attributes" do it "doesn't allow mass assignment of person" do @photo.save! - @photo.update_attributes(:person => Factory(:person)) - @photo.reload.person.should == @user.person + @photo.update_attributes(:author => Factory(:person)) + @photo.reload.author.should == @user.person end it "doesn't allow mass assignment of person_id" do @photo.save! - @photo.update_attributes(:person_id => Factory(:person).id) - @photo.reload.person.should == @user.person + @photo.update_attributes(:author_id => Factory(:person).id) + @photo.reload.author.should == @user.person end it 'allows assignmant of caption' do @photo.save! @@ -50,14 +50,14 @@ describe Photo do it 'has a constructor' do image = File.open(@fixture_name) photo = Photo.diaspora_initialize( - :person => @user.person, :user_file => image) + :author => @user.person, :user_file => image) photo.created_at.nil?.should be_true photo.image.read.nil?.should be_false end it 'sets a remote url' do image = File.open(@fixture_name) photo = Photo.diaspora_initialize( - :person => @user.person, :user_file => image) + :author => @user.person, :user_file => image) photo.remote_photo_path.should include("http") photo.remote_photo_name.should include(".png") end @@ -139,7 +139,7 @@ describe Photo do xml = @photo.to_diaspora_xml @photo.destroy - zord = Postzord::Receiver.new(user2, :person => @photo.person) + zord = Postzord::Receiver.new(user2, :person => @photo.author) zord.parse_and_receive(xml) new_photo = Photo.where(:guid => @photo.guid).first diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 96b56e22b..d2b8c7bb5 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -12,7 +12,7 @@ describe Post do describe 'deletion' do it 'should delete a posts comments on delete' do - post = Factory.create(:status_message, :person => @user.person) + post = Factory.create(:status_message, :author => @user.person) @user.comment "hey", :on => post post.destroy Post.where(:id => post.id).empty?.should == true diff --git a/spec/models/post_visibility_spec.rb b/spec/models/post_visibility_spec.rb index 3c2cf91c3..ae8a1141c 100644 --- a/spec/models/post_visibility_spec.rb +++ b/spec/models/post_visibility_spec.rb @@ -6,7 +6,7 @@ describe PostVisibility do @aspect = @user.aspects.create(:name => 'Boozers') @person = Factory(:person) - @post = Factory(:status_message, :person => @person) + @post = Factory(:status_message, :author => @person) end it 'has an aspect' do pv = PostVisibility.new(:aspect => @aspect) diff --git a/spec/models/status_message_spec.rb b/spec/models/status_message_spec.rb index 7beb656e0..697bef70b 100644 --- a/spec/models/status_message_spec.rb +++ b/spec/models/status_message_spec.rb @@ -21,11 +21,11 @@ describe StatusMessage do end describe '#diaspora_handle=' do - it 'sets #person' do + it 'sets #author' do person = Factory.create(:person) - post = Factory.create(:status_message, :person => @user.person) + post = Factory.create(:status_message, :author => @user.person) post.diaspora_handle = person.diaspora_handle - post.person.should == person + post.author.should == person end end it "should have either a message or at least one photo" do @@ -167,7 +167,7 @@ STR end describe "XML" do before do - @message = Factory.create(:status_message, :message => "I hate WALRUSES!", :person => @user.person) + @message = Factory.create(:status_message, :message => "I hate WALRUSES!", :author => @user.person) @xml = @message.to_xml.to_s end it 'serializes the unescaped, unprocessed message' do @@ -193,7 +193,7 @@ STR @marshalled.guid.should == @message.guid end it 'marshals the author' do - @marshalled.person.should == @message.person + @marshalled.author.should == @message.author end it 'marshals the diaspora_handle' do @marshalled.diaspora_handle.should == @message.diaspora_handle diff --git a/spec/models/user/attack_vectors_spec.rb b/spec/models/user/attack_vectors_spec.rb index d40d7d23b..de2bfb50d 100644 --- a/spec/models/user/attack_vectors_spec.rb +++ b/spec/models/user/attack_vectors_spec.rb @@ -67,7 +67,7 @@ describe "attack vectors" do zord = Postzord::Receiver.new(user, :salmon_xml => salmon_xml) zord.perform - malicious_message = Factory.build( :status_message, :id => original_message.id, :message => 'BAD!!!', :person => user3.person) + malicious_message = Factory.build(:status_message, :id => original_message.id, :message => 'BAD!!!', :author => user3.person) salmon_xml = user3.salmon(malicious_message).xml_for(user.person) zord = Postzord::Receiver.new(user, :salmon_xml => salmon_xml) zord.perform @@ -83,7 +83,7 @@ describe "attack vectors" do zord.perform lambda { - malicious_message = Factory.build( :status_message, :id => original_message.id, :message => 'BAD!!!', :person => user2.person) + malicious_message = Factory.build( :status_message, :id => original_message.id, :message => 'BAD!!!', :author => user2.person) salmon_xml2 = user3.salmon(malicious_message).xml_for(user.person) zord = Postzord::Receiver.new(user, :salmon_xml => salmon_xml) diff --git a/spec/models/user/commenting_spec.rb b/spec/models/user/commenting_spec.rb deleted file mode 100644 index 8534549e5..000000000 --- a/spec/models/user/commenting_spec.rb +++ /dev/null @@ -1,43 +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 - - let!(:user1){alice} - let!(:user2){bob} - let!(:aspect1){user1.aspects.first} - let!(:aspect2){user2.aspects.first} - - before do - @post = user1.build_post(:status_message, :message => "hey", :to => aspect1.id) - @post.save - user1.dispatch_post(@post, :to => "all") - end - - describe '#dispatch_comment' do - context "post owner's contact is commenting" do - it "doesn't call receive on local users" do - user1.should_not_receive(:receive_comment) - user2.should_not_receive(:receive_comment) - - comment = user2.build_comment "why so formal?", :on => @post - comment.save! - user2.dispatch_comment comment - end - end - - context "post owner is commenting on own post" do - it "doesn't call receive on local users" do - user1.should_not_receive(:receive_comment) - user2.should_not_receive(:receive_comment) - - comment = user1.build_comment "why so formal?", :on => @post - comment.save! - user1.dispatch_comment comment - end - end - end -end diff --git a/spec/models/user/querying_spec.rb b/spec/models/user/querying_spec.rb index da876409c..bce6037c4 100644 --- a/spec/models/user/querying_spec.rb +++ b/spec/models/user/querying_spec.rb @@ -47,7 +47,7 @@ describe User do describe "#visible_posts" do it "queries by person id" do - query = @user2.visible_posts(:person_id => @user2.person.id) + query = @user2.visible_posts(:author_id => @user2.person.id) query.include?(@status_message1).should == true query.include?(@status_message2).should == true query.include?(@status_message3).should == false @@ -195,7 +195,7 @@ describe User do end it 'returns nil if the input is nil' do - @user.contact_for(nil).should be_nil + @user.contact_for(nil).should be_nil end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 078107eef..41008ef54 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -271,7 +271,7 @@ describe User do fixture_name = File.join(File.dirname(__FILE__), '..', 'fixtures', fixture_filename) image = File.open(fixture_name) @photo = Photo.diaspora_initialize( - :person => alice.person, :user_file => image) + :author => alice.person, :user_file => image) @photo.save! @params = {:photo => @photo} end diff --git a/spec/shared_behaviors/relayable.rb b/spec/shared_behaviors/relayable.rb new file mode 100644 index 000000000..f6cc2d1a3 --- /dev/null +++ b/spec/shared_behaviors/relayable.rb @@ -0,0 +1,88 @@ +# 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 Diaspora::Relayable do + shared_examples_for "it is relayable" do + context 'encryption' do + describe '#parent_author_signature' do + it 'should sign the object if the user is the post author' do + @object_by_parent_author.verify_parent_author_signature.should be_true + end + + it 'does not sign as the parent author is not parent' do + @object_by_recipient.author_signature = @object_by_recipient.send(:sign_with_key, @local_leia.encryption_key) + @object_by_recipient.verify_parent_author_signature.should be_false + end + + it 'should verify a object made on a remote post by a different contact' do + @object_by_recipient.author_signature = @object_by_recipient.send(:sign_with_key, @local_leia.encryption_key) + @object_by_recipient.parent_author_signature = @object_by_recipient.send(:sign_with_key, @local_luke.encryption_key) + @object_by_recipient.verify_parent_author_signature.should be_true + end + end + + describe '#author_signature' do + it 'should sign as the object author' do + @object_on_remote_parent.signature_valid?.should be_true + @object_by_parent_author.signature_valid?.should be_true + @object_by_recipient.signature_valid?.should be_true + end + end + end + + context 'propagation' do + describe '#receive' do + it 'does not overwrite a object that is already in the db' do + lambda{ + @dup_object_by_parent_author.receive(@local_leia, @local_luke.person) + }.should_not change(@dup_object_by_parent_author.class, :count) + end + + it 'does not process if post_creator_signature is invalid' do + @object_by_parent_author.delete # remove object from db so we set a creator sig + @dup_object_by_parent_author.parent_author_signature = "dsfadsfdsa" + @dup_object_by_parent_author.receive(@local_leia, @local_luke.person).should == nil + end + + it 'signs when the person receiving is the parent author' do + @object_by_recipient.save + @object_by_recipient.receive(@local_luke, @local_leia.person) + @object_by_recipient.reload.parent_author_signature.should_not be_blank + end + + it 'dispatches when the person receiving is the parent author' do + p = Postzord::Dispatch.new(@local_luke, @object_by_recipient) + p.should_receive(:post) + Postzord::Dispatch.stub!(:new).and_return(p) + @object_by_recipient.receive(@local_luke, @local_leia.person) + end + + it 'sockets to the user' do + pending + @object_by_recipient.should_receive(:socket_to_user).exactly(3).times + @object_by_recipient.receive(@local_luke, @local_leia.person) + end + + it 'calls after_receive callback' do + @object_by_recipient.should_receive(:after_receive) + @object_by_recipient.class.stub(:where).and_return([@object_by_recipient]) + @object_by_recipient.receive(@local_luke, @local_leia.person) + end + end + + describe '#subscribers' do + it 'returns the posts original audience, if the post is owned by the user' do + @object_by_parent_author.subscribers(@local_luke).map(&:id).should =~ [@local_leia.person, @remote_raphael].map(&:id) + end + + it 'returns the owner of the original post, if the user owns the object' do + @object_by_recipient.subscribers(@local_leia).map(&:id).should =~ [@local_luke.person].map(&:id) + end + end + end + end +end + diff --git a/spec/support/user_methods.rb b/spec/support/user_methods.rb index e47c2fd68..3d091d914 100644 --- a/spec/support/user_methods.rb +++ b/spec/support/user_methods.rb @@ -18,10 +18,7 @@ class User fantasy_resque do p = build_post(class_name, opts) if p.save! - raise 'MongoMapper failed to catch a failed save' unless p.id - self.aspects.reload - aspects = self.aspects_from_ids(opts[:to]) add_to_streams(p, aspects) dispatch_post(p, :to => opts[:to]) @@ -34,8 +31,7 @@ class User fantasy_resque do c = build_comment(text, options) if c.save! - raise 'MongoMapper failed to catch a failed save' unless c.id - dispatch_comment(c) + Postzord::Dispatch.new(self, c).post end c end