Merge branch 'master' of git://github.com/diaspora/diaspora into input-placeholders

This commit is contained in:
Florian Staudacher 2012-01-19 13:29:35 +01:00
commit ad5a17776f
113 changed files with 1199 additions and 1507 deletions

View file

@ -32,6 +32,7 @@ group :production do # we don't install these on travis to speed up test runs
gem 'newrelic_rpm'
gem 'rack-google-analytics', :require => 'rack/google-analytics'
gem 'rack-piwik', :require => 'rack/piwik'
gem 'rack-ssl', :require => 'rack/ssl'
end
# configuration
@ -116,7 +117,7 @@ gem 'jasmine', '~> 1.1.2'
group :test do
gem 'capybara', '~> 1.1.2'
gem 'cucumber-rails', '1.2.1'
gem 'cucumber-rails', '1.2.1', :require => false
gem 'cucumber-api-steps', '0.6', :require => false
gem 'database_cleaner', '0.7.1'
gem 'diaspora-client', :git => 'git://github.com/diaspora/diaspora-client.git'
@ -149,4 +150,7 @@ group :development do
gem 'ruby-debug19', :platforms => :ruby_19
gem 'ruby-debug', :platforms => :mri_18
gem 'yard', :require => false
# speed up development requests (already pulled into rails 3.2)
gem 'active_reload'
end

View file

@ -68,6 +68,7 @@ GEM
rack-mount (~> 0.6.14)
rack-test (~> 0.5.7)
tzinfo (~> 0.3.23)
active_reload (0.6.1)
activemodel (3.0.11)
activesupport (= 3.0.11)
builder (~> 2.1.2)
@ -147,7 +148,7 @@ GEM
abstract (>= 1.0.0)
eventmachine (0.12.10)
excon (0.9.4)
factory_girl (2.4.0)
factory_girl (2.4.2)
activesupport
factory_girl_rails (1.5.0)
factory_girl (~> 2.4.0)
@ -292,6 +293,8 @@ GEM
rack-mount (0.6.14)
rack (>= 1.0.0)
rack-piwik (0.1.2)
rack-ssl (1.3.2)
rack
rack-test (0.5.7)
rack (>= 1.0)
rails (3.0.11)
@ -416,6 +419,7 @@ PLATFORMS
DEPENDENCIES
SystemTimer (= 1.2.3)
active_reload
activerecord-import
acts-as-taggable-on!
acts_as_api
@ -471,6 +475,7 @@ DEPENDENCIES
pg
rack-google-analytics
rack-piwik
rack-ssl
rails (= 3.0.11)
rails-i18n
redcarpet (= 2.0.1)

View file

@ -1,9 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class Api::V0::TagsController < ApplicationController
def show
render :json => Api::V0::Serializers::Tag.new(params[:name])
end
end

View file

@ -1,13 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class Api::V0::UsersController < ApplicationController
def show
if user = User.find_by_username(params[:username])
render :json => Api::V0::Serializers::User.new(user)
else
head :not_found
end
end
end

View file

@ -83,7 +83,7 @@ class ApplicationController < ActionController::Base
def redirect_unless_admin
unless current_user.admin?
redirect_to multi_url, :notice => 'you need to be an admin to do that'
redirect_to multi_stream_url, :notice => 'you need to be an admin to do that'
return
end
end
@ -111,7 +111,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_in_path_for(resource)
stored_location_for(:user) || (current_user.getting_started? ? getting_started_path : multi_path)
stored_location_for(:user) || (current_user.getting_started? ? getting_started_path : multi_stream_path)
end
def tag_followings
@ -127,23 +127,7 @@ class ApplicationController < ActionController::Base
@tags ||= current_user.followed_tags
end
# @param stream_klass [Constant]
# @return [String] JSON representation of posts given a [Stream] constant.
def stream_json(stream_klass)
render_for_api :backbone, :json => stream(stream_klass).stream_posts, :root => :posts
end
def stream(stream_klass)
authenticate_user!
stream_klass.new(current_user, :max_time => max_time)
end
def default_stream_action(stream_klass)
@stream = stream(stream_klass)
render 'aspects/index'
end
def max_time
params[:max_time] ? Time.at(params[:max_time].to_i) : Time.now
params[:max_time] ? Time.at(params[:max_time].to_i) : Time.now + 1
end
end

View file

@ -2,29 +2,13 @@
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, "lib", 'stream', "aspect")
class AspectsController < ApplicationController
before_filter :authenticate_user!
before_filter :save_selected_aspects, :only => :index
before_filter :ensure_page, :only => :index
respond_to :html,
:js,
:json
def index
stream_klass = Stream::Aspect
aspect_ids = (session[:a_ids] ? session[:a_ids] : [])
@stream = Stream::Aspect.new(current_user, aspect_ids,
:max_time => params[:max_time].to_i)
respond_with do |format|
format.html { render 'aspects/index' }
format.json{ render_for_api :backbone, :json => @stream.stream_posts, :root => :posts }
end
end
def create
@aspect = current_user.aspects.create(params[:aspect])
@ -132,16 +116,4 @@ class AspectsController < ApplicationController
end
@aspect.save
end
def ensure_page
params[:max_time] ||= Time.now + 1
end
private
def save_selected_aspects
if params[:a_ids].present?
session[:a_ids] = params[:a_ids]
end
end
end

View file

@ -1,19 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, 'lib','stream', 'comments')
class CommentStreamController < ApplicationController
respond_to :html, :json
def index
stream_klass = Stream::Comments
respond_with do |format|
format.html{ default_stream_action(stream_klass) }
format.json{ stream_json(stream_klass) }
end
end
end

View file

@ -6,7 +6,7 @@ class HomeController < ApplicationController
def show
if current_user
redirect_to multi_path if current_user
redirect_to multi_stream_path if current_user
elsif is_mobile_device?
redirect_to user_session_path
else

View file

@ -1,19 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, 'lib','stream', 'likes')
class LikeStreamController < ApplicationController
respond_to :html, :json
def index
stream_klass = Stream::Likes
respond_with do |format|
format.html{ default_stream_action(stream_klass) }
format.json{ stream_json(stream_klass) }
end
end
end

View file

@ -1,19 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, 'lib','stream', 'mention')
class MentionsController < ApplicationController
respond_to :html, :json
def index
stream_klass = Stream::Mention
respond_with do |format|
format.html{ default_stream_action(stream_klass) }
format.json{ stream_json(stream_klass) }
end
end
end

View file

@ -1,20 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, 'lib', 'stream', 'multi')
class MultisController < ApplicationController
respond_to :html, :json
def index
stream_klass = Stream::Multi
respond_with do |format|
format.html{ default_stream_action(stream_klass) }
format.mobile{ default_stream_action(stream_klass) }
format.json{ stream_json(stream_klass) }
end
end
end

View file

@ -50,7 +50,7 @@ class NotificationsController < VannaController
post_process :html do
def post_read_all(json)
Response.new(:status => 302, :location => multi_path)
Response.new(:status => 302, :location => multi_stream_path)
end
end

View file

@ -2,12 +2,9 @@
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, 'lib', 'stream', 'public')
class PostsController < ApplicationController
before_filter :authenticate_user!, :except => :show
before_filter :set_format_if_malformed_from_status_net, :only => :show
before_filter :redirect_unless_admin, :only => :index
respond_to :html,
:mobile,
@ -54,7 +51,7 @@ class PostsController < ApplicationController
respond_to do |format|
format.js {render 'destroy'}
format.json { render :nothing => true, :status => 204 }
format.all {redirect_to multi_path}
format.all {redirect_to multi_stream_path}
end
else
Rails.logger.info "event=post_destroy status=failure user=#{current_user.diaspora_handle} reason='User does not own post'"
@ -62,16 +59,12 @@ class PostsController < ApplicationController
end
end
def index
default_stream_action(Stream::Public)
end
private
def set_format_if_malformed_from_status_net
request.format = :html if request.format == 'application/html+xml'
end
private
def user_can_not_comment_on_post?
if @post.public && @post.author.local?
false

View file

@ -11,37 +11,14 @@ class ShareVisibilitiesController < ApplicationController
params[:shareable_id] ||= params[:post_id]
params[:shareable_type] ||= 'Post'
@post = accessible_post
@contact = current_user.contact_for(@post.author)
if @contact && @vis = ShareVisibility.where(:contact_id => @contact.id,
:shareable_id => params[:shareable_id],
:shareable_type => params[:shareable_type]).first
@vis.hidden = !@vis.hidden
if @vis.save
update_cache(@vis)
render :nothing => true, :status => 200
return
end
end
render :nothing => true, :status => 403
vis = current_user.toggle_hidden_shareable(accessible_post)
RedisCache.update_cache_for(current_user, accessible_post, vis)
render :nothing => true, :status => 200
end
protected
def update_cache(visibility)
return unless RedisCache.configured?
cache = RedisCache.new(current_user, 'created_at')
if visibility.hidden?
cache.remove(accessible_post.id)
else
cache.add(accessible_post.created_at.to_i, accessible_post.id)
end
end
def accessible_post
@post ||= Post.where(:id => params[:post_id]).select("id, guid, author_id, created_at").first
@post ||= params[:shareable_type].constantize.where(:id => params[:post_id]).select("id, guid, author_id, created_at").first
end
end

View file

@ -70,7 +70,7 @@ class StatusMessagesController < ApplicationController
respond_to do |format|
format.html { redirect_to :back}
format.mobile{ redirect_to multi_path}
format.mobile{ redirect_to multi_stream_path}
format.json{ render :json => @status_message.as_api_response(:backbone), :status => 201 }
end
else

View file

@ -0,0 +1,71 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require File.join(Rails.root, "lib", "stream", "aspect")
require File.join(Rails.root, "lib", "stream", "multi")
require File.join(Rails.root, "lib", "stream", "comments")
require File.join(Rails.root, "lib", "stream", "likes")
require File.join(Rails.root, "lib", "stream", "mention")
require File.join(Rails.root, "lib", "stream", "followed_tag")
class StreamsController < ApplicationController
before_filter :authenticate_user!
before_filter :save_selected_aspects, :only => :aspects
before_filter :redirect_unless_admin, :only => :public
respond_to :html,
:mobile,
:json
def aspects
aspect_ids = (session[:a_ids] ? session[:a_ids] : [])
@stream = Stream::Aspect.new(current_user, aspect_ids,
:max_time => max_time)
stream_responder
end
def public
stream_responder(Stream::Public)
end
def multi
stream_responder(Stream::Multi)
end
def commented
stream_responder(Stream::Comments)
end
def liked
stream_responder(Stream::Likes)
end
def mentioned
stream_responder(Stream::Mention)
end
def followed_tags
stream_responder(Stream::FollowedTag)
end
private
def stream_responder(stream_klass=nil)
if stream_klass.present?
@stream ||= stream_klass.new(current_user, :max_time => max_time)
end
respond_with do |format|
format.html { render 'layouts/main_stream' }
format.mobile { render 'layouts/main_stream' }
format.json { render_for_api :backbone, :json => @stream.stream_posts, :root => :posts }
end
end
def save_selected_aspects
if params[:a_ids].present?
session[:a_ids] = params[:a_ids]
end
end
end

