mirror of
https://github.com/discourse/discourse.git
synced 2024-12-24 20:03:54 +08:00
5f64fd0a21
Introduce new patterns for direct sql that are safe and fast. MiniSql is not prone to memory bloat that can happen with direct PG usage. It also has an extremely fast materializer and very a convenient API - DB.exec(sql, *params) => runs sql returns row count - DB.query(sql, *params) => runs sql returns usable objects (not a hash) - DB.query_hash(sql, *params) => runs sql returns an array of hashes - DB.query_single(sql, *params) => runs sql and returns a flat one dimensional array - DB.build(sql) => returns a sql builder See more at: https://github.com/discourse/mini_sql
280 lines
8.0 KiB
Ruby
280 lines
8.0 KiB
Ruby
# This class performs calculations to determine if a user qualifies for
|
|
# the Leader (3) trust level.
|
|
class TrustLevel3Requirements
|
|
|
|
class PenaltyCounts
|
|
attr_reader :silenced, :suspended
|
|
|
|
def initialize(row)
|
|
@silenced = row['silence_count'] || 0
|
|
@suspended = row['suspend_count'] || 0
|
|
end
|
|
|
|
def total
|
|
@silenced + @suspended
|
|
end
|
|
end
|
|
|
|
include ActiveModel::Serialization
|
|
|
|
LOW_WATER_MARK = 0.9
|
|
|
|
attr_accessor :days_visited, :min_days_visited,
|
|
:num_topics_replied_to, :min_topics_replied_to,
|
|
:topics_viewed, :min_topics_viewed,
|
|
:posts_read, :min_posts_read,
|
|
:topics_viewed_all_time, :min_topics_viewed_all_time,
|
|
:posts_read_all_time, :min_posts_read_all_time,
|
|
:num_flagged_posts, :max_flagged_posts,
|
|
:num_likes_given, :min_likes_given,
|
|
:num_likes_received, :min_likes_received,
|
|
:num_likes_received, :min_likes_received,
|
|
:num_likes_received_days, :min_likes_received_days,
|
|
:num_likes_received_users, :min_likes_received_users,
|
|
:trust_level_locked, :on_grace_period
|
|
|
|
def initialize(user)
|
|
@user = user
|
|
end
|
|
|
|
def requirements_met?
|
|
return false if trust_level_locked
|
|
|
|
(!@user.suspended?) &&
|
|
(!@user.silenced?) &&
|
|
penalty_counts.total == 0 &&
|
|
days_visited >= min_days_visited &&
|
|
num_topics_replied_to >= min_topics_replied_to &&
|
|
topics_viewed >= min_topics_viewed &&
|
|
posts_read >= min_posts_read &&
|
|
num_flagged_posts <= max_flagged_posts &&
|
|
num_flagged_by_users <= max_flagged_by_users &&
|
|
topics_viewed_all_time >= min_topics_viewed_all_time &&
|
|
posts_read_all_time >= min_posts_read_all_time &&
|
|
num_likes_given >= min_likes_given &&
|
|
num_likes_received >= min_likes_received &&
|
|
num_likes_received_users >= min_likes_received_users &&
|
|
num_likes_received_days >= min_likes_received_days
|
|
end
|
|
|
|
def requirements_lost?
|
|
return false if trust_level_locked
|
|
|
|
@user.suspended? ||
|
|
@user.silenced? ||
|
|
penalty_counts.total > 0 ||
|
|
days_visited < min_days_visited * LOW_WATER_MARK ||
|
|
num_topics_replied_to < min_topics_replied_to * LOW_WATER_MARK ||
|
|
topics_viewed < min_topics_viewed * LOW_WATER_MARK ||
|
|
posts_read < min_posts_read * LOW_WATER_MARK ||
|
|
num_flagged_posts > max_flagged_posts ||
|
|
num_flagged_by_users > max_flagged_by_users ||
|
|
topics_viewed_all_time < min_topics_viewed_all_time ||
|
|
posts_read_all_time < min_posts_read_all_time ||
|
|
num_likes_given < min_likes_given * LOW_WATER_MARK ||
|
|
num_likes_received < min_likes_received * LOW_WATER_MARK ||
|
|
num_likes_received_users < min_likes_received_users * LOW_WATER_MARK ||
|
|
num_likes_received_days < min_likes_received_days * LOW_WATER_MARK
|
|
end
|
|
|
|
def time_period
|
|
SiteSetting.tl3_time_period
|
|
end
|
|
|
|
def trust_level_locked
|
|
!@user.manual_locked_trust_level.nil?
|
|
end
|
|
|
|
def on_grace_period
|
|
@user.on_tl3_grace_period?
|
|
end
|
|
|
|
def days_visited
|
|
@user.user_visits.where("visited_at > ? and posts_read > 0", time_period.days.ago).count
|
|
end
|
|
|
|
def penalty_counts
|
|
args = {
|
|
user_id: @user.id,
|
|
silence_user: UserHistory.actions[:silence_user],
|
|
unsilence_user: UserHistory.actions[:unsilence_user],
|
|
suspend_user: UserHistory.actions[:suspend_user],
|
|
unsuspend_user: UserHistory.actions[:unsuspend_user]
|
|
}
|
|
|
|
sql = <<~SQL
|
|
SELECT SUM(
|
|
CASE
|
|
WHEN action = :silence_user THEN 1
|
|
WHEN action = :unsilence_user THEN -1
|
|
ELSE 0
|
|
END
|
|
) AS silence_count,
|
|
SUM(
|
|
CASE
|
|
WHEN action = :suspend_user THEN 1
|
|
WHEN action = :unsuspend_user THEN -1
|
|
ELSE 0
|
|
END
|
|
) AS suspend_count
|
|
FROM user_histories AS uh
|
|
WHERE uh.target_user_id = :user_id
|
|
AND uh.action IN (:silence_user, :unsilence_user, :suspend_user, :unsuspend_user)
|
|
SQL
|
|
|
|
PenaltyCounts.new(DB.query_hash(sql, args).first)
|
|
end
|
|
|
|
def min_days_visited
|
|
SiteSetting.tl3_requires_days_visited
|
|
end
|
|
|
|
def num_topics_replied_to
|
|
@user.posts.select('distinct topic_id').where('created_at > ? AND post_number > 1', time_period.days.ago).count
|
|
end
|
|
|
|
def min_topics_replied_to
|
|
SiteSetting.tl3_requires_topics_replied_to
|
|
end
|
|
|
|
def topics_viewed_query
|
|
TopicViewItem.where(user_id: @user.id).select('topic_id')
|
|
end
|
|
|
|
def topics_viewed
|
|
topics_viewed_query.where('viewed_at > ?', time_period.days.ago).count
|
|
end
|
|
|
|
def min_topics_viewed
|
|
[
|
|
(TrustLevel3Requirements.num_topics_in_time_period.to_i * (SiteSetting.tl3_requires_topics_viewed.to_f / 100.0)).round,
|
|
SiteSetting.tl3_requires_topics_viewed_cap
|
|
].min
|
|
end
|
|
|
|
def posts_read
|
|
@user.user_visits.where('visited_at > ?', time_period.days.ago).pluck(:posts_read).sum
|
|
end
|
|
|
|
def min_posts_read
|
|
[
|
|
(TrustLevel3Requirements.num_posts_in_time_period.to_i * (SiteSetting.tl3_requires_posts_read.to_f / 100.0)).round,
|
|
SiteSetting.tl3_requires_posts_read_cap
|
|
].min
|
|
end
|
|
|
|
def topics_viewed_all_time
|
|
topics_viewed_query.count
|
|
end
|
|
|
|
def min_topics_viewed_all_time
|
|
SiteSetting.tl3_requires_topics_viewed_all_time
|
|
end
|
|
|
|
def posts_read_all_time
|
|
@user.user_visits.pluck(:posts_read).sum
|
|
end
|
|
|
|
def min_posts_read_all_time
|
|
SiteSetting.tl3_requires_posts_read_all_time
|
|
end
|
|
|
|
def num_flagged_posts
|
|
PostAction.with_deleted
|
|
.where(post_id: flagged_post_ids)
|
|
.where.not(user_id: @user.id)
|
|
.where.not(agreed_at: nil)
|
|
.pluck(:post_id)
|
|
.uniq.count
|
|
end
|
|
|
|
def max_flagged_posts
|
|
SiteSetting.tl3_requires_max_flagged
|
|
end
|
|
|
|
def num_flagged_by_users
|
|
@_num_flagged_by_users ||= PostAction.with_deleted
|
|
.where(post_id: flagged_post_ids)
|
|
.where.not(user_id: @user.id)
|
|
.where.not(agreed_at: nil)
|
|
.pluck(:user_id)
|
|
.uniq.count
|
|
end
|
|
|
|
def max_flagged_by_users
|
|
SiteSetting.tl3_requires_max_flagged
|
|
end
|
|
|
|
def num_likes_given
|
|
UserAction.where(user_id: @user.id, action_type: UserAction::LIKE).where('created_at > ?', time_period.days.ago).count
|
|
end
|
|
|
|
def min_likes_given
|
|
SiteSetting.tl3_requires_likes_given
|
|
end
|
|
|
|
def num_likes_received_query
|
|
UserAction.where(user_id: @user.id, action_type: UserAction::WAS_LIKED).where('created_at > ?', time_period.days.ago)
|
|
end
|
|
|
|
def num_likes_received
|
|
num_likes_received_query.count
|
|
end
|
|
|
|
def min_likes_received
|
|
SiteSetting.tl3_requires_likes_received
|
|
end
|
|
|
|
def num_likes_received_days
|
|
# don't do a COUNT(DISTINCT date(created_at)) here!
|
|
num_likes_received_query.pluck('date(created_at)').uniq.size
|
|
end
|
|
|
|
def min_likes_received_days
|
|
# Since min_likes_received / 3 can be greater than the number of days in time_period,
|
|
# cap this result to be less than time_period.
|
|
[(min_likes_received.to_f / 3.0).ceil, (0.75 * time_period.to_f).ceil].min
|
|
end
|
|
|
|
def num_likes_received_users
|
|
# don't do a COUNT(DISTINCT acting_user_id) here!
|
|
num_likes_received_query.pluck(:acting_user_id).uniq.size
|
|
end
|
|
|
|
def min_likes_received_users
|
|
(min_likes_received.to_f / 4.0).ceil
|
|
end
|
|
|
|
def self.clear_cache
|
|
$redis.del NUM_TOPICS_KEY
|
|
$redis.del NUM_POSTS_KEY
|
|
end
|
|
|
|
CACHE_DURATION = 1.day.seconds - 60
|
|
NUM_TOPICS_KEY = "tl3_num_topics"
|
|
NUM_POSTS_KEY = "tl3_num_posts"
|
|
|
|
def self.num_topics_in_time_period
|
|
$redis.get(NUM_TOPICS_KEY) || begin
|
|
count = Topic.listable_topics.visible.created_since(SiteSetting.tl3_time_period.days.ago).count
|
|
$redis.setex NUM_TOPICS_KEY, CACHE_DURATION, count
|
|
count
|
|
end
|
|
end
|
|
|
|
def self.num_posts_in_time_period
|
|
$redis.get(NUM_POSTS_KEY) || begin
|
|
count = Post.public_posts.visible.created_since(SiteSetting.tl3_time_period.days.ago).count
|
|
$redis.setex NUM_POSTS_KEY, CACHE_DURATION, count
|
|
count
|
|
end
|
|
end
|
|
|
|
def flagged_post_ids
|
|
@_flagged_post_ids ||= @user.posts
|
|
.with_deleted
|
|
.where('created_at > ? AND (spam_count > 0 OR inappropriate_count > 0)', time_period.days.ago)
|
|
.pluck(:id)
|
|
end
|
|
end
|