Merge branch 'private_messages'

Conflicts:
	app/controllers/status_messages_controller.rb
	app/models/data_point.rb
	app/models/status_message.rb
	db/schema.rb
	lib/fake.rb
	public/stylesheets/sass/application.sass
	spec/models/mention_spec.rb
This commit is contained in:
danielgrippi 2011-03-08 21:23:41 -08:00
commit 1f5edb1d92
113 changed files with 2682 additions and 625 deletions

View file

@ -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)

View file

@ -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

View file

@ -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'

View file

@ -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{

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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
"<img alt=\"#{h(person.name)}\" class=\"avatar\" #{("data-owner_id="+@user_id.to_s) if @user_id} data-person_id=\"#{person.id}\" src=\"#{person.profile.image_url(size)}\" title=\"#{h(person.name)}\">".html_safe
end
def person_link(person)
"<a href='/people/#{person.id}'>
def person_link(person, opts={})
"<a href='/people/#{person.id}' class='#{opts[:class]}'>
#{h(person.name)}
</a>".html_safe
end

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
module PrivateMessagesHelper
end

View file

@ -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?

View file

@ -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}"

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,6 @@
class ConversationVisibility < ActiveRecord::Base
belongs_to :conversation
belongs_to :person
end

View file

@ -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

View file

@ -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

82
app/models/message.rb Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -88,7 +88,7 @@ class StatusMessage < Post
<<-XML
<entry>
<title>#{x(self.formatted_message(:plain_text => true))}</title>
<link rel="alternate" type="text/html" href="#{person.url}p/#{self.id}"/>
<link rel="alternate" type="text/html" href="#{self.author.url}status_messages/#{self.id}"/>
<id>#{person.url}posts/#{self.id}</id>
<published>#{self.created_at.xmlschema}</published>
<updated>#{self.updated_at.xmlschema}</updated>

View file

@ -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

View file

@ -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)

View file

@ -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})"

View file

@ -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'), '#'

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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')

View file

@ -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')

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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])

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

135
lib/diaspora/relayable.rb Normal file
View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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)

View file

@ -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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

BIN
public/images/reply.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -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);
};

View file

@ -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('<ul class="as-selections" id="as-selections-'+x+'"></ul>').wrap('<li class="as-original" id="as-original-'+x+'"></li>');
var selections_holder = $("#as-selections-"+x);
var org_li = $("#as-original-"+x);
var results_holder = $('<div class="as-results" id="as-results-'+x+'"></div>').hide();
var results_ul = $('<ul class="as-list"></ul>');
var values_input = $('<input type="hidden" class="as-values" name="'+x+'" id="as-values-'+x+'" />');
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('<li class="as-message">'+opts.limitText+'</li>');
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<d_count;i++){
var num = i;
num_count++;
var forward = false;
if(opts.searchObjProps == "value") {
var str = data[num].value;
} else {
var str = "";
var names = opts.searchObjProps.split(",");
for(var y=0;y<names.length;y++){
var name = $.trim(names[y]);
str = str+data[num][name]+" ";
}
}
if(str){
if (!opts.matchCase){ str = str.toLowerCase(); }
if(str.search(query) != -1 && values_input.val().search(","+data[num][opts.selectedValuesProp]+",") == -1){
forward = true;
}
}
if(forward){
var formatted = $('<li class="as-result-item" id="as-result-item-'+num+'"></li>').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,"<em>$1</em>");
}
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('<li class="as-message">'+opts.emptyText+'</li>');
}
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 = $('<li class="as-selection-item" id="as-selection-'+num+'"></li>').click(function(){
opts.selectionClick.call(this, $(this));
selections_holder.children().removeClass("selected");
$(this).addClass("selected");
}).mousedown(function(){ input_focus = false; });
var close = $('<a class="as-close">&times;</a>').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);

View file

@ -165,6 +165,14 @@ var View = {
}
},
conversation_participants: {
bind: function() {
$(".conversation_participants img").tipsy({
live: true
});
}
},
whatIsThis: {
bind: function() {
$(".what_is_this").tipsy({

View file

@ -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

View file

@ -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;
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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|

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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)}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

102
spec/models/message_spec.rb Normal file
View file

@ -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

Some files were not shown because too many files have changed in this diff Show more