end
def search
- @accounts = SearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account)
+ @accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account)
set_account_counters_maps(@accounts) unless @accounts.nil?
--- /dev/null
+# frozen_string_literal: true
+
+class Api::V1::SearchController < ApiController
+ respond_to :json
+
+ def index
+ @search = OpenStruct.new(SearchService.new.call(params[:q], 5, params[:resolve] == 'true', current_account))
+ end
+end
end
def search_for(terms, limit = 10)
- textsearch = '(setweight(to_tsvector(\'simple\', accounts.display_name), \'A\') || setweight(to_tsvector(\'simple\', accounts.username), \'B\') || setweight(to_tsvector(\'simple\', coalesce(accounts.domain, \'\')), \'C\'))'
- query = 'to_tsquery(\'simple\', \'\'\' \' || ? || \' \'\'\' || \':*\')'
+ textsearch = '(setweight(to_tsvector(\'simple\', accounts.display_name), \'A\') || setweight(to_tsvector(\'simple\', accounts.username), \'B\') || setweight(to_tsvector(\'simple\', coalesce(accounts.domain, \'\')), \'C\'))'
+ query = 'to_tsquery(\'simple\', \'\'\' \' || ? || \' \'\'\' || \':*\')'
sql = <<SQL
SELECT
def to_param
name
end
+
+ class << self
+ def search_for(terms, limit = 5)
+ textsearch = 'to_tsvector(\'simple\', tags.name)'
+ query = 'to_tsquery(\'simple\', \'\'\' \' || ? || \' \'\'\' || \':*\')'
+
+ sql = <<SQL
+ SELECT
+ tags.*,
+ ts_rank_cd(#{textsearch}, #{query}) AS rank
+ FROM tags
+ WHERE #{query} @@ #{textsearch}
+ ORDER BY rank DESC
+ LIMIT ?
+SQL
+
+ Tag.find_by_sql([sql, terms, terms, limit])
+ end
+ end
end
--- /dev/null
+# frozen_string_literal: true
+
+class AccountSearchService < BaseService
+ def call(query, limit, resolve = false, account = nil)
+ return [] if query.blank? || query.start_with?('#')
+
+ username, domain = query.gsub(/\A@/, '').split('@')
+ domain = nil if TagManager.instance.local_domain?(domain)
+
+ if domain.nil?
+ exact_match = Account.find_local(username)
+ results = account.nil? ? Account.search_for(username, limit) : Account.advanced_search_for(username, account, limit)
+ else
+ exact_match = Account.find_remote(username, domain)
+ results = account.nil? ? Account.search_for("#{username} #{domain}", limit) : Account.advanced_search_for("#{username} #{domain}", account, limit)
+ end
+
+ results = [exact_match] + results.reject { |a| a.id == exact_match.id } if exact_match
+
+ if resolve && !exact_match && !domain.nil?
+ results = [FollowRemoteAccountService.new.call("#{username}@#{domain}")]
+ end
+
+ results
+ end
+end
--- /dev/null
+# frozen_string_literal: true
+
+class FetchRemoteResourceService < BaseService
+ def call(url)
+ atom_url, body = FetchAtomService.new.call(url)
+
+ return nil if atom_url.nil?
+
+ xml = Nokogiri::XML(body)
+ xml.encoding = 'utf-8'
+
+ if xml.root.name == 'feed'
+ FetchRemoteAccountService.new.call(atom_url)
+ elsif xml.root.name == 'entry'
+ FetchRemoteStatusService.new.call(atom_url)
+ end
+ end
+end
class SearchService < BaseService
def call(query, limit, resolve = false, account = nil)
- return if query.blank? || query.start_with?('#')
+ return if query.blank?
- username, domain = query.gsub(/\A@/, '').split('@')
- domain = nil if TagManager.instance.local_domain?(domain)
+ results = { accounts: [], hashtags: [], statuses: [] }
- if domain.nil?
- exact_match = Account.find_local(username)
- results = account.nil? ? Account.search_for(username, limit) : Account.advanced_search_for(username, account, limit)
- else
- exact_match = Account.find_remote(username, domain)
- results = account.nil? ? Account.search_for("#{username} #{domain}", limit) : Account.advanced_search_for("#{username} #{domain}", account, limit)
- end
+ if query =~ /\Ahttps?:\/\//
+ resource = FetchRemoteResourceService.new.call(query)
- results = [exact_match] + results.reject { |a| a.id == exact_match.id } if exact_match
-
- if resolve && !exact_match && !domain.nil?
- results = [FollowRemoteAccountService.new.call("#{username}@#{domain}")]
+ results[:accounts] << resource if resource.is_a?(Account)
+ results[:statuses] << resource if resource.is_a?(Status)
+ else
+ results[:accounts] = AccountSearchService.new.call(query, limit, resolve, account)
+ results[:hashtags] = Tag.search_for(query.gsub(/\A#/, ''), limit) unless query.start_with?('@')
end
results
--- /dev/null
+object @search
+
+child accounts: :accounts do
+ extends 'api/v1/accounts/show'
+end
+
+node(:hashtags) do |search|
+ search.hashtags.map(&:name)
+end
+
+child statuses: :statuses do
+ extends 'api/v1/statuses/show'
+end
get '/timelines/public', to: 'timelines#public', as: :public_timeline
get '/timelines/tag/:id', to: 'timelines#tag', as: :hashtag_timeline
+ get '/search', to: 'search#index', as: :search
+
resources :follows, only: [:create]
resources :media, only: [:create]
resources :apps, only: [:create]