[#948] /api/v1/account_search added optional parameters (limit, offset, following)

This commit is contained in:
Maksim 2019-06-14 11:39:57 +00:00 committed by lain
commit ee4ed87fb4
8 changed files with 300 additions and 181 deletions

View file

@ -7,45 +7,69 @@ defmodule Pleroma.User.Search do
alias Pleroma.User
import Ecto.Query
def search(query, opts \\ []) do
@similarity_threshold 0.25
@limit 20
def search(query_string, opts \\ []) do
resolve = Keyword.get(opts, :resolve, false)
following = Keyword.get(opts, :following, false)
result_limit = Keyword.get(opts, :limit, @limit)
offset = Keyword.get(opts, :offset, 0)
for_user = Keyword.get(opts, :for_user)
# Strip the beginning @ off if there is a query
query = String.trim_leading(query, "@")
query_string = String.trim_leading(query_string, "@")
maybe_resolve(resolve, for_user, query)
maybe_resolve(resolve, for_user, query_string)
{:ok, results} =
Repo.transaction(fn ->
Ecto.Adapters.SQL.query(Repo, "select set_limit(0.25)", [])
Ecto.Adapters.SQL.query(
Repo,
"select set_limit(#{@similarity_threshold})",
[]
)
query
|> search_query(for_user)
query_string
|> search_query(for_user, following)
|> paginate(result_limit, offset)
|> Repo.all()
end)
results
end
defp search_query(query, for_user) do
query
|> union_query()
defp search_query(query_string, for_user, following) do
for_user
|> base_query(following)
|> search_subqueries(query_string)
|> union_subqueries
|> distinct_query()
|> boost_search_rank_query(for_user)
|> subquery()
|> order_by(desc: :search_rank)
|> limit(20)
|> maybe_restrict_local(for_user)
end
defp union_query(query) do
fts_subquery = fts_search_subquery(query)
trigram_subquery = trigram_search_subquery(query)
defp base_query(_user, false), do: User
defp base_query(user, true), do: User.get_followers_query(user)
defp paginate(query, limit, offset) do
from(q in query, limit: ^limit, offset: ^offset)
end
defp union_subqueries({fts_subquery, trigram_subquery}) do
from(s in trigram_subquery, union_all: ^fts_subquery)
end
defp search_subqueries(base_query, query_string) do
{
fts_search_subquery(base_query, query_string),
trigram_search_subquery(base_query, query_string)
}
end
defp distinct_query(q) do
from(s in subquery(q), order_by: s.search_type, distinct: s.id)
end
@ -102,7 +126,8 @@ defmodule Pleroma.User.Search do
)
end
defp fts_search_subquery(term, query \\ User) do
@spec fts_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
defp fts_search_subquery(query, term) do
processed_query =
term
|> String.replace(~r/\W+/, " ")
@ -144,9 +169,10 @@ defmodule Pleroma.User.Search do
|> User.restrict_deactivated()
end
defp trigram_search_subquery(term) do
@spec trigram_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
defp trigram_search_subquery(query, term) do
from(
u in User,
u in query,
select_merge: %{
# ^1 gives 'Postgrex expected a binary, got 1' for some weird reason
search_type: fragment("?", 1),

View file

@ -15,4 +15,22 @@ defmodule Pleroma.Web.ControllerHelper do
|> put_status(status)
|> json(json)
end
@spec fetch_integer_param(map(), String.t(), integer() | nil) :: integer() | nil
def fetch_integer_param(params, name, default \\ nil) do
params
|> Map.get(name, default)
|> param_to_integer(default)
end
defp param_to_integer(val, _) when is_integer(val), do: val
defp param_to_integer(val, default) when is_binary(val) do
case Integer.parse(val) do
{res, _} -> res
_ -> default
end
end
defp param_to_integer(_, default), do: default
end

View file

@ -1118,58 +1118,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
statuses = Activity.search(user, query)
tags_path = Web.base_url() <> "/tag/"
tags =
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
"statuses" =>
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
"hashtags" => tags
}
json(conn, res)
end
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
statuses = Activity.search(user, query)
tags =
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
"statuses" =>
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
"hashtags" => tags
}
json(conn, res)
end
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
json(conn, res)
end
def favourites(%{assigns: %{user: user}} = conn, params) do
params =
params

View file

@ -0,0 +1,79 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.ControllerHelper
require Logger
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, search_options(params, user))
statuses = Activity.search(user, query)
tags_path = Web.base_url() <> "/tag/"
tags =
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
"statuses" =>
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
"hashtags" => tags
}
json(conn, res)
end
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, search_options(params, user))
statuses = Activity.search(user, query)
tags =
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
"statuses" =>
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
"hashtags" => tags
}
json(conn, res)
end
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, search_options(params, user))
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
json(conn, res)
end
defp search_options(params, user) do
[
resolve: params["resolve"] == "true",
following: params["following"] == "true",
limit: ControllerHelper.fetch_integer_param(params, "limit"),
offset: ControllerHelper.fetch_integer_param(params, "offset"),
for_user: user
]
end
end

View file

@ -412,7 +412,7 @@ defmodule Pleroma.Web.Router do
get("/trends", MastodonAPIController, :empty_array)
get("/accounts/search", MastodonAPIController, :account_search)
get("/accounts/search", SearchController, :account_search)
scope [] do
pipe_through(:oauth_read_or_public)
@ -431,7 +431,7 @@ defmodule Pleroma.Web.Router do
get("/accounts/:id/following", MastodonAPIController, :following)
get("/accounts/:id", MastodonAPIController, :user)
get("/search", MastodonAPIController, :search)
get("/search", SearchController, :search)
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
end
@ -439,7 +439,7 @@ defmodule Pleroma.Web.Router do
scope "/api/v2", Pleroma.Web.MastodonAPI do
pipe_through([:api, :oauth_read_or_public])
get("/search", MastodonAPIController, :search2)
get("/search", SearchController, :search2)
end
scope "/api", Pleroma.Web do