diff --git a/Changelog.md b/Changelog.md index 997795094..2c71a9e69 100644 --- a/Changelog.md +++ b/Changelog.md @@ -33,6 +33,7 @@ Although the chat was never enabled per default and was marked as experimental, * Add client-site rescaling of post images if they exceed the maximum possible size [#7734](https://github.com/diaspora/diaspora/pull/7734) * Add backend for archive import [#7660](https://github.com/diaspora/diaspora/pull/7660) * For pods running PostgreSQL, make sure that no upper-case/mixed-case tags exist, and create a `lower(name)` index on tags to speed up ActsAsTaggableOn [#8206](https://github.com/diaspora/diaspora/pull/8206) +* Allow podmins/moderators to see all local public posts to improve moderation [#8232](https://github.com/diaspora/diaspora/pull/8232) # 0.7.16.0 diff --git a/app/assets/javascripts/app/router.js b/app/assets/javascripts/app/router.js index 265b3cd48..f7bd8a440 100644 --- a/app/assets/javascripts/app/router.js +++ b/app/assets/javascripts/app/router.js @@ -24,6 +24,7 @@ app.Router = Backbone.Router.extend({ "posts/:id(/)": "singlePost", "profile/edit(/)": "settings", "public(/)": "stream", + "local_public(/)": "stream", "stream(/)": "stream", "tags/:name(/)": "followed_tags", "u/:name(/)": "profile", diff --git a/app/controllers/streams_controller.rb b/app/controllers/streams_controller.rb index 0d0947616..943295908 100644 --- a/app/controllers/streams_controller.rb +++ b/app/controllers/streams_controller.rb @@ -25,6 +25,14 @@ class StreamsController < ApplicationController stream_responder(Stream::Public) end + def local_public + if AppConfig.local_posts_stream?(current_user) + stream_responder(Stream::LocalPublic) + else + head :not_found + end + end + def activity stream_responder(Stream::Activity) end diff --git a/app/helpers/stream_helper.rb b/app/helpers/stream_helper.rb index cada31f8b..503580dd6 100644 --- a/app/helpers/stream_helper.rb +++ b/app/helpers/stream_helper.rb @@ -22,7 +22,7 @@ module StreamHelper end private - + # rubocop:disable Rails/HelperInstanceVariable def next_stream_path if current_page?(:stream) stream_path(max_time: time_for_scroll(@stream)) @@ -30,6 +30,8 @@ module StreamHelper activity_stream_path(max_time: time_for_scroll(@stream)) elsif current_page?(:aspects_stream) aspects_stream_path(max_time: time_for_scroll(@stream), a_ids: session[:a_ids]) + elsif current_page?(:local_public_stream) + local_public_stream_path(max_time: time_for_scroll(@stream)) elsif current_page?(:public_stream) public_stream_path(max_time: time_for_scroll(@stream)) elsif current_page?(:commented_stream) @@ -44,6 +46,7 @@ module StreamHelper raise "in order to use pagination for this new stream, update next_stream_path in stream helper" end end + # rubocop:enable Rails/HelperInstanceVariable def time_for_scroll(stream) if stream.stream_posts.empty? diff --git a/app/models/post.rb b/app/models/post.rb index 76f829f23..e8abc75ff 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -51,6 +51,13 @@ class Post < ApplicationRecord scope :all_public, -> { where(public: true) } + scope :all_local_public, -> { + where(" exists ( + select 1 from people where posts.author_id = people.id + and people.pod_id is null) + and posts.public = true") + } + scope :commented_by, ->(person) { select('DISTINCT posts.*') .joins(:comments) diff --git a/app/views/streams/main_stream.html.haml b/app/views/streams/main_stream.html.haml index 56be82d92..cf4aaebae 100644 --- a/app/views/streams/main_stream.html.haml +++ b/app/views/streams/main_stream.html.haml @@ -37,6 +37,9 @@ = render "aspects/aspect_listings", stream: @stream %li.nested-list = render "tags/followed_tags_listings" + - if AppConfig.local_posts_stream?(current_user) + %li{data: {stream: "local_public"}} + = link_to t("streams.local_public.title"), local_public_stream_path, rel: "backbone", class: "hoverable" %li{data: {stream: "public"}} = link_to t("streams.public.title"), public_stream_path, rel: "backbone", class: "hoverable" diff --git a/config/defaults.yml b/config/defaults.yml index 619c43a0c..639ef6a52 100644 --- a/config/defaults.yml +++ b/config/defaults.yml @@ -74,6 +74,7 @@ defaults: settings: pod_name: 'diaspora*' enable_registrations: true + enable_local_posts_stream: 'disabled' autofollow_on_join: true autofollow_on_join_user: 'hq@pod.diaspora.software' welcome_message: diff --git a/config/diaspora.toml.example b/config/diaspora.toml.example index bdd5ec391..b41b4c1eb 100644 --- a/config/diaspora.toml.example +++ b/config/diaspora.toml.example @@ -287,6 +287,14 @@ ## (or commented out) to enable the first registration (you). #enable_registrations = true +## Show local posts stream (default="disabled") +## If any other setting than disabled local public posts +## created on this pod can be shown. +## Setting this to admins shows the local posts stream only to users with the admin role. +## Setting this to moderators shows the local posts stream only to users with the moderator or admin role. +## Setting this to everyone shows the local posts stream to all users. +# enable_local_posts_stream= "disabled"|"admins"|"moderators"|"everyone" + ## Auto-follow on sign-up (default=true) ## Users will automatically follow a specified account on creation. ## Set this to false if you don't want your users to automatically diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 12379c1b6..2e8a21915 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -1249,6 +1249,9 @@ en: public: title: "Public activity" + local_public: + title: "Local posts" + multi: title: "Stream" diff --git a/config/routes.rb b/config/routes.rb index 9a5782a41..e776cbb04 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -52,6 +52,7 @@ Rails.application.routes.draw do get "activity" => "streams#activity", :as => "activity_stream" get "stream" => "streams#multi", :as => "stream" get "public" => "streams#public", :as => "public_stream" + get "local_public" => "streams#local_public", :as => "local_public_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" diff --git a/lib/configuration_methods.rb b/lib/configuration_methods.rb index 0c7a358d6..22a5e1a16 100644 --- a/lib/configuration_methods.rb +++ b/lib/configuration_methods.rb @@ -50,6 +50,15 @@ module Configuration self["services.#{service}.authorized"] == true end + def local_posts_stream?(user) + return true if settings.enable_local_posts_stream == "admins" && + Role.is_admin?(user) + return true if settings.enable_local_posts_stream == "moderators" && + (Role.moderator?(user) || Role.is_admin?(user)) + + settings.enable_local_posts_stream == "everyone" + end + def secret_token if heroku? return ENV["SECRET_TOKEN"] if ENV["SECRET_TOKEN"] diff --git a/lib/stream.rb b/lib/stream.rb index 27cb67e25..51c7512e6 100644 --- a/lib/stream.rb +++ b/lib/stream.rb @@ -1,14 +1,15 @@ # frozen_string_literal: true module Stream - require 'stream/activity' - require 'stream/aspect' - require 'stream/comments' - require 'stream/followed_tag' - require 'stream/likes' - require 'stream/mention' - require 'stream/multi' - require 'stream/person' - require 'stream/public' - require 'stream/tag' + require "stream/activity" + require "stream/aspect" + require "stream/comments" + require "stream/followed_tag" + require "stream/likes" + require "stream/mention" + require "stream/multi" + require "stream/person" + require "stream/public" + require "stream/local_public" + require "stream/tag" end diff --git a/lib/stream/local_public.rb b/lib/stream/local_public.rb new file mode 100644 index 000000000..bb25f359b --- /dev/null +++ b/lib/stream/local_public.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# rubocop:disable Style/ClassAndModuleChildren +class Stream::LocalPublic < Stream::Base + def link(opts={}) + Rails.application.routes.url_helpers.local_public_stream_path(opts) + end + + def title + I18n.t("streams.local_public.title") + end + + # @return [ActiveRecord::Association] AR association of posts + def posts + @posts ||= Post.all_local_public + end + + def can_comment?(post) + post.author.local? + end + + # Override base class method + def aspects + ["public"] + end +end +# rubocop:enable Style/ClassAndModuleChildren diff --git a/spec/helpers/stream_helper_spec.rb b/spec/helpers/stream_helper_spec.rb index dac0196cb..da403c42b 100644 --- a/spec/helpers/stream_helper_spec.rb +++ b/spec/helpers/stream_helper_spec.rb @@ -20,6 +20,13 @@ describe StreamHelper, type: :helper do expect(helper.next_page_path).to include "/public" end + it "works for local-public page when current page is local-public stream" do + allow(helper).to receive(:current_page?).and_return(false) + expect(helper).to receive(:current_page?).with(:local_public_stream).and_return(true) + allow(helper).to receive(:controller).and_return(build_controller(StreamsController)) + expect(helper.next_page_path).to include local_public_stream_path + end + it "works for stream page when current page is stream" do allow(helper).to receive(:current_page?).and_return(false) expect(helper).to receive(:current_page?).with(:stream).and_return(true) diff --git a/spec/lib/configuration_methods_spec.rb b/spec/lib/configuration_methods_spec.rb index ae812578c..2453d406f 100644 --- a/spec/lib/configuration_methods_spec.rb +++ b/spec/lib/configuration_methods_spec.rb @@ -128,6 +128,43 @@ describe Configuration::Methods do end end + describe "#has_local_posts_stream" do + let!(:moderator) { create(:person) } + let!(:admin) { create(:person) } + before do + moderator.roles.create(name: "moderator") + admin.roles.create(name: "admin") + end + + it "return false if show_local_posts_link is 'disabled'" do + AppConfig.settings.enable_local_posts_stream = "disabled" + expect(AppConfig.local_posts_stream?(admin)).to be false + expect(AppConfig.local_posts_stream?(moderator)).to be false + expect(AppConfig.local_posts_stream?(alice)).to be false + end + + it "return true for admins if show_local_posts_link is 'admins'" do + AppConfig.settings.enable_local_posts_stream = "admins" + expect(AppConfig.local_posts_stream?(admin)).to be true + expect(AppConfig.local_posts_stream?(moderator)).to be false + expect(AppConfig.local_posts_stream?(alice)).to be false + end + + it "return true for admins and moderators if show_local_posts_link is 'moderators'" do + AppConfig.settings.enable_local_posts_stream = "moderators" + expect(AppConfig.local_posts_stream?(admin)).to be true + expect(AppConfig.local_posts_stream?(moderator)).to be true + expect(AppConfig.local_posts_stream?(alice)).to be false + end + + it "return true for everybody if show_local_posts_link is 'everyone'" do + AppConfig.settings.enable_local_posts_stream = "everyone" + expect(AppConfig.local_posts_stream?(admin)).to be true + expect(AppConfig.local_posts_stream?(moderator)).to be true + expect(AppConfig.local_posts_stream?(alice)).to be true + end + end + describe "#version_string" do before do @version = double diff --git a/spec/lib/stream/local_public_spec.rb b/spec/lib/stream/local_public_spec.rb new file mode 100644 index 000000000..69892a07b --- /dev/null +++ b/spec/lib/stream/local_public_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require Rails.root.join("spec/shared_behaviors/stream") + +describe Stream::LocalPublic do + before do + @stream = Stream::LocalPublic.new(alice) + end + + describe "shared behaviors" do + it_should_behave_like "it is a stream" + end + + describe "#posts" do + it "calls Post#all_local_public" do + expect(Post).to receive(:all_local_public) + @stream.posts + end + end +end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 3e77e142f..11eaf55de 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -58,6 +58,23 @@ describe Post, type: :model do end end + describe ".all_local_public" do + it "includes all public posts from local" do + post1 = FactoryBot.create(:status_message, author: alice.person, public: true) + post2 = FactoryBot.create(:status_message, author: bob.person, public: true) + expect(Post.all_local_public.ids).to match_array([post1.id, post2.id]) + end + + it "doesn't include any posts from other pods" do + pod = FactoryBot.create(:pod) + external_person = FactoryBot.create(:person, pod: pod) + FactoryBot.create(:status_message, author: alice.person, public: true) + FactoryBot.create(:status_message, author: bob.person, public: true) + post_from_extern = FactoryBot.create(:status_message, author: external_person, public: true) + expect(Post.all_local_public.ids).not_to match_array([post_from_extern.id]) + end + end + describe ".for_a_stream" do it "calls #for_visible_shareable_sql" do time = double