View file

@ -2,22 +2,12 @@
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
#
require File.join(Rails.root, 'lib', 'stream', 'followed_tag')
class TagFollowingsController < ApplicationController
before_filter :authenticate_user!
respond_to :html, :json
def index
stream_klass = Stream::FollowedTag
respond_with do |format|
format.html{ default_stream_action(stream_klass) }
format.json{ stream_json(stream_klass) }
end
end
# POST /tag_followings
# POST /tag_followings.xml
def create
@ -73,6 +63,6 @@ class TagFollowingsController < ApplicationController
@tag_following = current_user.tag_followings.create(:tag_id => @tag.id)
end
end
redirect_to multi_path
redirect_to multi_stream_path
end
end

View file

@ -87,7 +87,7 @@ class UsersController < ApplicationController
if params[:user] && params[:user][:current_password] && current_user.valid_password?(params[:user][:current_password])
current_user.close_account!
sign_out current_user
redirect_to(multi_path, :notice => I18n.t('users.destroy.success'))
redirect_to(multi_stream_path, :notice => I18n.t('users.destroy.success'))
else
if params[:user].present? && params[:user][:current_password].present?
flash[:error] = t 'users.destroy.wrong_password'
@ -111,7 +111,7 @@ class UsersController < ApplicationController
format.any { redirect_to person_path(user.person.id) }
end
else
redirect_to multi_path, :error => I18n.t('users.public.does_not_exist', :username => params[:username])
redirect_to multi_stream_path, :error => I18n.t('users.public.does_not_exist', :username => params[:username])
end
end
@ -127,14 +127,14 @@ class UsersController < ApplicationController
def logged_out
@page = :logged_out
if user_signed_in?
redirect_to multi_path
redirect_to multi_stream_path
end
end
def getting_started_completed
user = current_user
user.update_attributes(:getting_started => false)
redirect_to multi_path
redirect_to multi_stream_path
end
def export
@ -150,7 +150,7 @@ class UsersController < ApplicationController
def user_photo
username = params[:username].split('@')[0]
user = User.find_by_username(username)
if user.present?
if user.present?
redirect_to user.profile.image_url
else
render :nothing => true, :status => 404

View file

@ -83,7 +83,7 @@ class VannaController < Vanna::Base
def redirect_unless_admin
unless current_user.admin?
redirect_to multi_path, :notice => 'you need to be an admin to do that'
redirect_to multi_stream_path, :notice => 'you need to be an admin to do that'
return
end
end

View file

@ -32,6 +32,10 @@ module ApplicationHelper
without_close_html + link_to(image_tag('deletelabel.png'), "#", :class => 'close')
end
def diaspora_id_host
User.diaspora_id_host
end
def jquery_include_tag
javascript_include_tag('//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js') +
content_tag(:script) do

View file

@ -10,25 +10,21 @@ module StreamHelper
"/apps/1?#{{:max_time => @posts.last.created_at.to_i}.to_param}"
elsif controller.instance_of?(PeopleController)
local_or_remote_person_path(@person, :max_time => time_for_scroll(@stream))
elsif controller.instance_of?(TagFollowingsController)
tag_followings_path(:max_time => time_for_scroll(@stream))
elsif controller.instance_of?(MentionsController)
mentions_path(:max_time => time_for_scroll(@stream))
elsif controller.instance_of?(MultisController)
multi_path(:max_time => time_for_scroll(@stream))
elsif controller.instance_of?(PostsController)
public_stream_path(:max_time => time_for_scroll(@stream))
elsif controller.instance_of?(AspectsController)
aspects_path(:max_time => time_for_scroll(@stream), :a_ids => @stream.aspect_ids)
elsif controller.instance_of?(LikeStreamController)
like_stream_path(:max_time => time_for_scroll(@stream))
elsif controller.instance_of?(CommentStreamController)
comment_stream_path(:max_time => time_for_scroll(@stream))
elsif controller.instance_of?(StreamsController)
multi_stream_path(:max_time => time_for_scroll(@stream))
else
raise 'in order to use pagination for this new controller, update next_page_path in stream helper'
end
end
def reshare?(post)
post.instance_of?(Reshare)
end
private
def time_for_scroll(stream)
if stream.stream_posts.empty?
(Time.now() + 1).to_i
@ -36,8 +32,4 @@ module StreamHelper
stream.stream_posts.last.send(stream.order.to_sym).to_i
end
end
def reshare?(post)
post.instance_of?(Reshare)
end
end

View file

@ -1,19 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class Api::V0::Serializers::Tag
def initialize(tag)
@stream = Stream::Tag.new(nil, tag)
end
def as_json(opts={})
{
"name" => @stream.tag_name,
"person_count" => @stream.tagged_people_count,
"followed_count" => @stream.tag_follow_count,
"posts" => []
}
end
end

View file

@ -1,20 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class Api::V0::Serializers::User
def initialize(user)
@person = user.person
@profile = @person.profile
end
def as_json(opts={})
{
"diaspora_id" => @person.diaspora_handle,
"first_name" => @profile.first_name,
"last_name" => @profile.last_name,
"image_url" => @profile.image_url,
"searchable" => @profile.searchable
}
end
end

View file

@ -103,6 +103,10 @@ HELP
end
end
def self.bare_pod_uri
self[:pod_uri].authority.gsub('www.', '')
end
def self.normalize_admins
self[:admins] ||= []
self[:admins].collect! { |username| username.downcase }

View file

@ -22,6 +22,7 @@ class Notification < ActiveRecord::Base
else
n = note_type.make_notification(recipient, target, actor, note_type)
end
if n
n.email_the_user(target, actor)
n
@ -43,7 +44,8 @@ class Notification < ActiveRecord::Base
private
def self.concatenate_or_create(recipient, target, actor, notification_type)
return nil if share_visiblity_is_hidden?(recipient, target)
return nil if suppress_notification?(recipient, target)
if n = notification_type.where(:target_id => target.id,
:target_type => target.class.base_class,
:recipient_id => recipient.id,
@ -64,22 +66,16 @@ private
def self.make_notification(recipient, target, actor, notification_type)
return nil if share_visiblity_is_hidden?(recipient, target)
return nil if suppress_notification?(recipient, target)
n = notification_type.new(:target => target,
:recipient_id => recipient.id)
:recipient_id => recipient.id)
n.actors = n.actors | [actor]
n.unread = false if target.is_a? Request
n.save!
n
end
#horrible hack that should not be here!
def self.share_visiblity_is_hidden?(recipient, post)
return false unless post.is_a?(Post)
contact = recipient.contact_for(post.author)
return false unless contact && recipient && post
pv = ShareVisibility.where(:contact_id => contact.id, :shareable_id => post.id, :shareable_type => post.class.base_class.to_s).first
pv.present? ? pv.hidden? : false
def self.suppress_notification?(recipient, post)
post.is_a?(Post) && recipient.is_shareable_hidden?(post)
end
end

View file

@ -51,7 +51,6 @@ class Person < ActiveRecord::Base
has_many :mentions, :dependent => :destroy
before_destroy :remove_all_traces
before_validation :clean_url
validates :url, :presence => true
@ -322,10 +321,6 @@ class Person < ActiveRecord::Base
end
private
def remove_all_traces
Notification.joins(:notification_actors).where(:notification_actors => {:person_id => self.id}).all.each{ |n| n.destroy}
end
def fix_profile
Webfinger.new(self.diaspora_handle).fetch
self.reload

View file

@ -71,27 +71,40 @@ class Post < ActiveRecord::Base
end
def self.excluding_blocks(user)
people = user.blocks.includes(:person).map{|b| b.person}
people = user.blocks.map{|b| b.person_id}
scope = scoped
if people.present?
where("posts.author_id NOT IN (?)", people.map { |person| person.id })
else
scoped
if people.any?
scope = scope.where("posts.author_id NOT IN (?)", people)
end
scope
end
def self.excluding_hidden_shareables(user)
scope = scoped
if user.has_hidden_shareables_of_type?
scope = scope.where('posts.id NOT IN (?)', user.hidden_shareables["#{self.base_class}"])
end
scope
end
def self.excluding_hidden_content(user)
excluding_blocks(user).excluding_hidden_shareables(user)
end
def self.for_a_stream(max_time, order, user=nil)
scope = self.for_visible_shareable_sql(max_time, order).
includes_for_a_stream
scope = scope.excluding_blocks(user) if user.present?
scope = scope.excluding_hidden_content(user) if user.present?
scope
end
#############
def self.diaspora_initialize params
def self.diaspora_initialize(params)
new_post = self.new params.to_hash
new_post.author = params[:author]
new_post.public = params[:public] if params[:public]

View file

