mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-05 11:23:47 +08:00
Previously, a user's Forum Best Friend Forever (FBFF) could be someone they had muted or ignored, which is a poor user experience - you wouldn't want someone you've explicitly blocked from your feed to be presented as your "best friend". This adds NOT EXISTS subqueries to both post_query and like_query methods to filter out any users that the current user has in their muted_users or ignored_users lists. The like_query method now accepts a user parameter to enable this filtering.
164 lines
5.7 KiB
Ruby
164 lines
5.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Forum Best Friend Forever ranking
|
|
# Score is informative only, do not show in UI
|
|
module DiscourseRewind
|
|
module Action
|
|
class Fbff < BaseReport
|
|
MAX_SUMMARY_RESULTS = 50
|
|
LIKE_SCORE = 1
|
|
REPLY_SCORE = 10
|
|
|
|
FakeData = {
|
|
data: {
|
|
fbff: {
|
|
id: 2,
|
|
username: "codingpal",
|
|
name: "Coding Pal",
|
|
avatar_template: "/letter_avatar_proxy/v4/letter/c/3be4f8/{size}.png",
|
|
},
|
|
yourself: {
|
|
id: 1,
|
|
username: "you",
|
|
name: "You",
|
|
avatar_template: "/letter_avatar_proxy/v4/letter/y/f05b48/{size}.png",
|
|
},
|
|
},
|
|
identifier: "fbff",
|
|
}
|
|
|
|
def call
|
|
return FakeData if should_use_fake_data?
|
|
|
|
most_liked_users =
|
|
like_query(user, date)
|
|
.where(acting_user_id: user.id)
|
|
.group(:user_id)
|
|
.order("COUNT(*) DESC")
|
|
.limit(MAX_SUMMARY_RESULTS)
|
|
.pluck("user_actions.user_id, COUNT(*)")
|
|
.map { |user_id, count| { user_id => count } }
|
|
.reduce({}, :merge)
|
|
|
|
most_liked_by_users =
|
|
like_query(user, date)
|
|
.where(user: user)
|
|
.group(:acting_user_id)
|
|
.order("COUNT(*) DESC")
|
|
.limit(MAX_SUMMARY_RESULTS)
|
|
.pluck("acting_user_id, COUNT(*)")
|
|
.map { |acting_user_id, count| { acting_user_id => count } }
|
|
.reduce({}, :merge)
|
|
|
|
users_who_most_replied_me =
|
|
post_query(user, date)
|
|
.where(posts: { user_id: user.id })
|
|
.group("replies.user_id")
|
|
.order("COUNT(*) DESC")
|
|
.limit(MAX_SUMMARY_RESULTS)
|
|
.pluck("replies.user_id, COUNT(*)")
|
|
.map { |user_id, count| { user_id => count } }
|
|
.reduce({}, :merge)
|
|
|
|
users_i_most_replied =
|
|
post_query(user, date)
|
|
.where("replies.user_id = ?", user.id)
|
|
.group("posts.user_id")
|
|
.order("COUNT(*) DESC")
|
|
.limit(MAX_SUMMARY_RESULTS)
|
|
.pluck("posts.user_id, COUNT(*)")
|
|
.map { |user_id, count| { user_id => count } }
|
|
.reduce({}, :merge)
|
|
|
|
# NOTE: At some point maybe we want to include chat interactions
|
|
# in the calculations here.
|
|
fbffs = [
|
|
apply_score(most_liked_users, LIKE_SCORE),
|
|
apply_score(most_liked_by_users, LIKE_SCORE),
|
|
apply_score(users_who_most_replied_me, REPLY_SCORE),
|
|
apply_score(users_i_most_replied, REPLY_SCORE),
|
|
]
|
|
|
|
fbff_id =
|
|
fbffs
|
|
.flatten
|
|
.inject { |h1, h2| h1.merge(h2) { |_, v1, v2| v1 + v2 } }
|
|
&.sort_by { |_, v| -v }
|
|
&.first
|
|
&.first
|
|
|
|
return if !fbff_id
|
|
|
|
{
|
|
data: {
|
|
fbff: BasicUserSerializer.new(User.find(fbff_id), root: false).as_json,
|
|
yourself: BasicUserSerializer.new(user, root: false).as_json,
|
|
},
|
|
identifier: "fbff",
|
|
}
|
|
end
|
|
|
|
def post_query(user, date)
|
|
Post
|
|
.with(eligible_users: User.real.activated.not_suspended.select(:id))
|
|
.joins(:topic)
|
|
.includes(:topic)
|
|
.where(
|
|
"posts.post_type IN (?)",
|
|
Topic.visible_post_types(user, include_moderator_actions: false),
|
|
)
|
|
.joins(
|
|
"INNER JOIN posts replies ON posts.topic_id = replies.topic_id AND posts.reply_to_post_number = replies.post_number",
|
|
)
|
|
.joins(
|
|
"INNER JOIN topics ON replies.topic_id = topics.id
|
|
AND topics.archetype <> 'private_message'
|
|
AND replies.post_type IN (#{Topic.visible_post_types(user, include_moderator_actions: false).join(",")})",
|
|
)
|
|
.joins("INNER JOIN eligible_users eu ON eu.id = replies.user_id")
|
|
.joins("INNER JOIN eligible_users eu2 ON eu2.id = posts.user_id")
|
|
.where("replies.created_at BETWEEN ? AND ?", date.first, date.last)
|
|
.where("posts.created_at BETWEEN ? AND ?", date.first, date.last)
|
|
.where("replies.user_id <> posts.user_id")
|
|
.where(<<~SQL, user_id: user.id)
|
|
NOT EXISTS (
|
|
SELECT 1 FROM muted_users
|
|
WHERE muted_users.user_id = :user_id
|
|
AND muted_users.muted_user_id IN (replies.user_id, posts.user_id)
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM ignored_users
|
|
WHERE ignored_users.user_id = :user_id
|
|
AND ignored_users.ignored_user_id IN (replies.user_id, posts.user_id)
|
|
)
|
|
SQL
|
|
end
|
|
|
|
def like_query(user, date)
|
|
UserAction
|
|
.with(eligible_users: User.real.activated.not_suspended.select(:id))
|
|
.joins(:target_topic, :target_post)
|
|
.joins("INNER JOIN eligible_users eu ON eu.id = user_actions.user_id")
|
|
.joins("INNER JOIN eligible_users eu2 ON eu2.id = user_actions.acting_user_id")
|
|
.where(created_at: date)
|
|
.where(action_type: UserAction::WAS_LIKED)
|
|
.where(<<~SQL, user_id: user.id)
|
|
NOT EXISTS (
|
|
SELECT 1 FROM muted_users
|
|
WHERE muted_users.user_id = :user_id
|
|
AND muted_users.muted_user_id IN (user_actions.user_id, user_actions.acting_user_id)
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM ignored_users
|
|
WHERE ignored_users.user_id = :user_id
|
|
AND ignored_users.ignored_user_id IN (user_actions.user_id, user_actions.acting_user_id)
|
|
)
|
|
SQL
|
|
end
|
|
|
|
def apply_score(users, score)
|
|
users.map { |user_id, count| { user_id => count * score } }
|
|
end
|
|
end
|
|
end
|
|
end
|