search_service.rb 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # frozen_string_literal: true
  2. class SearchService < BaseService
  3. def call(query, account, limit, options = {})
  4. @query = query.strip
  5. @account = account
  6. @options = options
  7. @limit = limit.to_i
  8. @offset = options[:type].blank? ? 0 : options[:offset].to_i
  9. @resolve = options[:resolve] || false
  10. default_results.tap do |results|
  11. if url_query?
  12. results.merge!(url_resource_results) unless url_resource.nil?
  13. elsif @query.present?
  14. results[:accounts] = perform_accounts_search! if account_searchable?
  15. results[:statuses] = perform_statuses_search! if full_text_searchable?
  16. results[:hashtags] = perform_hashtags_search! if hashtag_searchable?
  17. end
  18. end
  19. end
  20. private
  21. def perform_accounts_search!
  22. AccountSearchService.new.call(
  23. @query,
  24. @account,
  25. limit: @limit,
  26. resolve: @resolve,
  27. offset: @offset
  28. )
  29. end
  30. def perform_statuses_search!
  31. definition = StatusesIndex.filter(term: { searchable_by: @account.id })
  32. .query(multi_match: { type: 'most_fields', query: @query, operator: 'and', fields: %w(text text.stemmed) })
  33. if @options[:account_id].present?
  34. definition = definition.filter(term: { account_id: @options[:account_id] })
  35. end
  36. if @options[:min_id].present? || @options[:max_id].present?
  37. range = {}
  38. range[:gt] = @options[:min_id].to_i if @options[:min_id].present?
  39. range[:lt] = @options[:max_id].to_i if @options[:max_id].present?
  40. definition = definition.filter(range: { id: range })
  41. end
  42. results = definition.limit(@limit).offset(@offset).objects.compact
  43. account_ids = results.map(&:account_id)
  44. account_domains = results.map(&:account_domain)
  45. preloaded_relations = relations_map_for_account(@account, account_ids, account_domains)
  46. results.reject { |status| StatusFilter.new(status, @account, preloaded_relations).filtered? }
  47. rescue Faraday::ConnectionFailed
  48. []
  49. end
  50. def perform_hashtags_search!
  51. Tag.search_for(
  52. @query.gsub(/\A#/, ''),
  53. @limit,
  54. @offset
  55. )
  56. end
  57. def default_results
  58. { accounts: [], hashtags: [], statuses: [] }
  59. end
  60. def url_query?
  61. @options[:type].blank? && @query =~ /\Ahttps?:\/\//
  62. end
  63. def url_resource_results
  64. { url_resource_symbol => [url_resource] }
  65. end
  66. def url_resource
  67. @_url_resource ||= ResolveURLService.new.call(@query, on_behalf_of: @account)
  68. end
  69. def url_resource_symbol
  70. url_resource.class.name.downcase.pluralize.to_sym
  71. end
  72. def full_text_searchable?
  73. return false unless Chewy.enabled?
  74. statuses_search? && !@account.nil? && !((@query.start_with?('#') || @query.include?('@')) && !@query.include?(' '))
  75. end
  76. def account_searchable?
  77. account_search? && !(@query.include?('@') && @query.include?(' '))
  78. end
  79. def hashtag_searchable?
  80. hashtag_search? && !@query.include?('@')
  81. end
  82. def account_search?
  83. @options[:type].blank? || @options[:type] == 'accounts'
  84. end
  85. def hashtag_search?
  86. @options[:type].blank? || @options[:type] == 'hashtags'
  87. end
  88. def statuses_search?
  89. @options[:type].blank? || @options[:type] == 'statuses'
  90. end
  91. def relations_map_for_account(account, account_ids, domains)
  92. {
  93. blocking: Account.blocking_map(account_ids, account.id),
  94. blocked_by: Account.blocked_by_map(account_ids, account.id),
  95. muting: Account.muting_map(account_ids, account.id),
  96. following: Account.following_map(account_ids, account.id),
  97. domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
  98. }
  99. end
  100. end