@ -30,6 +30,8 @@ class User < ActiveRecord::Base
validates_associated :person
validate :no_person_with_same_username
serialize :hidden_shareables, Hash
has_one :person, :foreign_key => :owner_id
delegate :public_key, :posts, :photos, :owns?, :diaspora_handle, :name, :public_url, :profile, :first_name, :last_name, :to => :person
@ -99,6 +101,55 @@ class User < ActiveRecord::Base
end
end
def hidden_shareables
self[:hidden_shareables] ||= {}
end
def add_hidden_shareable(key, share_id, opts={})
if self.hidden_shareables.has_key?(key)
self.hidden_shareables[key] << share_id
else
self.hidden_shareables[key] = [share_id]
end
self.save unless opts[:batch]
self.hidden_shareables
end
def remove_hidden_shareable(key, share_id)
if self.hidden_shareables.has_key?(key)
self.hidden_shareables[key].delete(share_id)
end
end
def is_shareable_hidden?(shareable)
shareable_type = shareable.class.base_class.name
if self.hidden_shareables.has_key?(shareable_type)
self.hidden_shareables[shareable_type].include?(shareable.id.to_s)
else
false
end
end
def toggle_hidden_shareable(share)
share_id = share.id.to_s
key = share.class.base_class.to_s
if self.hidden_shareables.has_key?(key) && self.hidden_shareables[key].include?(share_id)
self.remove_hidden_shareable(key, share_id)
self.save
false
else
self.add_hidden_shareable(key, share_id)
self.save
true
end
end
def has_hidden_shareables_of_type?(t = Post)
share_type = t.base_class.to_s
self.hidden_shareables[share_type].present?
end
def self.create_from_invitation!(invitation)
user = User.new
user.generate_keys
@ -114,7 +165,6 @@ class User < ActiveRecord::Base
Resque.enqueue(Jobs::ResetPassword, self.id)
end
def update_user_preferences(pref_hash)
if self.disable_mail
UserPreference::VALID_EMAIL_TYPES.each{|x| self.user_preferences.find_or_create_by_email_type(x)}
@ -377,10 +427,14 @@ class User < ActiveRecord::Base
def set_person(person)
person.url = AppConfig[:pod_url]
person.diaspora_handle = "#{self.username}@#{AppConfig[:pod_uri].authority}"
person.diaspora_handle = "#{self.username}#{User.diaspora_id_host}"
self.person = person
end
def self.diaspora_id_host
"@#{AppConfig.bare_pod_uri}"
end
def seed_aspects
self.aspects.create(:name => I18n.t('aspects.seed.family'))
self.aspects.create(:name => I18n.t('aspects.seed.friends'))
@ -394,7 +448,6 @@ class User < ActiveRecord::Base
aq
end
def encryption_key
OpenSSL::PKey::RSA.new(serialized_private_key)
end
@ -403,32 +456,6 @@ class User < ActiveRecord::Base
AppConfig[:admins].present? && AppConfig[:admins].include?(self.username)
end
def remove_all_traces
disconnect_everyone
remove_mentions
remove_person
end
def remove_person
self.person.destroy
end
def disconnect_everyone
self.contacts.each do |contact|
if contact.person.remote?
self.disconnect(contact)
else
contact.person.owner.disconnected_by(self.person)
remove_contact(contact, :force => true)
end
end
self.aspects.destroy_all
end
def remove_mentions
Mention.where( :person_id => self.person.id).delete_all
end
def guard_unconfirmed_email
self.unconfirmed_email = nil if unconfirmed_email.blank? || unconfirmed_email == email
@ -445,7 +472,6 @@ class User < ActiveRecord::Base
end
end
# Generate public/private keys for User and associated Person
def generate_keys
key_size = (Rails.env == 'test' ? 512 : 4096)
@ -476,7 +502,7 @@ class User < ActiveRecord::Base
end
def no_person_with_same_username
diaspora_id = "#{self.username}@#{AppConfig[:pod_uri].host}"
diaspora_id = "#{self.username}#{User.diaspora_id_host}"
if self.username_changed? && Person.exists?(:diaspora_handle => diaspora_id)
errors[:base] << 'That username has already been taken'
end

View file

@ -27,7 +27,7 @@
= t('username')
= f.text_field :username, :title => t('registrations.new.enter_username')
%span.host_uri
= "@#{AppConfig[:pod_uri].host}"
= diaspora_id_host
.clearfix
%b
= t('email')

View file

@ -50,7 +50,7 @@
= yield
%header
= link_to(image_tag('white@2x.png', :height => 20, :width => 127, :id => 'header_title'), multi_path)
= link_to(image_tag('white@2x.png', :height => 20, :width => 127, :id => 'header_title'), multi_stream_path)
- if user_signed_in?
- if yield(:header_action).present?
= yield(:header_action)

View file

@ -13,9 +13,9 @@
= link_to image_tag('close_label.png'), getting_started_completed_path, :id => "gs-skip-x"
.span-23
%h1
= t('.welcome_to_diaspora', :name => current_user.first_name)
= t('aspects.index.welcome_to_diaspora', :name => current_user.first_name)
%h3
= t('.introduce_yourself')
= t('aspects.index.introduce_yourself')
%br
%br
%br
@ -31,21 +31,21 @@
.section
%ul.left_nav
%li
= link_to t("streams.multi.title"), multi_path, :class => 'home_selector', :rel => 'backbone'
= link_to t("streams.multi.title"), multi_stream_path, :class => 'home_selector', :rel => 'backbone'
= render 'aspects/aspect_listings', :stream => @stream
%ul.left_nav
%li
= link_to t('streams.mentions.title'), mentions_path, :class => 'home_selector', :rel => 'backbone'
= link_to t('streams.mentions.title'), mentioned_stream_path, :class => 'home_selector', :rel => 'backbone'
%ul.left_nav
%li
= link_to t('streams.comment_stream.title'), comment_stream_path, :class => 'home_selector', :rel => 'backbone'
= link_to t('streams.comment_stream.title'), commented_stream_path, :class => 'home_selector', :rel => 'backbone'
%ul.left_nav
%li
= link_to t('streams.like_stream.title'), like_stream_path, :class => 'home_selector', :rel => 'backbone'
= link_to t('streams.like_stream.title'), liked_stream_path, :class => 'home_selector', :rel => 'backbone'
#followed_tags_listing
= render 'tags/followed_tags_listings'

View file

@ -0,0 +1,18 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
%h2{:style => "padding:0 10px;display:none;"}
- if @stream.for_all_aspects?
= t('all_aspects')
- else
= @stream.aspect
#main_stream.stream
= render 'shared/stream', :posts => @stream.stream_posts
-if @stream.stream_posts.length > 0
#pagination
%a.more-link.paginate{:href => next_page_path}
%h1
= t("more")

View file

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
xmlns:hm='http://host-meta.net/xrd/1.0'>
<hm:Host><%= AppConfig[:pod_uri].authority %></hm:Host>
<hm:Host><%= AppConfig[:pod_bare_domain] %></hm:Host>
<Link rel='lrdd'
template='<%= AppConfig[:pod_url] %>webfinger?q={uri}'>
<Title>Resource Descriptor</Title>

View file

@ -28,7 +28,7 @@
= t('username')
= f.text_field :username, :title => t('registrations.new.enter_username')
%span.host_uri
= "@#{AppConfig[:pod_uri].host}"
= diaspora_id_host
.clearfix
%b
= t('email')

View file

@ -16,7 +16,7 @@
.centered
= f.text_field :username
%span.host_uri
= "@#{AppConfig[:pod_uri].host}"
= diaspora_id_host
.row
.row
= f.label :email, t('email')

View file

@ -5,7 +5,7 @@
- if user_signed_in?
%ul.left_nav
%li
%b=link_to t('streams.followed_tag.title'), tag_followings_path, :class => 'home_selector'
%b=link_to t('streams.followed_tag.title'), followed_tags_stream_path, :class => 'home_selector'
- if @stream.is_a?(Stream::FollowedTag)
%ul.sub_nav

View file

@ -18,7 +18,9 @@
</a>
</span>
<%= text %>
<div class="collapsible">
<%= text %>
</div>
<div class="comment_info">
<time class="timeago" datetime="<%= created_at %>"/>

View file

@ -12,6 +12,7 @@
<% } %>
<% } %>
<%= text %>
<%= o_embed_html %>
<div class="collapsible">
<%= text %>
<%= o_embed_html %>
</div>

View file

@ -4,10 +4,7 @@
- content_for :head do
:css
body {
margin-top: 220px;
}
body { margin-top: 220px; }
#grey_header
.row
@ -25,7 +22,7 @@
%h4
= t('.simply_visit')
%strong
= link_to AppConfig[:pod_url], multi_path
= link_to AppConfig[:pod_url], multi_stream_path
= t('.on_your_mobile_device')
%p.dull

View file

@ -15,30 +15,38 @@ defaults: &defaults
# However changing http to https is okay and has no consquences. If you do change it
# you have to start over as it's hardcoded into the database.
# For development and testing, you can leave this as is.
pod_url: "http://localhost:3000"
# Websocket host - leave as 0.0.0.0 unless you know what you are doing
socket_host: 0.0.0.0
# Websocket port - should normally be 8080 or 8081.
socket_port: 8080
pod_url: "http://localhost:3000/"
# Setting the root certificate bundle (this is operating system specific). Examples, uncomment one:
ca_file: '/etc/pki/tls/certs/ca-bundle.crt' # CentOS
#ca_file: '/etc/ssl/certs/ca-certificates.crt' # Debian
#ca_file: '/etc/ssl/certs/ca-certificates.crt' # Gentoo
# Secure websocket confguration (wss://).
# Requires SSL cert and key
socket_secure: false
socket_cert_chain_location: '/full/path/to/cert_chain.crt'
socket_private_key_location: '/full/path/to/file.key'
# URL for a remote redis, on the default port. Don't forget to restrict IP access!
# leave it empty for the default (localhost)
redis_url: ''
# Redis cache
# Enable the cache layer (Redis)
# If you expect to have thousands of users on your pod,
# we *highly* suggest you enable this.
# IMPORTANT: THE CACHE REQUIRES REDIS 2.4 OR LATER.
#
# By default, the cache layer will piggyback off of the Redis
# database used by your Resque workers.
redis_cache: false
# The location of your redis cache.
# IMPORTANT: DO NOT CHANGE THIS IF YOU DO NOT KNOW WHAT YOU ARE DOING!
#
# Leave this blank to use the same Redis database
# that your Resque workers use (happy path).
#
# This takes an ip (or DNS record). It assumes that your Redis database
# is running on the default Redis port.
redis_location: ''
# Amazon S3 for photos
# s3 config - if set, carrierwave will store your photos on s3. Otherwise they're on the filesystem.
@ -90,9 +98,6 @@ defaults: &defaults
# Enable extensive logging to log/{development,test,production}.log
debug: false
# Enable extensive logging to websocket server.
socket_debug : false
# Hoptoad api key, send failures to Hoptoad
hoptoad_api_key: ''
@ -105,6 +110,7 @@ defaults: &defaults
tumblr_consumer_key: ''
tumblr_consumer_secret: ''
# Miscellaneous
NEW_RELIC_LICENSE_KEY: ''
@ -116,12 +122,6 @@ defaults: &defaults
# will be disabled.
single_process_mode: false
# File containing pid of running script/websocket_server.rb
socket_pidfile: "log/diaspora-wsd.pid"
# Do not touch unless you know what you're doing
socket_collection_name: 'websocket'
# Diaspora is only tested against this default pubsub server. You probably don't want to change this.
pubsub_server: 'https://pubsubhubbub.appspot.com/'
@ -175,27 +175,10 @@ defaults: &defaults
# Sender address in Diaspora's outgoing mail.
smtp_sender_address: 'no-reply@joindiaspora.com'
# Redis cache
# Enable the cache layer (Redis)
# If you expect to have thousands of users on your pod,
# we *highly* suggest you enable this.
# IMPORTANT: THE CACHE REQUIRES REDIS 2.4 OR LATER.
#
# By default, the cache layer will piggyback off of the Redis
# database used by your Resque workers.
redis_cache: false
# The location of your redis cache.
# IMPORTANT: DO NOT CHANGE THIS IF YOU DO NOT KNOW WHAT YOU ARE DOING!
#
# Leave this blank to use the same Redis database
# that your Resque workers use (happy path).
#
# This takes an ip (or DNS record). It assumes that your Redis database
# is running on the default Redis port.
redis_location: ''
#when set, your pod will not force you to use https in production
#NOTE: not all features of Diaspora work without SSL, and you may have trouble federating
# with other pods
circumvent_ssl_requirement: false
# Web tracking
@ -216,12 +199,6 @@ defaults: &defaults
cloudfiles_db_container: 'Database Backup'
cloudfiles_images_container: 'Image Backup'
# Donations
# Leave this blank to not show the request for donations
# Use paypal for recurring donations
paypal_hosted_button_id: ""
#
# Use this section to override default settings in specific environments
#
@ -239,17 +216,17 @@ production:
test:
<<: *defaults
pod_url: "http://localhost:9887"
pod_url: "http://localhost:9887/"
socket_port: 8081
open_invitations: true
serve_static_assets: true
integration_1:
<<: *defaults
pod_url: "http://localhost:45789"
pod_url: "http://localhost:45789/"
serve_static_assets: true
integration_2:
<<: *defaults
pod_url: "http://localhost:34658"
pod_url: "http://localhost:34658/"
serve_static_assets: true

