mirror of
https://github.com/discourse/discourse.git
synced 2025-06-19 22:23:04 +08:00

This reverts commit 38de3d7bd1f503743c5d0237bc8a8d9d89effb8e. This changed seemed to be blocking our own AI helper as well if it has the “Search” tool.
258 lines
7.7 KiB
Ruby
258 lines
7.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class SearchController < ApplicationController
|
|
before_action :cancel_overloaded_search, only: [:query]
|
|
skip_before_action :check_xhr, only: :show
|
|
after_action :add_noindex_header
|
|
|
|
PAGE_LIMIT = 10
|
|
|
|
def self.valid_context_types
|
|
%w[user topic category private_messages tag]
|
|
end
|
|
|
|
def show
|
|
permitted_params = params.permit(:q, :page)
|
|
@search_term = permitted_params[:q]
|
|
|
|
# a q param has been given but it's not in the correct format
|
|
# eg: ?q[foo]=bar
|
|
raise Discourse::InvalidParameters.new(:q) if params[:q].present? && !@search_term.present?
|
|
|
|
if @search_term.present? && @search_term.length < SiteSetting.min_search_term_length
|
|
raise Discourse::InvalidParameters.new(:q)
|
|
end
|
|
|
|
if @search_term.present? && @search_term.include?("\u0000")
|
|
raise Discourse::InvalidParameters.new("string contains null byte")
|
|
end
|
|
|
|
page = permitted_params[:page]
|
|
# check for a malformed page parameter
|
|
raise Discourse::InvalidParameters if page && (!page.is_a?(String) || page.to_i.to_s != page)
|
|
if page && page.to_i > PAGE_LIMIT
|
|
raise Discourse::InvalidParameters.new("page parameter must not be greater than 10")
|
|
end
|
|
|
|
discourse_expires_in 1.minute
|
|
|
|
search_args = {
|
|
type_filter: "topic",
|
|
guardian: guardian,
|
|
blurb_length: 300,
|
|
page: [page.to_i, 1].max,
|
|
}
|
|
|
|
context, type = lookup_search_context
|
|
if context
|
|
search_args[:search_context] = context
|
|
search_args[:type_filter] = type if type
|
|
end
|
|
|
|
search_args[:search_type] = :full_page
|
|
search_args[:ip_address] = request.remote_ip
|
|
search_args[:user_agent] = request.user_agent
|
|
search_args[:user_id] = current_user.id if current_user.present?
|
|
|
|
if rate_limit_search
|
|
return(
|
|
render json: failed_json.merge(message: I18n.t("rate_limiter.slow_down")),
|
|
status: :too_many_requests
|
|
)
|
|
elsif site_overloaded?
|
|
result =
|
|
Search::GroupedSearchResults.new(
|
|
type_filter: search_args[:type_filter],
|
|
term: @search_term,
|
|
search_context: context,
|
|
)
|
|
|
|
result.error = I18n.t("search.extreme_load_error")
|
|
else
|
|
search = Search.new(@search_term, search_args)
|
|
result = search.execute(readonly_mode: @readonly_mode)
|
|
result.find_user_data(guardian) if result
|
|
end
|
|
|
|
serializer = serialize_data(result, GroupedSearchResultSerializer, result: result)
|
|
|
|
respond_to do |format|
|
|
format.html { store_preloaded("search", MultiJson.dump(serializer)) }
|
|
format.json { render_json_dump(serializer) }
|
|
end
|
|
end
|
|
|
|
def query
|
|
params.require(:term)
|
|
|
|
if params[:term].include?("\u0000")
|
|
raise Discourse::InvalidParameters.new("string contains null byte")
|
|
end
|
|
|
|
discourse_expires_in 1.minute
|
|
|
|
search_args = { guardian: guardian }
|
|
|
|
search_args[:type_filter] = params[:type_filter] if params[:type_filter].present?
|
|
search_args[:search_for_id] = true if params[:search_for_id].present?
|
|
|
|
context, type = lookup_search_context
|
|
|
|
if context
|
|
search_args[:search_context] = context
|
|
search_args[:type_filter] = type if type
|
|
end
|
|
|
|
search_args[:search_type] = :header
|
|
search_args[:ip_address] = request.remote_ip
|
|
search_args[:user_agent] = request.user_agent
|
|
search_args[:user_id] = current_user.id if current_user.present?
|
|
search_args[:restrict_to_archetype] = params[:restrict_to_archetype] if params[
|
|
:restrict_to_archetype
|
|
].present?
|
|
|
|
if rate_limit_search
|
|
return(
|
|
render json: failed_json.merge(message: I18n.t("rate_limiter.slow_down")),
|
|
status: :too_many_requests
|
|
)
|
|
elsif site_overloaded?
|
|
result =
|
|
GroupedSearchResults.new(
|
|
type_filter: search_args[:type_filter],
|
|
term: params[:term],
|
|
search_context: context,
|
|
)
|
|
else
|
|
search = Search.new(params[:term], search_args)
|
|
result = search.execute(readonly_mode: @readonly_mode)
|
|
end
|
|
render_serialized(result, GroupedSearchResultSerializer, result: result)
|
|
end
|
|
|
|
def click
|
|
params.require(:search_log_id)
|
|
params.require(:search_result_type)
|
|
params.require(:search_result_id)
|
|
|
|
search_result_type = params[:search_result_type].downcase.to_sym
|
|
if SearchLog.search_result_types.has_key?(search_result_type)
|
|
attributes = { id: params[:search_log_id] }
|
|
if current_user.present?
|
|
attributes[:user_id] = current_user.id
|
|
else
|
|
attributes[:ip_address] = request.remote_ip
|
|
end
|
|
|
|
if search_result_type == :tag
|
|
search_result_id = Tag.find_by_name(params[:search_result_id])&.id
|
|
else
|
|
search_result_id = params[:search_result_id]
|
|
end
|
|
|
|
SearchLog.where(attributes).update_all(
|
|
search_result_type: SearchLog.search_result_types[search_result_type],
|
|
search_result_id: search_result_id,
|
|
)
|
|
end
|
|
|
|
render json: success_json
|
|
end
|
|
|
|
protected
|
|
|
|
def site_overloaded?
|
|
queue_time = request.env["REQUEST_QUEUE_SECONDS"]
|
|
if queue_time
|
|
threshold = GlobalSetting.disable_search_queue_threshold.to_f
|
|
threshold > 0 && queue_time > threshold
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def rate_limit_search
|
|
begin
|
|
if current_user.present?
|
|
RateLimiter.new(
|
|
current_user,
|
|
"search-min",
|
|
SiteSetting.rate_limit_search_user,
|
|
1.minute,
|
|
).performed!
|
|
else
|
|
RateLimiter.new(
|
|
nil,
|
|
"search-min-#{request.remote_ip}-per-sec",
|
|
SiteSetting.rate_limit_search_anon_user_per_second,
|
|
1.second,
|
|
).performed!
|
|
RateLimiter.new(
|
|
nil,
|
|
"search-min-#{request.remote_ip}-per-min",
|
|
SiteSetting.rate_limit_search_anon_user_per_minute,
|
|
1.minute,
|
|
).performed!
|
|
RateLimiter.new(
|
|
nil,
|
|
"search-min-anon-global-per-sec",
|
|
SiteSetting.rate_limit_search_anon_global_per_second,
|
|
1.second,
|
|
).performed!
|
|
RateLimiter.new(
|
|
nil,
|
|
"search-min-anon-global-per-min",
|
|
SiteSetting.rate_limit_search_anon_global_per_minute,
|
|
1.minute,
|
|
).performed!
|
|
end
|
|
rescue RateLimiter::LimitExceeded => e
|
|
return e
|
|
end
|
|
false
|
|
end
|
|
|
|
def cancel_overloaded_search
|
|
render_json_error I18n.t("search.extreme_load_error"), status: 409 if site_overloaded?
|
|
end
|
|
|
|
def lookup_search_context
|
|
return if params[:skip_context] == "true"
|
|
|
|
search_context = params[:search_context]
|
|
unless search_context
|
|
if (context = params[:context]) && (id = params[:context_id])
|
|
search_context = { type: context, id: id, name: id }
|
|
end
|
|
end
|
|
|
|
if search_context.present?
|
|
if SearchController.valid_context_types.exclude?(search_context[:type])
|
|
raise Discourse::InvalidParameters.new(:search_context)
|
|
end
|
|
raise Discourse::InvalidParameters.new(:search_context) if search_context[:id].blank?
|
|
|
|
# A user is found by username
|
|
context_obj = nil
|
|
if %w[user private_messages].include? search_context[:type]
|
|
context_obj = User.find_by(username_lower: search_context[:id].downcase)
|
|
elsif "category" == search_context[:type]
|
|
context_obj = Category.find_by(id: search_context[:id].to_i)
|
|
elsif "topic" == search_context[:type]
|
|
context_obj = Topic.find_by(id: search_context[:id].to_i)
|
|
elsif "tag" == search_context[:type]
|
|
if !DiscourseTagging.hidden_tag_names(guardian).include?(search_context[:id])
|
|
context_obj = Tag.where_name(search_context[:id]).first
|
|
end
|
|
end
|
|
|
|
type_filter = nil
|
|
type_filter = "private_messages" if search_context[:type] == "private_messages"
|
|
|
|
guardian.ensure_can_see!(context_obj)
|
|
|
|
[context_obj, type_filter]
|
|
end
|
|
end
|
|
end
|