diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/pleroma/user/search.ex | 58 | ||||
| -rw-r--r-- | lib/pleroma/web/controller_helper.ex | 18 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 52 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/search_controller.ex | 79 | ||||
| -rw-r--r-- | lib/pleroma/web/router.ex | 6 | 
5 files changed, 142 insertions, 71 deletions
| diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index f88dffa7b..ed06c2ab9 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -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), diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 55706eeb8..8a753bb4f 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 46049dd24..84359eea6 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/search_controller.ex b/lib/pleroma/web/mastodon_api/search_controller.ex new file mode 100644 index 000000000..0d1e2355d --- /dev/null +++ b/lib/pleroma/web/mastodon_api/search_controller.ex @@ -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 diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 1b37d6a93..17733a77b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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 | 