View file

@ -37,6 +37,7 @@ Diaspora::Application.configure do
# In production, Apache or nginx will already do this
config.serve_static_assets = false
# Enable serving of images, stylesheets, and javascripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"

View file

@ -0,0 +1,4 @@
if EnviromentConfiguration.enforce_ssl?
Rails.application.config.middleware.insert_before HoptoadNotifier::UserInformer, Rack::SSL
puts "Rack::SSL is enabled"
end

View file

@ -4,15 +4,10 @@
Diaspora::Application.routes.draw do
# Posting and Reading
resources :reshares
resources :aspects do
put :toggle_contact_visibility
end
resources :status_messages, :only => [:new, :create]
resources :posts, :only => [:show, :destroy] do
@ -20,12 +15,23 @@ Diaspora::Application.routes.draw do
resources :comments, :only => [:new, :create, :destroy, :index]
end
get 'p/:id' => 'posts#show', :as => 'short_post'
get 'public_stream' => 'posts#index', :as => 'public_stream'
# roll up likes into a nested resource above
resources :comments, :only => [:create, :destroy] do
resources :likes, :only => [:create, :destroy, :index]
end
# Streams
get "public" => "streams#public", :as => "public_stream"
get "stream" => "streams#multi", :as => "multi_stream"
get "followed_tags" => "streams#followed_tags", :as => "followed_tags_stream"
get "mentions" => "streams#mentioned", :as => "mentioned_stream"
get "liked" => "streams#liked", :as => "liked_stream"
get "commented" => "streams#commented", :as => "commented_stream"
get "aspects" => "streams#aspects", :as => "aspects_stream"
resources :aspects do
put :toggle_contact_visibility
end
get 'bookmarklet' => 'status_messages#bookmarklet'
@ -50,26 +56,19 @@ Diaspora::Application.routes.draw do
resources :tags, :only => [:index]
scope "tags/:name" do
post "tag_followings" => "tag_followings#create", :as => 'tag_tag_followings'
delete "tag_followings" => "tag_followings#destroy"
delete "tag_followings" => "tag_followings#destroy", :as => 'tag_tag_followings'
end
post "multiple_tag_followings" => "tag_followings#create_multiple", :as => 'multiple_tag_followings'
get "tag_followings" => "tag_followings#index", :as => 'tag_followings'
resources :mentions, :only => [:index]
resources "tag_followings", :only => [:create]
get 'comment_stream' => 'comment_stream#index', :as => 'comment_stream'
get 'like_stream' => 'like_stream#index', :as => 'like_stream'
get 'tags/:name' => 'tags#show', :as => 'tag'
resources :apps, :only => [:show]
#Cubbies info page
resource :token, :only => :show
resource :token, :only => :show
# Users and people
@ -120,8 +119,6 @@ Diaspora::Application.routes.draw do
get 'community_spotlight' => "contacts#spotlight", :as => 'community_spotlight'
get 'stream' => "multis#index", :as => 'multi'
resources :people, :except => [:edit, :update] do
resources :status_messages
resources :photos

View file

@ -0,0 +1,28 @@
class Person < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
end
class User < ActiveRecord::Base
serialize :hidden_shareables, Hash
end
class Contact < ActiveRecord::Base
belongs_to :user
end
class ShareVisibility < ActiveRecord::Base
belongs_to :contact
end
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'share_visibility_converter')
class MoveRecentlyHiddenPostsToUser < ActiveRecord::Migration
def self.up
add_column :users, :hidden_shareables, :text
ShareVisibilityConverter.copy_hidden_share_visibilities_to_users(true)
end
def self.down
remove_column :users, :hidden_shareables
end
end

View file

@ -463,6 +463,7 @@ ActiveRecord::Schema.define(:version => 20120114191018) do
t.boolean "show_community_spotlight_in_stream", :default => true, :null => false
t.boolean "auto_follow_back", :default => false
t.integer "auto_follow_back_aspect_id"
t.text "hidden_shareables"
end
add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true

View file

@ -16,7 +16,7 @@ Feature: invitation acceptance
And I preemptively confirm the alert
And I follow "awesome_button"
Then I should be on the multi page
Then I should be on the multi stream page
Scenario: accept invitation from user
Given I have been invited by a user
@ -34,7 +34,7 @@ Feature: invitation acceptance
And I preemptively confirm the alert
And I follow "awesome_button"
Then I should be on the multi page
Then I should be on the multi stream page
Scenario: sends an invitation
Given a user with email "bob@bob.bob"

View file

@ -1,11 +0,0 @@
Feature: API
In order to use a client application
as an epic developer
I need to get user's info
Scenario: Getting a users public profile
Given a user named "Maxwell S" with email "maxwell@example.com"
And I send and accept JSON
When I send a GET request for "/api/v0/users/maxwell_s"
Then the response status should be "200"
And the JSON response should have "first_name" with the text "Maxwell"

View file

@ -11,7 +11,7 @@ Feature: Change password
Then I should see "Password changed"
Then I should be on the new user session page
When I sign in with password "newsecret"
Then I should be on the multi page
Then I should be on the multi stream page
Scenario: Reset my password
Given a user with email "forgetful@users.net"

View file

@ -29,11 +29,11 @@ Feature: posting
Scenario: can stop following a tag from the tag page
When I press "Following #boss"
And I go to the tag_followings page
And I go to the followed tags stream page
Then I should not see "#boss" within ".left_nav"
Scenario: can stop following a tag from the homepage
When I go to the tag_followings page
When I go to the followed tags stream page
And I preemptively confirm the alert
And I hover over the "li.unfollow#tag-following-boss"
And I follow "unfollow_boss"

View file

@ -6,7 +6,7 @@ Feature: user authentication
And I fill in "Username" with "ohai"
And I fill in "Password" with "secret"
And I press "Sign in"
Then I should be on the multi page
Then I should be on the multi stream page
@javascript
Scenario: user logs out
@ -19,5 +19,5 @@ Feature: user authentication
Scenario: user uses token auth
Given a user with username "ohai" and password "secret"
When I post a photo with a token
And I go to the multi page
And I go to the multi stream page
Then I should be on the new user session page

View file

@ -22,6 +22,7 @@ Feature: oembed
Given I expand the publisher
When I fill in "status_message_fake_text" with "http://mytube.com/watch?v=M3r2XDceM6A&format=json"
And I press "Share"
And I wait for the ajax to finish
And I follow "Your Aspects"
Then I should not see a video player

View file

@ -12,7 +12,6 @@ Feature: viewing the photo lightbox
And I fill in "status_message_fake_text" with "Look at this dog"
And I press "Share"
And I wait for the ajax to finish
And I am on the aspects page
Scenario: viewing a photo
Then I should see an image attached to the post

View file

@ -19,6 +19,8 @@ Feature: posting from the main page
Given I expand the publisher
When I fill in "status_message_fake_text" with "I am eating a yogurt"
And I press "Share"
And I wait for the ajax to finish
And I go to the aspects page
Then I should see "I am eating a yogurt" within ".stream_element"

View file

@ -19,18 +19,18 @@ Feature: new user registration
And I preemptively confirm the alert
And I follow "awesome_button"
Then I should be on the multi page
Then I should be on the multi stream page
And I should not see "awesome_button"
Scenario: new user skips the setup wizard
When I preemptively confirm the alert
And I follow "awesome_button"
Then I should be on the multi page
Then I should be on the multi stream page
Scenario: closing a popover clears getting started
When I preemptively confirm the alert
And I follow "awesome_button"
Then I should be on the multi page
Then I should be on the multi stream page
And I have turned off jQuery effects
And I wait for the popovers to appear
And I click close on all the popovers

View file

@ -2,7 +2,7 @@ module NavigationHelpers
def path_to(page_name)
case page_name
when /^the home(?: )?page$/
multi_path
multi_stream_path
when /^step (\d)$/
if $1.to_i == 1
getting_started_path

View file

@ -19,6 +19,18 @@ class RedisCache
AppConfig[:redis_cache].present?
end
def self.update_cache_for(user, post, post_was_hidden)
return unless RedisCache.configured?
cache = RedisCache.new(user, 'created_at')
if post_was_hidden
cache.remove(post.id)
else
cache.add(post.created_at.to_i, post.id)
end
end
# @return [Boolean]
def cache_exists?
self.redis.exists(set_key)

View file

@ -1,5 +1,5 @@
module EnviromentConfiguration
ARRAY_SEPERATOR = '%|%'
def self.heroku?
ENV['HEROKU']
end
@ -29,6 +29,13 @@ module EnviromentConfiguration
end
end
def self.enforce_ssl?
return false unless Rails.env == 'production'
return false if ENV['NO_SSL']
return false if AppConfig[:circumvent_ssl_requirement].present?
true
end
def self.ca_cert_file_location
if self.heroku?
"/usr/lib/ssl/certs/ca-certificates.crt"

View file

@ -0,0 +1,53 @@
#we dont have the enviroment, and it is not carring over from the migration
unless defined?(Person)
class Person < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
end
end
unless defined?(User)
class User < ActiveRecord::Base
serialize :hidden_shareables, Hash
end
end
unless defined?(Contact)
class Contact < ActiveRecord::Base
belongs_to :user
end
end
unless defined?(ShareVisibility)
class ShareVisibility < ActiveRecord::Base
belongs_to :contact
end
end
class ShareVisibilityConverter
RECENT = 2 # number of weeks to do in the migration
def self.copy_hidden_share_visibilities_to_users(only_recent = false)
query = ShareVisibility.where(:hidden => true).includes(:contact => :user)
query = query.where('share_visibilities.updated_at > ?', RECENT.weeks.ago) if only_recent
count = query.count
puts "Updating #{count} records in batches of 1000..."
batch_count = 1
query.find_in_batches do |visibilities|
puts "Updating batch ##{batch_count} of #{(count/1000)+1}..."
batch_count += 1
visibilities.each do |visibility|
begin
type = visibility.shareable_type
id = visibility.shareable_id.to_s
u = visibility.contact.user
u.hidden_shareables ||= {}
u.hidden_shareables[type] ||= []
u.hidden_shareables[type] << id unless u.hidden_shareables[type].include?(id)
u.save!(:validate => false)
rescue Exception =>e
puts "ERROR: #{e.message} skipping pv with id: #{visibility.id}"
end
end
end
end
end

