discourse/app/models/user_search.rb
Sam Saffron 30990006a9 DEV: enable frozen string literal on all files
This reduces chances of errors where consumers of strings mutate inputs
and reduces memory usage of the app.

Test suite passes now, but there may be some stuff left, so we will run
a few sites on a branch prior to merging
2019-05-13 09:31:32 +08:00

122 lines
3.2 KiB
Ruby

# frozen_string_literal: true
# Searches for a user by username or full text or name (if enabled in SiteSettings)
require_dependency 'search'
class UserSearch
def initialize(term, opts = {})
@term = term
@term_like = "#{term.downcase.gsub("_", "\\_")}%"
@topic_id = opts[:topic_id]
@topic_allowed_users = opts[:topic_allowed_users]
@searching_user = opts[:searching_user]
@include_staged_users = opts[:include_staged_users] || false
@limit = opts[:limit] || 20
@group = opts[:group]
@guardian = Guardian.new(@searching_user)
@guardian.ensure_can_see_group!(@group) if @group
end
def scoped_users
users = User.where(active: true)
users = users.where(staged: false) unless @include_staged_users
if @group
users = users.where('users.id IN (
SELECT user_id FROM group_users WHERE group_id = ?
)', @group.id)
end
unless @searching_user && @searching_user.staff?
users = users.not_suspended
end
# Only show users who have access to private topic
if @topic_id && @topic_allowed_users == "true"
topic = Topic.find_by(id: @topic_id)
if topic.category && topic.category.read_restricted
users = users.includes(:secure_categories)
.where("users.admin = TRUE OR categories.id = ?", topic.category.id)
.references(:categories)
end
end
users.limit(@limit)
end
def filtered_by_term_users
users = scoped_users
if @term.present?
if SiteSetting.enable_names? && @term !~ /[_\.-]/
query = Search.ts_query(term: @term, ts_config: "simple")
users = users.includes(:user_search_data)
.references(:user_search_data)
.where("user_search_data.search_data @@ #{query}")
.order(DB.sql_fragment("CASE WHEN username_lower LIKE ? THEN 0 ELSE 1 END ASC", @term_like))
else
users = users.where("username_lower LIKE :term_like", term_like: @term_like)
end
end
users
end
def search_ids
users = Set.new
# 1. exact username matches
if @term.present?
scoped_users.where(username_lower: @term.downcase)
.limit(@limit)
.pluck(:id)
.each { |id| users << id }
end
return users.to_a if users.length >= @limit
# 2. in topic
if @topic_id
in_topic = filtered_by_term_users.where('users.id IN (SELECT p.user_id FROM posts p WHERE topic_id = ?)', @topic_id)
if @searching_user.present?
in_topic = in_topic.where('users.id <> ?', @searching_user.id)
end
in_topic
.order('last_seen_at DESC')
.limit(@limit - users.length)
.pluck(:id)
.each { |id| users << id }
end
return users.to_a if users.length >= @limit
# 3. global matches
if !@topic_id || @term.present?
filtered_by_term_users.order('last_seen_at DESC')
.limit(@limit - users.length)
.pluck(:id)
.each { |id| users << id }
end
users.to_a
end
def search
ids = search_ids
return User.where("0=1") if ids.empty?
User.joins("JOIN (SELECT unnest uid, row_number() OVER () AS rn
FROM unnest('{#{ids.join(",")}}'::int[])
) x on uid = users.id")
.order("rn")
end
end