From b5eff93a9d9bfc05173c8dee6a08ada05cc40e62 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 21 May 2013 16:39:51 +1000 Subject: [PATCH] update message bus to support per client filtering start work on user_tracking_state fix can_ban? in guardian expose protected scopes on topic_query we need move guardian spec to use build as opposed to creating topics / posts / users start work on user tracking spec --- Gemfile.lock | 2 +- app/models/user_tracking_state.rb | 41 ++++++++++++++++++ lib/guardian.rb | 6 +-- lib/topic_query.rb | 25 +++++------ spec/components/guardian_spec.rb | 56 ++++++++++++++++++++----- spec/models/user_tracking_state_spec.rb | 34 +++++++++++++++ 6 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 app/models/user_tracking_state.rb create mode 100644 spec/models/user_tracking_state_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index aee31ee6c..9dad0ac08 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,7 +8,7 @@ GIT GIT remote: https://github.com/SamSaffron/message_bus - revision: e5654728b1c1b97fa5c7bb79fd689b31cec3f01d + revision: 031a107bbe6e468caa67ff540485d70230d1c362 specs: message_bus (0.0.2) eventmachine diff --git a/app/models/user_tracking_state.rb b/app/models/user_tracking_state.rb new file mode 100644 index 000000000..110c6bc21 --- /dev/null +++ b/app/models/user_tracking_state.rb @@ -0,0 +1,41 @@ +# this class is used to mirror unread and new status back to end users +# in JavaScript there is a mirror class that is kept in-sync using the mssage bus +# the allows end users to always know which topics have unread posts in them +# and which topics are new + +class UserTrackingState + + CHANNEL = "/user-tracking" + + MessageBus.client_filter(CHANNEL) do |user_id, message| + if user_id + UserTrackingState.new(User.find(user_id)).filter(message) + else + nil + end + end + + def self.trigger_change(topic_id, post_number, user_id=nil) + MessageBus.publish(CHANNEL, "CHANGE", user_ids: [user_id].compact) + end + + def initialize(user) + @user = user + @query = TopicQuery.new(@user) + end + + def new_list + @query + .new_results(limit: false) + .select(topics: [:id, :created_at]) + .map{|t| [t.id, t.created_at]} + end + + def unread_list + [] + end + + def filter(message) + end + +end diff --git a/lib/guardian.rb b/lib/guardian.rb index 6e2ec95d6..fb7595ec3 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -109,11 +109,9 @@ class Guardian alias :can_activate? :can_approve? def can_ban?(user) - return false if user.blank? - return false unless @user.try(:admin?) - return false if user.admin? - true + is_staff? && user && !user.staff? end + alias :can_deactivate? :can_ban? def can_clear_flags?(post) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 4b733e1bd..17602c7d7 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -190,6 +190,19 @@ class TopicQuery create_list(:new_in_category) {|l| l.where(category_id: category.id).by_newest.first(25)} end + def new_results(list_opts={}) + default_list(list_opts) + .where("topics.created_at >= :created_at", created_at: @user.treat_as_new_topic_start_date) + .where("tu.last_read_post_number IS NULL") + .where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking]) + end + + def unread_results(list_opts={}) + default_list(list_opts) + .where("tu.last_read_post_number < topics.highest_post_number") + .where("COALESCE(tu.notification_level, :regular) >= :tracking", regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking]) + end + protected def create_list(filter, list_opts={}) @@ -240,18 +253,6 @@ class TopicQuery result end - def new_results(list_opts={}) - default_list(list_opts) - .where("topics.created_at >= :created_at", created_at: @user.treat_as_new_topic_start_date) - .where("tu.last_read_post_number IS NULL") - .where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking]) - end - - def unread_results(list_opts={}) - default_list(list_opts) - .where("tu.last_read_post_number < topics.highest_post_number") - .where("COALESCE(tu.notification_level, :regular) >= :tracking", regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking]) - end def random_suggested_results_for(topic, count, exclude_topic_ids) results = default_list(unordered: true, per_page: count) diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index 07aa73181..648c114fd 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -4,14 +4,14 @@ require_dependency 'post_destroyer' describe Guardian do - let(:user) { Fabricate(:user) } - let(:moderator) { Fabricate(:moderator) } - let(:admin) { Fabricate(:admin) } - let(:another_admin) { Fabricate(:another_admin) } - let(:coding_horror) { Fabricate(:coding_horror) } + let(:user) { build(:user) } + let(:moderator) { build(:moderator) } + let(:admin) { build(:admin) } + let(:another_admin) { build(:another_admin) } + let(:coding_horror) { build(:coding_horror) } - let(:topic) { Fabricate(:topic, user: user) } - let(:post) { Fabricate(:post, topic: topic, user: topic.user) } + let(:topic) { build(:topic, user: user) } + let(:post) { build(:post, topic: topic, user: topic.user) } it 'can be created without a user (not logged in)' do lambda { Guardian.new }.should_not raise_error @@ -22,8 +22,8 @@ describe Guardian do end describe 'post_can_act?' do - let(:post) { Fabricate(:post) } - let(:user) { Fabricate(:user) } + let(:post) { build(:post) } + let(:user) { build(:user) } it "returns false when the user is nil" do Guardian.new(nil).post_can_act?(post, :like).should be_false @@ -220,6 +220,9 @@ describe Guardian do describe 'a Post' do it 'correctly handles post visibility' do + post = Fabricate(:post) + topic = post.topic + Guardian.new(user).can_see?(post).should be_true post.trash! @@ -613,6 +616,7 @@ describe Guardian do end it "returns false when trying to delete your own post that has already been deleted" do + post = Fabricate(:post) PostDestroyer.new(user, post).destroy post.reload Guardian.new(user).can_delete?(post).should be_false @@ -642,7 +646,7 @@ describe Guardian do context 'a Category' do - let(:category) { Fabricate(:category, user: moderator) } + let(:category) { build(:category, user: moderator) } it 'returns false when not logged in' do Guardian.new.can_delete?(category).should be_false @@ -667,8 +671,33 @@ describe Guardian do end + context 'can_ban?' do + it 'returns false when a user tries to ban another user' do + Guardian.new(user).can_ban?(coding_horror).should be_false + end + + it 'returns true when an admin tries to ban another user' do + Guardian.new(admin).can_ban?(coding_horror).should be_true + end + + it 'returns true when a moderator tries to ban another user' do + Guardian.new(moderator).can_ban?(coding_horror).should be_true + end + + it 'returns false when staff tries to ban staff' do + Guardian.new(admin).can_ban?(moderator).should be_false + end + end + context 'a PostAction' do - let(:post_action) { PostAction.create(user_id: user.id, post_id: post.id, post_action_type_id: 1)} + let(:post_action) { + user.id = 1 + post.id = 1 + + a = PostAction.new(user_id: user.id, post_id: post.id, post_action_type_id: 1) + a.created_at = 1.minute.ago + a + } it 'returns false when not logged in' do Guardian.new.can_delete?(post_action).should be_false @@ -732,6 +761,8 @@ describe Guardian do end it "allows an admin to grant a regular user access" do + admin.id = 1 + user.id = 2 Guardian.new(admin).can_grant_admin?(user).should be_true end end @@ -750,6 +781,9 @@ describe Guardian do end it "allows an admin to revoke another admin's access" do + admin.id = 1 + another_admin.id = 2 + Guardian.new(admin).can_revoke_admin?(another_admin).should be_true end end diff --git a/spec/models/user_tracking_state_spec.rb b/spec/models/user_tracking_state_spec.rb new file mode 100644 index 000000000..b466a3cb7 --- /dev/null +++ b/spec/models/user_tracking_state_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe UserTrackingState do + + let(:user) do + Fabricate(:user) + end + + let(:post) do + Fabricate(:post) + end + + let(:state) do + UserTrackingState.new(user) + end + + it "correctly gets the list of new topics" do + state.new_list.should == [] + state.unread_list.should == [] + + new_post = post + + new_list = state.new_list + + new_list.length.should == 1 + new_list[0][0].should == post.topic.id + new_list[0][1].should be_within(1.second).of(post.topic.created_at) + + state.unread_list.should == [] + + # read it + + end +end