View file

@ -2,7 +2,7 @@ class Stream::Multi < Stream::Base
# @return [String] URL
def link(opts)
Rails.application.routes.url_helpers.multi_path(opts)
Rails.application.routes.url_helpers.multi_stream_path(opts)
end
# @return [String]

View file

@ -4,7 +4,16 @@
namespace :db do
desc "rebuild and prepare test db"
task :rebuild => [:drop, :drop_integration, :create, :migrate, :seed, 'db:test:prepare']
task :rebuild do
Rake::Task['db:drop'].invoke
Rake::Task['db:drop_integration'].invoke
Rake::Task['db:create'].invoke
Rake::Task['db:migrate'].invoke
puts "seeding users, this will take awhile"
`rake db:seed` #ghetto hax as we have active record garbage in our models
puts "seeded!"
Rake::Task['db:test:prepare'].invoke
end
namespace :integration do
# desc 'Check for pending migrations and load the integration schema'

View file

@ -3,6 +3,13 @@
# the COPYRIGHT file.
namespace :migrations do
desc 'copy all hidden share visibilities from share_visibilities to users. Can be run with the site still up.'
task :copy_hidden_share_visibilities_to_users => [:environment] do
require File.join(Rails.root, 'lib', 'share_visibility_converter')
ShareVisibilityConverter.copy_hidden_share_visibilities_to_users
end
desc 'absolutify all existing image references'
task :absolutify_image_references do
require File.join(File.dirname(__FILE__), '..', '..', 'config', 'environment')

View file

@ -2,126 +2,110 @@ require File.join(Rails.root, 'lib/hcard')
require File.join(Rails.root, 'lib/webfinger_profile')
class Webfinger
class WebfingerFailedError < RuntimeError; end
attr_accessor :host_meta_xrd, :webfinger_profile_xrd,
:webfinger_profile, :hcard, :hcard_xrd, :person,
:account, :ssl
def initialize(account)
@account = account.strip.gsub('acct:','').to_s.downcase
@ssl = true
self.account = account
self.ssl = true
Rails.logger.info("event=webfinger status=initialized target=#{account}")
end
def fetch
return person if existing_person_with_profile?
create_or_update_person_from_webfinger_profile!
end
def self.in_background(account, opts={})
Resque.enqueue(Jobs::FetchWebfinger, account)
end
def fetch
begin
person = Person.by_account_identifier(@account)
if person
if person.profile
Rails.logger.info("event=webfinger status=success route=local target=#{@account}")
return person
end
end
#everything below should be private I guess
def account=(str)
@account = str.strip.gsub('acct:','').to_s.downcase
end
profile_url = get_xrd
webfinger_profile = get_webfinger_profile(profile_url)
if person
person.assign_new_profile_from_hcard(get_hcard(webfinger_profile))
fingered_person = person
else
fingered_person = make_person_from_webfinger(webfinger_profile)
end
if fingered_person
Rails.logger.info("event=webfinger status=success route=remote target=#{@account}")
fingered_person
else
Rails.logger.info("event=webfinger status=failure route=remote target=#{@account}")
raise WebfingerFailedError.new(@account)
end
def get(url)
Rails.logger.info("Getting: #{url} for #{account}")
begin
Faraday.get(url).body
rescue Exception => e
Rails.logger.info("event=receive status=abort recipient=#{@account} reason='#{e.message}'")
nil
Rails.logger.info("Failed to fetch: #{url} for #{account}; #{e.message}")
raise e
end
end
private
def get_xrd
begin
http = Faraday.get xrd_url
def existing_person_with_profile?
cached_person.present? && cached_person.profile.present?
end
profile_url = webfinger_profile_url(http.body)
if profile_url
return profile_url
else
raise "no profile URL"
end
def cached_person
self.person ||= Person.by_account_identifier(account)
end
def create_or_update_person_from_webfinger_profile!
if person #update my profile please
person.assign_new_profile_from_hcard(self.hcard)
else
person = make_person_from_webfinger
end
Rails.logger.info("event=webfinger status=success route=remote target=#{@account}")
person
end
#this tries the xrl url with https first, then falls back to http
def host_meta_xrd
begin
get(host_meta_url)
rescue Exception => e
if @ssl
@ssl = false
if self.ssl
self.ssl = false
retry
else
raise e
raise I18n.t('webfinger.xrd_fetch_failed', :account => @account)
raise I18n.t('webfinger.xrd_fetch_failed', :account => account)
end
end
end
def get_webfinger_profile(profile_url)
begin
http = Faraday.get(profile_url)
def hcard
@hcard ||= HCard.build(hcard_xrd)
end
rescue
raise I18n.t('webfinger.fetch_failed', :profile_url => profile_url)
end
return http.body
def webfinger_profile
@webfinger_profile ||= WebfingerProfile.new(account, webfinger_profile_xrd)
end
def hcard_url
@wf_profile.hcard
self.webfinger_profile.hcard
end
def get_hcard(webfinger_profile)
unless webfinger_profile.strip == ""
@wf_profile = WebfingerProfile.new(@account, webfinger_profile)
begin
hcard = Faraday.get(hcard_url)
rescue
return I18n.t('webfinger.hcard_fetch_failed', :account => @account)
end
HCard.build hcard.body
else
nil
end
end
def make_person_from_webfinger(webfinger_profile)
card = get_hcard(webfinger_profile)
if card && @wf_profile
Person.create_from_webfinger(@wf_profile, card)
end
end
##helpers
private
def webfinger_profile_url(xrd_response)
doc = Nokogiri::XML::Document.parse(xrd_response)
def webfinger_profile_url
doc = Nokogiri::XML::Document.parse(self.host_meta_xrd)
return nil if doc.namespaces["xmlns"] != "http://docs.oasis-open.org/ns/xri/xrd-1.0"
swizzle doc.at('Link[rel=lrdd]').attribute('template').value
end
def xrd_url
domain = @account.split('@')[1]
"http#{'s' if @ssl}://#{domain}/.well-known/host-meta"
def webfinger_profile_xrd
@webfinger_profile_xrd ||= get(webfinger_profile_url)
end
def hcard_xrd
@hcard_xrd ||= get(hcard_url)
end
def make_person_from_webfinger
Person.create_from_webfinger(webfinger_profile, hcard)
end
def host_meta_url
domain = account.split('@')[1]
"http#{'s' if self.ssl}://#{domain}/.well-known/host-meta"
end
def swizzle(template)
template.gsub '{uri}', @account
template.gsub('{uri}', account)
end
end

View file

@ -1,3 +1,7 @@
app.collections.Comments = Backbone.Collection.extend({
model: app.models.Comment
model: app.models.Comment,
initialize : function(models, options) {
this.url = "/posts/" + options.post.id + "/comments" //not delegating to post.url() because when it is in a stream collection it delegates to that url
}
});

View file

@ -1,3 +1,7 @@
app.collections.Likes = Backbone.Collection.extend({
model: app.models.Like
model: app.models.Like,
initialize : function(models, options) {
this.url = "/posts/" + options.post.id + "/likes" //not delegating to post.url() because when it is in a stream collection it delegates to that url
}
});

View file

@ -0,0 +1,16 @@
app.collections.Posts = Backbone.Collection.extend({
url : "/posts",
model: function(attrs, options) {
var modelClass = app.models[attrs.post_type] || app.models.Post
return new modelClass(attrs, options);
},
parse: function(resp){
return resp.posts;
},
comparator : function(post) {
return -post.createdAt();
}
});

View file

@ -1,21 +0,0 @@
app.collections.Stream = Backbone.Collection.extend({
url: function() {
var path = document.location.pathname;
if(this.models.length) {
path += "?max_time=" + _.last(this.models).createdAt();
}
return path;
},
model: app.models.Post,
parse: function(resp){
return resp.posts;
},
comparator : function(post) {
return -post.createdAt();
}
});

View file

@ -1,2 +1 @@
app.models.Like = Backbone.Model.extend({
})
app.models.Like = Backbone.Model.extend({ })

View file

