Skip to content

Commit 70e6468

Browse files
authored
FEATURE: Add following feed to /filter (#148)
* FEATURE: Add following feed to `/filter` Adds `following-feed:<username>` to the `/filter` endpoint It returns the topics from the users that the current user is following(topics from the `/my/follow/feed` page). * DEV: lint * DEV: create `topics_for` method create tests for it and update `/filter` to use it * DEV: use `Topic` scope methods
1 parent 296b397 commit 70e6468

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

app/models/user_follower.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ def self.posts_for(user, current_user:, limit: nil, created_before: nil, created
2626
results
2727
end
2828

29+
def self.topics_for(user, current_user:, limit: nil, created_before: nil, created_after: nil)
30+
results =
31+
Topic
32+
.joins(:user)
33+
.joins("INNER JOIN user_followers ON user_followers.user_id = users.id")
34+
.preload(:user, :category)
35+
.where("user_followers.follower_id = ?", user.id)
36+
.listable_topics
37+
.visible
38+
.order(created_at: :desc)
39+
40+
results = filter_opted_out_users(results)
41+
results = Guardian.new(current_user).filter_allowed_categories(results)
42+
results = results.limit(limit) if limit
43+
results = results.where("topics.created_at < ?", created_before) if created_before
44+
results = results.where("topics.created_at > ?", created_after) if created_after
45+
46+
results
47+
end
48+
2949
def self.filter_opted_out_users(relation)
3050
truthy_values = sanitize_sql(["?", HasCustomFields::Helpers::CUSTOM_FIELD_TRUE])
3151
if SiteSetting.default_allow_people_to_follow_me

plugin.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,17 @@ module ::Follow
108108
next if !new_record
109109
Follow::NotificationHandler.new(post, notified).handle
110110
end
111+
112+
filter_following_topics = ->(scope, username, guardian) do
113+
user = User.find_by(username: username)
114+
115+
next scope if user.nil?
116+
next scope.none if user.id != guardian.user.id && !guardian.user.staff?
117+
118+
topic_ids = UserFollower.topics_for(user, current_user: guardian.user).pluck(:id)
119+
120+
scope.where("topics.id IN (?)", topic_ids)
121+
end
122+
123+
add_filter_custom_filter("following-feed", &filter_following_topics)
111124
end

spec/models/user_follower_spec.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,71 @@
188188
end
189189
end
190190
end
191+
192+
describe ".topics_for" do
193+
it "does not show topics in PMs" do
194+
Fabricate(:private_message_topic, user: followed)
195+
topics = UserFollower.topics_for(follower, current_user: admin)
196+
expect(topics.pluck(:id)).to be_blank
197+
end
198+
199+
it "filters with created_before" do
200+
topic_1 = Fabricate(:topic, user: followed, created_at: 1.day.ago)
201+
topic_2 = Fabricate(:topic, user: followed, created_at: 2.day.ago)
202+
203+
topics = UserFollower.topics_for(follower, current_user: admin, created_before: 25.hours.ago)
204+
expect(topics).to contain_exactly(topic_2)
205+
end
206+
207+
it "filters with created_after" do
208+
Fabricate(:topic, user: followed, created_at: 2.day.ago)
209+
topic_2 = Fabricate(:topic, user: followed, created_at: 1.day.ago)
210+
211+
topics = UserFollower.topics_for(follower, current_user: admin, created_after: 25.hours.ago)
212+
expect(topics).to contain_exactly(topic_2)
213+
end
214+
215+
it "does not show unlisted topics" do
216+
topic = Fabricate(:topic, user: followed)
217+
topic.update_status("visible", false, Discourse.system_user)
218+
topics = UserFollower.topics_for(follower, current_user: admin)
219+
expect(topics.pluck(:id)).to be_blank
220+
end
221+
222+
it "does not show topics in secured categories that current user does " \
223+
"not have access to" do
224+
Fabricate(:topic, user: followed, category: secure_category)
225+
topics = UserFollower.topics_for(follower, current_user: follower)
226+
expect(topics.pluck(:id)).to be_blank
227+
end
228+
229+
it "does not show deleted topics" do
230+
topic = Fabricate(:topic, user: followed)
231+
topic.update!(deleted_at: 1.minute.ago)
232+
topics = UserFollower.topics_for(follower, current_user: admin)
233+
expect(topics.pluck(:id)).to be_blank
234+
end
235+
236+
describe "it behaves like posts_for" do
237+
it "brings topics the same way as posts_for" do
238+
Fabricate(:post, user: followed)
239+
topics = UserFollower.topics_for(follower, current_user: admin)
240+
topic_ids_from_posts_for =
241+
UserFollower.posts_for(follower, current_user: admin).map(&:topic_id)
242+
243+
expect(topics.pluck(:id)).to contain_exactly(*topic_ids_from_posts_for)
244+
end
245+
246+
it "does not show repeated topics" do
247+
topic = Fabricate(:topic, user: followed)
248+
Fabricate.times(3, :post, user: followed, topic: topic)
249+
250+
topics = UserFollower.topics_for(follower, current_user: admin)
251+
expect(topics.size).to eq(1)
252+
253+
posts = UserFollower.posts_for(follower, current_user: admin)
254+
expect(posts.size).to eq(3)
255+
end
256+
end
257+
end
191258
end

spec/requests/follow_controller_spec.rb

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def response_topic_ids(response)
183183
expect(response.status).to eq(403)
184184
end
185185

186-
it "allows staff users to acess the follow posts feed of other users" do
186+
it "allows staff users to access the follow posts feed of other users" do
187187
mod = Fabricate(:moderator)
188188
sign_in(mod)
189189
get "/follow/posts/#{user1.username}.json"
@@ -281,4 +281,68 @@ def response_topic_ids(response)
281281
expect(p["topic"]["posts_count"]).to eq(post_1.topic.posts_count)
282282
end
283283
end
284+
285+
describe "#filter" do
286+
before do
287+
::Follow::Updater.new(user1, user2).watch_follow
288+
::Follow::Updater.new(user1, tl3).watch_follow
289+
end
290+
291+
fab!(:post_5) { Fabricate(:post, user: user2, created_at: 20.hours.ago) }
292+
fab!(:post_4) { Fabricate(:post, user: user2, created_at: 10.hours.ago) }
293+
fab!(:post_3) { Fabricate(:post, user: user2, created_at: 5.hours.ago) }
294+
fab!(:post_2) { Fabricate(:post, user: tl3, created_at: 3.hours.ago) }
295+
fab!(:post_1) { Fabricate(:post, user: user2, topic: post_3.topic, created_at: 2.hours.ago) }
296+
fab!(:post_by_unfollowed_user) { Fabricate(:post) }
297+
298+
it "allows users to see their own follow posts feed" do
299+
sign_in(user1)
300+
get "/follow/posts/#{user1.username}.json"
301+
expect(response.status).to eq(200)
302+
topic_ids_from_api = response.parsed_body["posts"].map { |t| t["topic_id"] }.uniq
303+
304+
get "/filter", params: { q: "following-feed:#{user1.username}", format: :json }
305+
expect(response.status).to eq(200)
306+
topic_ids_from_filter = response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }.uniq
307+
308+
expect(topic_ids_from_api).to match_array(topic_ids_from_filter)
309+
end
310+
311+
it "should not allow users to see other users follow posts feed" do
312+
sign_in(user2)
313+
get "/follow/posts/#{user1.username}.json"
314+
expect(response.status).to eq(403)
315+
316+
get "/filter", params: { q: "following-feed:#{user1.username}", format: :json }
317+
expect(response.parsed_body["topic_list"]["topics"]).to eq([])
318+
end
319+
320+
it "allows staff users to access the follow posts feed of other users" do
321+
user1_interacted_topics = [
322+
post_1.topic.id,
323+
post_2.topic.id,
324+
post_3.topic.id,
325+
post_4.topic.id,
326+
post_5.topic.id,
327+
].uniq
328+
329+
mod = Fabricate(:moderator)
330+
sign_in(mod)
331+
332+
get "/filter", params: { q: "following-feed:#{user1.username}", format: :json }
333+
expect(response.status).to eq(200)
334+
topic_ids_from_filter = response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }.uniq
335+
336+
expect(topic_ids_from_filter).to match_array(user1_interacted_topics)
337+
338+
admin = Fabricate(:admin)
339+
sign_in(admin)
340+
341+
get "/filter", params: { q: "following-feed:#{user1.username}", format: :json }
342+
expect(response.status).to eq(200)
343+
topic_ids_from_filter = response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }.uniq
344+
345+
expect(topic_ids_from_filter).to match_array(user1_interacted_topics)
346+
end
347+
end
284348
end

0 commit comments

Comments
 (0)