diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2019-02-26 15:21:36 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-26 15:21:36 +0100 |
commit | e7f20cc43ff21afa229da40ee4e5755495948772 (patch) | |
tree | 4b39ece2b4ed3ecb6b4aa876a6fdf304a0cb9e1e /app | |
parent | ea58e31822d07ca2286f971bf6b0275954dd2726 (diff) |
Add type, limit, offset, min_id, max_id, account_id to search API (#10091)
* Add type, limit, offset, min_id, max_id, account_id to search API Fix #8939 * Make the offset work on accounts and hashtags search as well * Assure brakeman we are not doing mass assignment here * Do not allow paginating unless a type is chosen * Fix search query and index id field on statuses instead of created_at
Diffstat (limited to 'app')
-rw-r--r-- | app/chewy/statuses_index.rb | 2 | ||||
-rw-r--r-- | app/controllers/api/v1/accounts/search_controller.rb | 5 | ||||
-rw-r--r-- | app/controllers/api/v1/search_controller.rb | 26 | ||||
-rw-r--r-- | app/controllers/api/v2/search_controller.rb | 2 | ||||
-rw-r--r-- | app/models/account.rb | 16 | ||||
-rw-r--r-- | app/models/tag.rb | 8 | ||||
-rw-r--r-- | app/services/account_search_service.rb | 11 | ||||
-rw-r--r-- | app/services/search_service.rb | 84 |
8 files changed, 99 insertions, 55 deletions
diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb index eafc1818b..8ce413f8a 100644 --- a/app/chewy/statuses_index.rb +++ b/app/chewy/statuses_index.rb @@ -48,6 +48,7 @@ class StatusesIndex < Chewy::Index end root date_detection: false do + field :id, type: 'long' field :account_id, type: 'long' field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).join("\n\n") } do @@ -55,7 +56,6 @@ class StatusesIndex < Chewy::Index end field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) } - field :created_at, type: 'date' end end end diff --git a/app/controllers/api/v1/accounts/search_controller.rb b/app/controllers/api/v1/accounts/search_controller.rb index 91c9f1547..4217b527a 100644 --- a/app/controllers/api/v1/accounts/search_controller.rb +++ b/app/controllers/api/v1/accounts/search_controller.rb @@ -16,10 +16,11 @@ class Api::V1::Accounts::SearchController < Api::BaseController def account_search AccountSearchService.new.call( params[:q], - limit_param(DEFAULT_ACCOUNTS_LIMIT), current_account, + limit: limit_param(DEFAULT_ACCOUNTS_LIMIT), resolve: truthy_param?(:resolve), - following: truthy_param?(:following) + following: truthy_param?(:following), + offset: params[:offset] ) end end diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb index dc1a37599..6131cbbb6 100644 --- a/app/controllers/api/v1/search_controller.rb +++ b/app/controllers/api/v1/search_controller.rb @@ -3,7 +3,7 @@ class Api::V1::SearchController < Api::BaseController include Authorization - RESULTS_LIMIT = 5 + RESULTS_LIMIT = 20 before_action -> { doorkeeper_authorize! :read, :'read:search' } before_action :require_user! @@ -11,30 +11,22 @@ class Api::V1::SearchController < Api::BaseController respond_to :json def index - @search = Search.new(search) + @search = Search.new(search_results) render json: @search, serializer: REST::SearchSerializer end private - def search - search_results.tap do |search| - search[:statuses].keep_if do |status| - begin - authorize status, :show? - rescue Mastodon::NotPermittedError - false - end - end - end - end - def search_results SearchService.new.call( params[:q], - RESULTS_LIMIT, - truthy_param?(:resolve), - current_account + current_account, + limit_param(RESULTS_LIMIT), + search_params.merge(resolve: truthy_param?(:resolve)) ) end + + def search_params + params.permit(:type, :offset, :min_id, :max_id, :account_id) + end end diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index 2e91d68ee..9aa6edc69 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -2,7 +2,7 @@ class Api::V2::SearchController < Api::V1::SearchController def index - @search = Search.new(search) + @search = Search.new(search_results) render json: @search, serializer: REST::V2::SearchSerializer end end diff --git a/app/models/account.rb b/app/models/account.rb index 12d7a747e..87ce90178 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -386,7 +386,7 @@ class Account < ApplicationRecord DeliveryFailureTracker.filter(urls) end - def search_for(terms, limit = 10) + def search_for(terms, limit = 10, offset = 0) textsearch, query = generate_query_for_search(terms) sql = <<-SQL.squish @@ -398,15 +398,15 @@ class Account < ApplicationRecord AND accounts.suspended = false AND accounts.moved_to_account_id IS NULL ORDER BY rank DESC - LIMIT ? + LIMIT ? OFFSET ? SQL - records = find_by_sql([sql, limit]) + records = find_by_sql([sql, limit, offset]) ActiveRecord::Associations::Preloader.new.preload(records, :account_stat) records end - def advanced_search_for(terms, account, limit = 10, following = false) + def advanced_search_for(terms, account, limit = 10, following = false, offset = 0) textsearch, query = generate_query_for_search(terms) if following @@ -427,10 +427,10 @@ class Account < ApplicationRecord AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC - LIMIT ? + LIMIT ? OFFSET ? SQL - records = find_by_sql([sql, account.id, account.id, account.id, limit]) + records = find_by_sql([sql, account.id, account.id, account.id, limit, offset]) else sql = <<-SQL.squish SELECT @@ -443,10 +443,10 @@ class Account < ApplicationRecord AND accounts.moved_to_account_id IS NULL GROUP BY accounts.id ORDER BY rank DESC - LIMIT ? + LIMIT ? OFFSET ? SQL - records = find_by_sql([sql, account.id, account.id, limit]) + records = find_by_sql([sql, account.id, account.id, limit, offset]) end ActiveRecord::Associations::Preloader.new.preload(records, :account_stat) diff --git a/app/models/tag.rb b/app/models/tag.rb index 4373e967b..788a678bd 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -64,9 +64,13 @@ class Tag < ApplicationRecord end class << self - def search_for(term, limit = 5) + def search_for(term, limit = 5, offset = 0) pattern = sanitize_sql_like(term.strip) + '%' - Tag.where('lower(name) like lower(?)', pattern).order(:name).limit(limit) + + Tag.where('lower(name) like lower(?)', pattern) + .order(:name) + .limit(limit) + .offset(offset) end end diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 7edbd9b47..7bdffbbd2 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true class AccountSearchService < BaseService - attr_reader :query, :limit, :options, :account + attr_reader :query, :limit, :offset, :options, :account - def call(query, limit, account = nil, options = {}) + def call(query, account = nil, options = {}) @query = query.strip - @limit = limit + @limit = options[:limit].to_i + @offset = options[:offset].to_i @options = options @account = account @@ -83,11 +84,11 @@ class AccountSearchService < BaseService end def advanced_search_results - Account.advanced_search_for(terms_for_query, account, limit, options[:following]) + Account.advanced_search_for(terms_for_query, account, limit, options[:following], offset) end def simple_search_results - Account.search_for(terms_for_query, limit) + Account.search_for(terms_for_query, limit, offset) end def terms_for_query diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 1c31e0509..e0da61dac 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true class SearchService < BaseService - attr_accessor :query, :account, :limit, :resolve - - def call(query, limit, resolve = false, account = nil) + def call(query, account, limit, options = {}) @query = query.strip @account = account - @limit = limit - @resolve = resolve + @options = options + @limit = limit.to_i + @offset = options[:type].blank? ? 0 : options[:offset].to_i + @resolve = options[:resolve] || false default_results.tap do |results| if url_query? results.merge!(url_resource_results) unless url_resource.nil? - elsif query.present? + elsif @query.present? results[:accounts] = perform_accounts_search! if account_searchable? results[:statuses] = perform_statuses_search! if full_text_searchable? results[:hashtags] = perform_hashtags_search! if hashtag_searchable? @@ -23,23 +23,46 @@ class SearchService < BaseService private def perform_accounts_search! - AccountSearchService.new.call(query, limit, account, resolve: resolve) + AccountSearchService.new.call( + @query, + @account, + limit: @limit, + resolve: @resolve, + offset: @offset + ) end def perform_statuses_search! - statuses = StatusesIndex.filter(term: { searchable_by: account.id }) - .query(multi_match: { type: 'most_fields', query: query, operator: 'and', fields: %w(text text.stemmed) }) - .limit(limit) - .objects - .compact + definition = StatusesIndex.filter(term: { searchable_by: @account.id }) + .query(multi_match: { type: 'most_fields', query: @query, operator: 'and', fields: %w(text text.stemmed) }) + + if @options[:account_id].present? + definition = definition.filter(term: { account_id: @options[:account_id] }) + end + + if @options[:min_id].present? || @options[:max_id].present? + range = {} + range[:gt] = @options[:min_id].to_i if @options[:min_id].present? + range[:lt] = @options[:max_id].to_i if @options[:max_id].present? + definition = definition.filter(range: { id: range }) + end + + results = definition.limit(@limit).offset(@offset).objects.compact + account_ids = results.map(&:account_id) + account_domains = results.map(&:account_domain) + preloaded_relations = relations_map_for_account(@account, account_ids, account_domains) - statuses.reject { |status| StatusFilter.new(status, account).filtered? } + results.reject { |status| StatusFilter.new(status, @account, preloaded_relations).filtered? } rescue Faraday::ConnectionFailed [] end def perform_hashtags_search! - Tag.search_for(query.gsub(/\A#/, ''), limit) + Tag.search_for( + @query.gsub(/\A#/, ''), + @limit, + @offset + ) end def default_results @@ -47,7 +70,7 @@ class SearchService < BaseService end def url_query? - query =~ /\Ahttps?:\/\// + @options[:type].blank? && @query =~ /\Ahttps?:\/\// end def url_resource_results @@ -55,7 +78,7 @@ class SearchService < BaseService end def url_resource - @_url_resource ||= ResolveURLService.new.call(query, on_behalf_of: @account) + @_url_resource ||= ResolveURLService.new.call(@query, on_behalf_of: @account) end def url_resource_symbol @@ -64,14 +87,37 @@ class SearchService < BaseService def full_text_searchable? return false unless Chewy.enabled? - !account.nil? && !((query.start_with?('#') || query.include?('@')) && !query.include?(' ')) + + statuses_search? && !@account.nil? && !((@query.start_with?('#') || @query.include?('@')) && !@query.include?(' ')) end def account_searchable? - !(query.include?('@') && query.include?(' ')) + account_search? && !(@query.include?('@') && @query.include?(' ')) end def hashtag_searchable? - !query.include?('@') + hashtag_search? && !@query.include?('@') + end + + def account_search? + @options[:type].blank? || @options[:type] == 'accounts' + end + + def hashtag_search? + @options[:type].blank? || @options[:type] == 'hashtags' + end + + def statuses_search? + @options[:type].blank? || @options[:type] == 'statuses' + end + + def relations_map_for_account(account, account_ids, domains) + { + blocking: Account.blocking_map(account_ids, account.id), + blocked_by: Account.blocked_by_map(account_ids, account.id), + muting: Account.muting_map(account_ids, account.id), + following: Account.following_map(account_ids, account.id), + domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id), + } end end |