@ -1,28 +1,22 @@
app.models.Post = Backbone.Model.extend({
urlRoot : "/posts",
initialize : function() {
this.comments = new app.collections.Comments(this.get("last_three_comments"));
this.comments.url = this.url() + '/comments';
this.likes = new app.collections.Likes(this.get("user_like")); // load in the user like initially
this.likes.url = this.url() + '/likes';
this.comments = new app.collections.Comments(this.get("last_three_comments"), {post : this});
this.likes = new app.collections.Likes(this.get("user_like"), { post : this}); // load in the user like initially
},
url : function() {
if(this.id) {
return "/posts/" + this.id;
} else {
return "/posts"
}
createdAt : function() {
return new Date(this.get("created_at")) / 1000;
},
createReshareUrl : "/reshares",
reshare : function(){
var reshare = new app.models.Reshare();
reshare.save({root_guid : this.baseGuid()}, {
success : function(){
app.stream.collection.add(reshare.toJSON());
}
});
return reshare;
return this._reshare = this._reshare || new app.models.Reshare({root_guid : this.get("guid")});
},
reshareAuthor : function(){
return this.get("author")
},
toggleLike : function() {
@ -34,24 +28,8 @@ app.models.Post = Backbone.Model.extend({
}
},
createdAt : function() {
return +new Date(this.get("created_at")) / 1000;
},
baseGuid : function() {
if(this.get("root")){
return this.get("root").guid;
} else {
return this.get("guid");
}
},
baseAuthor : function() {
if(this.get("root")){
return this.get("root").author;
} else {
return this.get("author");
}
like : function() {
this.set({ user_like : this.likes.create() });
},
unlike : function() {
@ -60,9 +38,5 @@ app.models.Post = Backbone.Model.extend({
likeModel.destroy();
this.set({ user_like : null });
},
like : function() {
this.set({ user_like : this.likes.create() });
}
});

View file

@ -1,8 +1,14 @@
app.models.Reshare = app.models.Post.extend({
url : function() { return "/reshares"; },
rootPost : function(){
this._rootPost = this._rootPost || new app.models.Post(this.get("root"))
return this._rootPost
return this._rootPost
},
reshare : function(){
this.rootPost().reshare()
},
reshareAuthor : function(){
return this.rootPost().reshareAuthor()
}
});

View file

@ -1,3 +1 @@
app.models.StatusMessage = app.models.Post.extend({
url : function() { return "/status_messages"; }
});
app.models.StatusMessage = app.models.Post.extend({ });

View file

@ -0,0 +1,36 @@
app.models.Stream = Backbone.Collection.extend({
initialize : function(){
this.posts = new app.collections.Posts();
},
url : function(){
return _.any(this.posts.models) ? this.timeFilteredPath() : this.basePath()
},
fetch: function() {
var self = this
this.posts
.fetch({
add : true,
url : self.url()
})
.done(
function(){
self.trigger("fetched", self);
}
)
},
basePath : function(){
return document.location.pathname;
},
timeFilteredPath : function(){
return this.basePath() + "?max_time=" + _.last(this.posts.models).createdAt();
},
add : function(models){
this.posts.add(models)
}
})

View file

@ -2,21 +2,22 @@ app.Router = Backbone.Router.extend({
routes: {
"stream": "stream",
"aspects:query": "stream",
"comment_stream": "stream",
"like_stream": "stream",
"commented": "stream",
"liked": "stream",
"mentions": "stream",
"people/:id": "stream",
"u/:name": "stream",
"tag_followings": "stream",
"followed_tags": "stream",
"tags/:name": "stream",
"posts/:id": "stream"
},
stream : function() {
app.stream = new app.views.Stream().render();
$("#main_stream").html(app.stream.el);
app.stream = new app.models.Stream()
app.page = new app.views.Stream().render();
$("#main_stream").html(app.page.el);
var streamFacesView = new app.views.StreamFaces({collection : app.stream.collection}).render();
var streamFacesView = new app.views.StreamFaces({collection : app.stream.posts}).render();
$('#selected_aspect_contacts .content').html(streamFacesView.el);
}
});

View file

@ -13,14 +13,15 @@ app.views.Feedback = app.views.StreamObject.extend({
this.model.toggleLike();
},
initialize: function(options){
this.setupRenderEvents();
this.reshareablePost = options.model;
},
resharePost : function(evt){
if(evt) { evt.preventDefault(); }
if(!window.confirm("Reshare " + this.reshareablePost.baseAuthor().name + "'s post?")) { return }
this.reshareablePost.reshare();
if(!window.confirm("Reshare " + this.model.reshareAuthor().name + "'s post?")) { return }
var reshare = this.model.reshare()
reshare.save({}, {
url: this.model.createReshareUrl,
success : function(){
app.stream.add(reshare);
}
});
}
})

View file

@ -35,12 +35,7 @@ app.views.Post = app.views.StreamObject.extend({
feedbackView : function(){
if(!window.app.user().current_user ) { return null }
var feedbackViewClass = this.resharedContent() ? app.views.ReshareFeedback : app.views.Feedback
return new feedbackViewClass({model : this.model});
},
resharedContent : function(){
return this.model.get('root')
return new app.views.Feedback({model : this.model});
},
postContentView: function(){
@ -68,9 +63,9 @@ app.views.Post = app.views.StreamObject.extend({
success : function(){
if(!app.stream) { return }
_.each(app.stream.collection.models, function(model){
_.each(app.stream.posts.models, function(model){
if(model.get("author").id == personId) {
app.stream.collection.remove(model);
app.stream.posts.remove(model);
}
})
}

View file

@ -12,7 +12,7 @@ app.views.Publisher = Backbone.View.extend({
},
initialize : function(){
this.collection = this.collection || new app.collections.Stream;
this.collection = this.collection || new app.collections.Posts;
return this;
},
@ -31,8 +31,9 @@ app.views.Publisher = Backbone.View.extend({
"photos" : serializedForm["photos[]"],
"services" : serializedForm["services[]"]
}, {
url : "/status_messages",
success : function() {
app.stream.collection.add(statusMessage.toJSON());
app.stream.posts.add(statusMessage.toJSON());
}
});

View file

@ -1,6 +0,0 @@
app.views.ReshareFeedback = app.views.Feedback.extend({
initialize : function(){
this.reshareablePost = (this.model instanceof app.models.Reshare) ? this.model.rootPost() : new app.models.Reshare(this.model.attributes).rootPost();
this.setupRenderEvents();
}
});

View file

@ -8,7 +8,7 @@ app.views.StreamFaces = app.views.Base.extend({
initialize : function(){
this.updatePeople()
this.collection.bind("add", this.updatePeople, this)
app.stream.posts.bind("add", this.updatePeople, this)
},
presenter : function() {

View file

@ -8,6 +8,7 @@ app.views.StreamObject = app.views.Base.extend({
this.$(".collapsible").expander({
slicePoint: 400,
widow: 12,
expandPrefix: "",
expandText: Diaspora.I18n.t("show_more"),
userCollapse: false
});

View file

@ -3,25 +3,86 @@ app.views.Stream = Backbone.View.extend({
"click #paginate": "render"
},
initialize: function() {
this.collection = this.collection || new app.collections.Stream;
this.collection.bind("add", this.addPost, this);
initialize: function(options) {
this.stream = app.stream || new app.models.Stream()
this.collection = this.stream.posts
this.publisher = new app.views.Publisher({collection : this.collection});
// inf scroll
// we're using this._loading to keep track of backbone's collection
// fetching state... is there a better way to do this?
var throttledScroll = _.throttle($.proxy(this.infScroll, this), 200);
$(window).scroll(throttledScroll);
this.stream.bind("fetched", this.collectionFetched, this)
this.collection.bind("add", this.addPost, this);
this.setupInfiniteScroll()
this.setupLightbox()
},
// lightbox delegation
this.lightbox = Diaspora.BaseWidget.instantiate("Lightbox");
$(this.el).delegate("a.stream-photo-link", "click", this.lightbox.lightboxImageClicked);
addPost : function(post) {
var postView = new app.views.Post({ model: post });
$(this.el)[
(this.collection.at(0).id == post.id)
? "prepend"
: "append"
](postView.render().el);
return this;
},
isLoading : function(){
return this._loading && !this._loading.isResolved();
},
allContentLoaded : false,
collectionFetched: function(collection, response) {
this.removeLoader()
if(!collection.parse(response).length || collection.parse(response).length == 0) {
this.allContentLoaded = true;
$(window).unbind('scroll')
return
}
$(this.el).append($("<a>", {
href: this.stream.url(),
id: "paginate"
}).text('Load more posts'));
},
render : function(evt) {
if(evt) { evt.preventDefault(); }
this.addLoader();
this._loading = this.stream.fetch();
return this;
},
addLoader: function(){
if(this.$("#paginate").length == 0) {
$(this.el).append($("<div>", {
"id" : "paginate"
}));
}
this.$("#paginate").html($("<img>", {
src : "/images/static-loader.png",
"class" : "loader"
}));
},
removeLoader : function(){
this.$("#paginate").remove();
},
setupLightbox : function(){
this.lightbox = Diaspora.BaseWidget.instantiate("Lightbox");
$(this.el).delegate("a.stream-photo-link", "click", this.lightbox.lightboxImageClicked);
},
setupInfiniteScroll : function() {
var throttledScroll = _.throttle($.proxy(this.infScroll, this), 200);
$(window).scroll(throttledScroll);
},
infScroll : function() {
if(this.allContentLoaded || this.isLoading()) { return }
@ -36,64 +97,4 @@ app.views.Stream = Backbone.View.extend({
return this;
},
isLoading : function(){
return this._loading && !this._loading.isResolved();
},
allContentLoaded : false,
addPost : function(post) {
var postView = new app.views.Post({ model: post });
$(this.el)[
(this.collection.at(0).id == post.id)
? "prepend"
: "append"
](postView.render().el);
return this;
},
collectionFetched: function(collection, response) {
this.$("#paginate").remove();
if(!collection.parse(response).length || collection.parse(response).length == 0) {
this.allContentLoaded = true;
$(window).unbind('scroll')
return
}
$(this.el).append($("<a>", {
href: this.collection.url(),
id: "paginate"
}).text('Load more posts'));
},
render : function(evt) {
if(evt) { evt.preventDefault(); }
var self = this;
self.addLoader();
this._loading = self.collection.fetch({
add: true,
success: $.proxy(this.collectionFetched, self)
});
return this;
},
addLoader: function(){
if(this.$("#paginate").length == 0) {
$(this.el).append($("<div>", {
"id" : "paginate"
}));
}
this.$("#paginate").html($("<img>", {
src : "/images/static-loader.png",
"class" : "loader"
}));
}
});

View file

@ -14,7 +14,7 @@ describe AdminsController do
context 'admin not signed in' do
it 'is behind redirect_unless_admin' do
get :user_search
response.should redirect_to multi_path
response.should redirect_to multi_stream_path
end
end
@ -64,7 +64,7 @@ describe AdminsController do
context 'admin not signed in' do
it 'is behind redirect_unless_admin' do
get :admin_inviter
response.should redirect_to multi_path
response.should redirect_to multi_stream_path
end
end

View file

@ -1,20 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require 'spec_helper'
describe Api::V0::TagsController do
describe '#show' do
it 'succeeds' do
get :show, :name => 'alice'
response.should be_success
end
it "returns the basic tag data" do
get :show, :name => 'alice'
parsed_json = JSON.parse(response.body)
parsed_json.keys.should =~ %w(name person_count followed_count posts)
end
end
end

View file

@ -1,23 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require 'spec_helper'
describe Api::V0::UsersController do
describe '#show' do
it 'succeeds' do
get :show, :username => 'alice'
response.should be_success
end
it "404s if there's no such user" do
get :show, :username => "*****"
response.should be_not_found
end
it "returns the public profile data" do
get :show, :username => 'alice'
parsed_json = JSON.parse(response.body)
parsed_json.keys.should =~ %w( diaspora_id first_name last_name image_url searchable )
end
end
end

View file

@ -35,52 +35,6 @@ describe AspectsController do
end
end
describe "#index" do
it 'assigns an Stream::Aspect' do
get :index
assigns(:stream).class.should == Stream::Aspect
end
describe 'filtering by aspect' do
before do
@aspect1 = alice.aspects.create(:name => "test aspect")
@stream = Stream::Aspect.new(alice, [])
@stream.stub(:posts).and_return([])
end
it 'respects a single aspect' do
Stream::Aspect.should_receive(:new).with(alice, [@aspect1.id], anything).and_return(@stream)
get :index, :a_ids => [@aspect1.id]
end
it 'respects multiple aspects' do
aspect2 = alice.aspects.create(:name => "test aspect two")
Stream::Aspect.should_receive(:new).with(alice, [@aspect1.id, aspect2.id], anything).and_return(@stream)
get :index, :a_ids => [@aspect1.id, aspect2.id]
end
end
describe 'performance', :performance => true do
before do
require 'benchmark'
8.times do |n|
user = Factory.create(:user)
aspect = user.aspects.create(:name => 'people')
connect_users(alice, @alices_aspect_1, user, aspect)
post = alice.post(:status_message, :text => "hello#{n}", :to => @alices_aspect_2.id)
8.times do |n|
user.comment "yo#{post.text}", :post => post
end
end
end
it 'takes time' do
Benchmark.realtime {
get :index
}.should < 1.5
end
end
end
describe "#show" do
it "succeeds" do
get :show, 'id' => @alices_aspect_1.id.to_s
@ -124,6 +78,7 @@ describe AspectsController do
end
end
end
context "with invalid params" do
it "does not create an aspect" do
alice.aspects.count.should == 2
@ -232,25 +187,4 @@ describe AspectsController do
end
end
end
describe "mobile site" do
before do
ap = alice.person
posts = []
posts << alice.post(:reshare, :root_guid => Factory(:status_message, :public => true).guid, :to => 'all')
posts << alice.post(:status_message, :text => 'foo', :to => alice.aspects)
photo = Factory(:activity_streams_photo, :public => true, :author => ap)
posts << photo
posts.each do |p|
alice.build_like(:positive => true, :target => p).save
end
alice.add_to_streams(photo, alice.aspects)
sign_in alice
end
it 'should not 500' do
get :index, :format => :mobile
response.should be_success
end
end
end

View file

@ -14,7 +14,7 @@ describe HomeController do
it 'redirects to multis index if user is logged in' do
sign_in alice
get :show, :home => true
response.should redirect_to(multi_path)
response.should redirect_to(multi_stream_path)
end
end
end

View file

@ -4,8 +4,8 @@
require 'spec_helper'
describe AspectsController do
describe '#index' do
describe StreamsController do
describe '#aspects' do
before do
sign_in :user, alice
@alices_aspect_2 = alice.aspects.create(:name => "another aspect")
@ -19,19 +19,19 @@ describe AspectsController do
end
it "generates a jasmine fixture", :fixture => true do
get :index
get :aspects
save_fixture(html_for("body"), "aspects_index")
end
it "generates a jasmine fixture with a prefill", :fixture => true do
get :index, :prefill => "reshare things"
get :aspects, :prefill => "reshare things"
save_fixture(html_for("body"), "aspects_index_prefill")
end
it 'generates a jasmine fixture with services', :fixture => true do
alice.services << Services::Facebook.create(:user_id => alice.id)
alice.services << Services::Twitter.create(:user_id => alice.id)
get :index, :prefill => "reshare things"
get :aspects, :prefill => "reshare things"
save_fixture(html_for("body"), "aspects_index_services")
end
@ -39,14 +39,14 @@ describe AspectsController do
bob.post(:status_message, :text => "Is anyone out there?", :to => @bob.aspects.where(:name => "generic").first.id)
message = alice.post(:status_message, :text => "hello "*800, :to => @alices_aspect_2.id)
5.times { bob.comment("what", :post => message) }
get :index
get :aspects
save_fixture(html_for("body"), "aspects_index_with_posts")
end
it 'generates a jasmine fixture with only posts', :fixture => true do
2.times { bob.post(:status_message, :text => "Is anyone out there?", :to => @bob.aspects.where(:name => "generic").first.id) }
get :index, :only_posts => true
get :aspects, :only_posts => true
save_fixture(response.body, "aspects_index_only_posts")
end
@ -54,14 +54,14 @@ describe AspectsController do
it "generates a jasmine fixture with a post with comments", :fixture => true do
message = bob.post(:status_message, :text => "HALO WHIRLED", :to => @bob.aspects.where(:name => "generic").first.id)
5.times { bob.comment("what", :post => message) }
get :index
get :aspects
save_fixture(html_for("body"), "aspects_index_post_with_comments")
end
it 'generates a jasmine fixture with a followed tag', :fixture => true do
@tag = ActsAsTaggableOn::Tag.create!(:name => "partytimeexcellent")
TagFollowing.create!(:tag => @tag, :user => alice)
get :index
get :aspects
save_fixture(html_for("body"), "aspects_index_with_one_followed_tag")
end
@ -89,7 +89,7 @@ describe AspectsController do
)
alice.post(:status_message, :text => "http://www.youtube.com/watch?v=UYrkQL1bX4A", :to => @alices_aspect_2.id)
get :index
get :aspects
save_fixture(html_for("body"), "aspects_index_with_video_post")
end
@ -98,7 +98,7 @@ describe AspectsController do
alice.build_like(:positive => true, :target => message).save
bob.build_like(:positive => true, :target => message).save
get :index
get :aspects
save_fixture(html_for("body"), "aspects_index_with_a_post_with_likes")
end
end

View file

@ -4,8 +4,8 @@
require 'spec_helper'
describe MultisController do
describe '#index' do
describe StreamsController do
describe '#multi' do
before do
sign_in :user, alice
end
@ -27,7 +27,7 @@ describe MultisController do
end
end
get :index, :format => :json
get :multi, :format => :json
response.should be_success
save_fixture(response.body, "multi_stream_json")

View file

@ -1,23 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require 'spec_helper'
describe LikeStreamController do
before do
sign_in :user, alice
end
describe 'index' do
it 'succeeds' do
get :index
response.should be_success
end
it 'assigns a stream' do
get :index
assigns[:stream].should be_a Stream::Likes
end
end
end

View file

@ -1,35 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require 'spec_helper'
describe MultisController do
describe '#index' do
before do
@old_spotlight_value = AppConfig[:community_spotlight]
sign_in :user, alice
end
after do
AppConfig[:community_spotlight] = @old_spotlight_value
end
it 'succeeds' do
AppConfig[:community_spotlight] = [bob.person.diaspora_handle]
get :index
response.should be_success
end
it 'succeeds without AppConfig[:community_spotlight]' do
AppConfig[:community_spotlight] = nil
get :index
response.should be_success
end
it 'succeeds on mobile' do
get :index, :format => :mobile
response.should be_success
end
end
end

View file

@ -137,23 +137,4 @@ describe PostsController do
StatusMessage.exists?(message.id).should be_true
end
end
describe '#index' do
before do
sign_in alice
end
it 'will succeed if admin' do
AppConfig[:admins] = [alice.username]
get :index
response.should be_success
end
it 'will redirect if not' do
AppConfig[:admins] = []
get :index
response.should be_redirect
end
end
end

View file

@ -67,7 +67,7 @@ describe RegistrationsController do
it "redirects to the home path" do
get :create, @valid_params
response.should be_redirect
response.location.should match /^#{multi_url}\??$/
response.location.should match /^#{multi_stream_url}\??$/
end
end

View file

@ -27,14 +27,14 @@ describe SessionsController do
it "redirects to /stream for a non-mobile user" do
post :create, {"user" => {"remember_me" => "0", "username" => @user.username, "password" => "evankorth"}}
response.should be_redirect
response.location.should match /^#{multi_url}\??$/
response.location.should match /^#{multi_stream_url}\??$/
end
it "redirects to /stream for a mobile user" do
@request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_1 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8B117 Safari/6531.22.7'
post :create, {"user" => {"remember_me" => "0", "username" => @user.username, "password" => "evankorth"}}
response.should be_redirect
response.location.should match /^#{multi_url}\??$/
response.location.should match /^#{multi_stream_url}\??$/
end
it 'queues up an update job' do

View file

@ -7,7 +7,6 @@ require 'spec_helper'
describe ShareVisibilitiesController do
before do
@status = alice.post(:status_message, :text => "hello", :to => alice.aspects.first)
@vis = @status.share_visibilities.first
sign_in :user, bob
end
@ -23,72 +22,23 @@ describe ShareVisibilitiesController do
end
it 'calls #update_cache' do
@controller.should_receive(:update_cache).with(an_instance_of(ShareVisibility))
RedisCache.should_receive(:update_cache_for).with(an_instance_of(User), an_instance_of(Post), true)
put :update, :format => :js, :id => 42, :post_id => @status.id
end
it 'marks hidden if visible' do
it 'it calls toggle_hidden_shareable' do
@controller.current_user.should_receive(:toggle_hidden_shareable).with(an_instance_of(Post))
put :update, :format => :js, :id => 42, :post_id => @status.id
@vis.reload.hidden.should be_true
end
it 'marks visible if hidden' do
@vis.update_attributes(:hidden => true)
put :update, :format => :js, :id => 42, :post_id => @status.id
@vis.reload.hidden.should be_false
end
end
context "post you do not see" do
before do
sign_in :user, eve
end
it 'does not let a user destroy a visibility that is not theirs' do
lambda {
put :update, :format => :js, :id => 42, :post_id => @status.id
}.should_not change(@vis.reload, :hidden).to(true)
end
it 'does not succeed' do
put :update, :format => :js, :id => 42, :post_id => @status.id
response.should_not be_success
end
end
end
describe '#update_cache' do
before do
@controller.params[:post_id] = @status.id
@cache = RedisCache.new(bob, 'created_at')
RedisCache.stub(:new).and_return(@cache)
RedisCache.stub(:configured?).and_return(true)
end
it 'does nothing if cache is not configured' do
RedisCache.stub(:configured?).and_return(false)
RedisCache.should_not_receive(:new)
@controller.send(:update_cache, @vis)
end
it 'removes the post from the cache if visibility is marked as hidden' do
@vis.hidden = true
@cache.should_receive(:remove).with(@vis.shareable_id)
@controller.send(:update_cache, @vis)
end
it 'adds the post from the cache if visibility is marked as hidden' do
@vis.hidden = false
@cache.should_receive(:add).with(@status.created_at.to_i, @vis.shareable_id)
@controller.send(:update_cache, @vis)
end
end
describe "#accessible_post" do
it "memoizes a query for a post given a post_id param" do
id = 1
@controller.params[:post_id] = id
@controller.params[:shareable_type] = 'Post'
Post.should_receive(:where).with(hash_including(:id => id)).once.and_return(stub.as_null_object)
2.times do |n|
@controller.send(:accessible_post)

View file

@ -0,0 +1,72 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require 'spec_helper'
describe StreamsController do
before do
sign_in alice
end
describe "#public" do
it 'will succeed if admin' do
AppConfig[:admins] = [alice.username]
get :public
response.should be_success
end
it 'will redirect if not' do
AppConfig[:admins] = []
get :public
response.should be_redirect
end
end
describe '#multi' do
before do
@old_spotlight_value = AppConfig[:community_spotlight]
end
after do
AppConfig[:community_spotlight] = @old_spotlight_value
end
it 'succeeds' do
AppConfig[:community_spotlight] = [bob.person.diaspora_handle]
get :multi
response.should be_success
end
it 'succeeds without AppConfig[:community_spotlight]' do
AppConfig[:community_spotlight] = nil
get :multi
response.should be_success
end
it 'succeeds on mobile' do
get :multi, :format => :mobile
response.should be_success
end
end
streams = [
{:path => :liked, :type => Stream::Likes},
{:path => :mentioned, :type => Stream::Mention},
{:path => :followed_tags, :type => Stream::FollowedTag}
]
streams.each do |s|
describe "##{s[:path]}" do
it 'succeeds' do
get s[:path]
response.should be_success
end
it 'assigns a stream' do
get s[:path]
assigns[:stream].should be_a s[:type]
end
end
end
end

View file

@ -1,195 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require 'spec_helper'
describe TagFollowingsController do
def valid_attributes
{:name => "partytimeexcellent"}
end
before do
@tag = ActsAsTaggableOn::Tag.create!(:name => "partytimeexcellent")
sign_in :user, bob
bob.followed_tags.create(:name => "testing")
end
describe 'index' do
it 'succeeds' do
get :index
response.should be_success
end
it 'assigns a stream' do
get :index
assigns[:stream].should be_a Stream::FollowedTag
end
describe 'if empty' do
it 'succeeds' do
bob.followed_tags.delete_all
get :index
response.should be_success
end
it 'assigns a stream' do
bob.followed_tags.delete_all
get :index
assigns[:stream].should be_a Stream::FollowedTag
end
end
end
describe "create" do
describe "successfully" do
it "creates a new TagFollowing" do
expect {
post :create, valid_attributes
response.should be_redirect
}.to change(TagFollowing, :count).by(1)
end
it "associates the tag following with the currently-signed-in user" do
expect {
post :create, valid_attributes
response.should be_redirect
}.to change(bob.tag_followings, :count).by(1)
end
it "assigns a newly created tag_following as @tag_following" do
post :create, valid_attributes
response.should be_redirect
assigns(:tag_following).should be_a(TagFollowing)
assigns(:tag_following).should be_persisted
end
it "creates the tag IFF it doesn't already exist" do
ActsAsTaggableOn::Tag.find_by_name('tomcruisecontrol').should be_nil
expect {
post :create, :name => "tomcruisecontrol"
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
end
it "flashes success to the tag page" do
post :create, valid_attributes
flash[:notice].should include(valid_attributes[:name])
end
it "flashes error if you already have a tag" do
TagFollowing.any_instance.stub(:save).and_return(false)
post :create, valid_attributes
flash[:error].should include(valid_attributes[:name])
end
it 'squashes the tag' do
ActsAsTaggableOn::Tag.find_by_name('somestuff').should be_nil
post :create, :name => "some stuff"
assigns[:tag].name.should == "somestuff"
ActsAsTaggableOn::Tag.find_by_name('somestuff').should_not be_nil
end
it 'downcases the tag name' do
ActsAsTaggableOn::Tag.find_by_name('somestuff').should be_nil
post :create, :name => "SOMESTUFF"
response.should be_redirect
assigns[:tag].name.should == "somestuff"
ActsAsTaggableOn::Tag.find_by_name('somestuff').should_not be_nil
end
it "normalizes the tag name" do
ActsAsTaggableOn::Tag.find_by_name('foobar').should be_nil
post :create, :name => "foo:bar"
assigns[:tag].name.should == "foobar"
ActsAsTaggableOn::Tag.find_by_name('foobar').should_not be_nil
end
end
describe 'fails to' do
it "create the tag if it already exists" do
ActsAsTaggableOn::Tag.find_by_name('tomcruisecontrol').should be_nil
expect {
post :create, :name => "tomcruisecontrol"
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
ActsAsTaggableOn::Tag.find_by_name('tomcruisecontrol').should_not be_nil
expect {
post :create, :name => "tomcruisecontrol"
}.to change(ActsAsTaggableOn::Tag, :count).by(0)
expect {
post :create, :name => "tom cruise control"
}.to change(ActsAsTaggableOn::Tag, :count).by(0)
expect {
post :create, :name => "TomCruiseControl"
}.to change(ActsAsTaggableOn::Tag, :count).by(0)
expect {
post :create, :name => "tom:cruise:control"
}.to change(ActsAsTaggableOn::Tag, :count).by(0)
end
it "create a tag following for a user other than the currently signed in user" do
expect {
expect {
post :create, valid_attributes.merge(:user_id => alice.id)
}.not_to change(alice.tag_followings, :count).by(1)
}.to change(bob.tag_followings, :count).by(1)
end
end
end
describe "DELETE destroy" do
before do
TagFollowing.create!(:tag => @tag, :user => bob )
TagFollowing.create!(:tag => @tag, :user => alice )
end
it "destroys the requested tag_following" do
expect {
delete :destroy, valid_attributes
}.to change(TagFollowing, :count).by(-1)
end
it "redirects and flashes error if you already don't follow the tag" do
delete :destroy, valid_attributes
response.should redirect_to(tag_path(:name => valid_attributes[:name]))
flash[:notice].should include(valid_attributes[:name])
end
it "redirects and flashes error if you already don't follow the tag" do
TagFollowing.any_instance.stub(:destroy).and_return(false)
delete :destroy, valid_attributes
response.should redirect_to(tag_path(:name => valid_attributes[:name]))
flash[:error].should include(valid_attributes[:name])
end
end
describe "#create_multiple" do
it "redirects" do
post :create_multiple, :tags => "#foo,#bar"
response.should be_redirect
end
it "handles no tags parameter" do
expect { post :create_multiple, :name => 'not tags' }.to_not raise_exception
end
it "adds multiple tags" do
expect { post :create_multiple, :tags => "#tags,#cats,#bats," }.to change{ bob.followed_tags.count }.by(3)
end
it "adds non-followed tags" do
TagFollowing.create!(:tag => @tag, :user => bob )
expect { post :create_multiple, :tags => "#partytimeexcellent,#a,#b," }.to change{ bob.followed_tags.count }.by(2)
end
it "normalizes the tag names" do
bob.followed_tags.delete_all
post :create_multiple, :tags => "#foo:bar,#bar#foo"
bob.followed_tags(true).map(&:name).should =~ ["foobar", "barfoo"]
end
end
end

View file

@ -66,7 +66,7 @@ Factory.define :user do |u|
user.person = Factory.build(:person, :profile => Factory.build(:profile),
:owner_id => user.id,
:serialized_public_key => user.encryption_key.public_key.export,
:diaspora_handle => "#{user.username}@#{AppConfig[:pod_url].gsub(/(https?:|www\.)\/\//, '').chop!}")
:diaspora_handle => "#{user.username}#{User.diaspora_id_host}")
end
u.after_create do |user|
user.person.save
@ -100,6 +100,7 @@ end
Factory.define(:photo) do |p|
p.sequence(:random_string) {|n| ActiveSupport::SecureRandom.hex(10) }
p.association :author, :factory => :person
p.after_build do |p|
p.unprocessed_image.store! File.open(File.join(File.dirname(__FILE__), 'fixtures', 'button.png'))
p.update_remote_path

View file

@ -0,0 +1,10 @@
describe("app.collections.comments", function(){
describe("url", function(){
it("should user the post id", function(){
var post =new app.models.Post({id : 5})
var collection = new app.collections.Comments([], {post : post})
expect(collection.url).toBe("/posts/5/comments")
})
})
})

View file

@ -0,0 +1,10 @@
describe("app.collections.Likes", function(){
describe("url", function(){
it("should user the post id", function(){
var post =new app.models.Post({id : 5})
var collection = new app.collections.Likes([], {post : post})
expect(collection.url).toBe("/posts/5/likes")
})
})
})

View file

@ -1,19 +0,0 @@
describe("app.collections.Stream", function() {
describe("url", function() {
var stream = new app.collections.Stream(),
expectedPath = document.location.pathname;
it("returns the correct path", function() {
expect(stream.url()).toEqual(expectedPath);
});
it("returns the json path with max_time if the collection has models", function() {
var post = new app.models.Post();
spyOn(post, "createdAt").andReturn(1234);
stream.add(post);
expect(stream.url()).toEqual(expectedPath + "?max_time=1234");
});
});
});

View file

@ -3,6 +3,15 @@ describe("app.models.Post", function() {
this.post = new app.models.Post();
})
describe("url", function(){
it("should be /posts when it doesn't have an id", function(){
expect(new app.models.Post().url()).toBe("/posts")
})
it("should be /posts/id when it doesn't have an id", function(){
expect(new app.models.Post({id: 5}).url()).toBe("/posts/5")
})
})
describe("createdAt", function() {
it("returns the post's created_at as an integer", function() {
var date = new Date;
@ -13,22 +22,6 @@ describe("app.models.Post", function() {
});
});
describe("baseGuid", function(){
it("returns the post's guid if the post does not have a root", function() {
this.post.attributes.root = null;
this.post.attributes.guid = "abcd";
expect(this.post.baseGuid()).toBe("abcd")
})
it("returns the post's root guid if the post has a root", function() {
this.post.attributes.root = {guid : "1234"}
this.post.attributes.guid = "abcd";
expect(this.post.baseGuid()).toBe("1234")
})
})
describe("toggleLike", function(){
it("calls unliked when the user_like exists", function(){
this.post.set({user_like : "123"});
@ -67,20 +60,4 @@ describe("app.models.Post", function() {
expect(app.models.Like.prototype.destroy).toHaveBeenCalled();
})
})
describe("baseAuthor", function(){
it("returns the post's guid if the post does not have a root", function() {
this.post.attributes.root = null;
this.post.attributes.author = "abcd";
expect(this.post.baseAuthor()).toBe("abcd")
})
it("returns the post's root guid if the post has a root", function() {
this.post.attributes.root = {author : "1234"}
this.post.attributes.author = "abcd";
expect(this.post.baseAuthor()).toBe("1234")
})
})
});

View file

@ -1,9 +1,9 @@
describe("app.models.Reshare", function(){
describe("rootPost", function(){
beforeEach(function(){
this.reshare = new app.models.Reshare({root: {a:"namaste", be : "aloha", see : "community"}})
});
beforeEach(function(){
this.reshare = new app.models.Reshare({root: {a:"namaste", be : "aloha", see : "community"}})
});
describe("rootPost", function(){
it("should be the root attrs", function(){
expect(this.reshare.rootPost().get("be")).toBe("aloha")
});
@ -16,5 +16,13 @@ describe("app.models.Reshare", function(){
expect(this.reshare.rootPost()).toBe(this.reshare.rootPost())
});
});
describe(".reshare", function(){
it("reshares the root post", function(){
spyOn(this.reshare.rootPost(), "reshare")
this.reshare.reshare()
expect(this.reshare.rootPost().reshare).toHaveBeenCalled()
})
})
});

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