diff --git a/lib/topics_filter.rb b/lib/topics_filter.rb index 807a63c9855..b94bb10297e 100644 --- a/lib/topics_filter.rb +++ b/lib/topics_filter.rb @@ -16,9 +16,11 @@ class TopicsFilter return @scope if query_string.blank? query_string.scan( - /(?[-=])?(?\w+):(?[^\s]+)/, + /(?[-=])?(?[\w-]+):(?[^\s]+)/, ) do |key_prefix, key, value| case key + when "created-by" + filter_created_by_user(usernames: value.split(",")) when "in" @scope = filter_state(state: value) when "status" @@ -90,6 +92,17 @@ class TopicsFilter private + def filter_created_by_user(usernames:) + register_scope( + key: :created_by_user, + params: { + usernames: usernames.map(&:downcase), + }, + ) do |usernames_lower| + @scope.joins(:user).where("users.username_lower IN (?)", usernames_lower) + end + end + def filter_state(state:) case state when "pinned" diff --git a/spec/lib/topics_filter_spec.rb b/spec/lib/topics_filter_spec.rb index 478be56b18c..9872cc4f1a0 100644 --- a/spec/lib/topics_filter_spec.rb +++ b/spec/lib/topics_filter_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe TopicsFilter do - fab!(:user) { Fabricate(:user) } + fab!(:user) { Fabricate(:user, username: "username") } fab!(:admin) { Fabricate(:admin) } fab!(:group) { Fabricate(:group) } @@ -737,5 +737,78 @@ RSpec.describe TopicsFilter do ).to contain_exactly(topic_without_tag.id, topic_with_group_only_tag.id) end end + + describe "when filtering by topic author" do + fab!(:user2) { Fabricate(:user, username: "username2") } + fab!(:topic_by_user) { Fabricate(:topic, user: user) } + fab!(:topic2_by_user) { Fabricate(:topic, user: user) } + fab!(:topic_by_user2) { Fabricate(:topic, user: user2) } + + describe "when query string is `created-by:username`" do + it "should return the topics created by the specified user" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("created-by:#{user.username}") + .pluck(:id), + ).to contain_exactly(topic_by_user.id, topic2_by_user.id) + end + end + + describe "when query string is `created-by:username2`" do + it "should return the topics created by the specified user" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("created-by:#{user2.username}") + .pluck(:id), + ).to contain_exactly(topic_by_user2.id) + end + end + + describe "when query string is `created-by:username created-by:username2`" do + it "should return the topics created by either of the specified users" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("created-by:#{user.username} created-by:#{user2.username}") + .pluck(:id), + ).to contain_exactly(topic_by_user.id, topic2_by_user.id, topic_by_user2.id) + end + end + + describe "when query string is `created-by:username,invalid`" do + it "should only return the topics created by the user with the valid username" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("created-by:#{user.username},invalid") + .pluck(:id), + ).to contain_exactly(topic_by_user.id, topic2_by_user.id) + end + end + + describe "when query string is `created-by:username,username2`" do + it "should return the topics created by either of the specified users" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("created-by:#{user.username},#{user2.username}") + .pluck(:id), + ).to contain_exactly(topic_by_user.id, topic2_by_user.id, topic_by_user2.id) + end + end + + describe "when query string is `created-by:invalid`" do + it "should not return any topics" do + expect( + TopicsFilter + .new(guardian: Guardian.new) + .filter_from_query_string("created-by:invalid") + .pluck(:id), + ).to eq([]) + end + end + end end end diff --git a/spec/requests/list_controller_spec.rb b/spec/requests/list_controller_spec.rb index e7d3484c057..66f7fb1a37e 100644 --- a/spec/requests/list_controller_spec.rb +++ b/spec/requests/list_controller_spec.rb @@ -1132,9 +1132,25 @@ RSpec.describe ListController do describe "#filter" do fab!(:category) { Fabricate(:category) } fab!(:tag) { Fabricate(:tag, name: "tag1") } + fab!(:group) { Fabricate(:group) } + fab!(:private_category) { Fabricate(:private_category, group: Fabricate(:group)) } + fab!(:private_message_topic) { Fabricate(:private_message_topic) } + fab!(:topic_in_private_category) { Fabricate(:topic, category: private_category) } before { SiteSetting.experimental_topics_filter = true } + it "should not return topics that the user is not allowed to view" do + sign_in(user) + + get "/filter.json" + + expect(response.status).to eq(200) + + expect( + response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }, + ).to contain_exactly(topic.id) + end + it "should respond with 403 response code for an anonymous user" do get "/filter.json" @@ -1212,6 +1228,40 @@ RSpec.describe ListController do end end + describe "when filtering with the `created-by:` filter" do + fab!(:topic2) { Fabricate(:topic, user: admin) } + + before do + topic.update!(user: user) + user.update!(username: "username") + admin.update!(username: "username2") + end + + it "returns only topics created by the user when `q` query param is `created-by:username`" do + sign_in(user) + + get "/filter.json", params: { q: "created-by:username" } + + expect(response.status).to eq(200) + + expect( + response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }, + ).to contain_exactly(topic.id) + end + + it "returns only topics created by either user when `q` query param is `created-by:username,username2`" do + sign_in(user) + + get "/filter.json", params: { q: "created-by:username,username2" } + + expect(response.status).to eq(200) + + expect( + response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }, + ).to contain_exactly(topic.id, topic2.id) + end + end + describe "when filtering with the `in:` filter" do fab!(:user_muted_topic) do Fabricate(:topic).tap do |topic|