aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/user.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/user.ex')
-rw-r--r--lib/pleroma/user.ex179
1 files changed, 150 insertions, 29 deletions
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 681280539..18137106e 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -35,7 +35,7 @@ defmodule Pleroma.User do
field(:avatar, :map)
field(:local, :boolean, default: true)
field(:follower_address, :string)
- field(:search_distance, :float, virtual: true)
+ field(:search_rank, :float, virtual: true)
field(:tags, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime)
has_many(:notifications, Notification)
@@ -447,8 +447,7 @@ defmodule Pleroma.User do
def get_by_nickname(nickname) do
Repo.get_by(User, nickname: nickname) ||
if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
- [local_nickname, _] = String.split(nickname, "@")
- Repo.get_by(User, nickname: local_nickname)
+ Repo.get_by(User, nickname: local_nickname(nickname))
end
end
@@ -511,6 +510,12 @@ defmodule Pleroma.User do
{:ok, Repo.all(q)}
end
+ def get_followers_ids(user, page \\ nil) do
+ q = get_followers_query(user, page)
+
+ Repo.all(from(u in q, select: u.id))
+ end
+
def get_friends_query(%User{id: id, following: following}, nil) do
from(
u in User,
@@ -535,6 +540,12 @@ defmodule Pleroma.User do
{:ok, Repo.all(q)}
end
+ def get_friends_ids(user, page \\ nil) do
+ q = get_friends_query(user, page)
+
+ Repo.all(from(u in q, select: u.id))
+ end
+
def get_follow_requests_query(%User{} = user) do
from(
a in Activity,
@@ -666,37 +677,120 @@ defmodule Pleroma.User do
Repo.all(query)
end
- def search(query, resolve \\ false) do
- # strip the beginning @ off if there is a query
+ def search(query, resolve \\ false, for_user \\ nil) do
+ # Strip the beginning @ off if there is a query
query = String.trim_leading(query, "@")
- if resolve do
- User.get_or_fetch_by_nickname(query)
- end
+ if resolve, do: User.get_or_fetch_by_nickname(query)
- inner =
- from(
- u in User,
- select_merge: %{
- search_distance:
- fragment(
- "? <-> (? || coalesce(?, ''))",
- ^query,
- u.nickname,
- u.name
- )
- },
- where: not is_nil(u.nickname)
- )
+ fts_results = do_search(fts_search_subquery(query), for_user)
+ {:ok, trigram_results} =
+ Repo.transaction(fn ->
+ Ecto.Adapters.SQL.query(Repo, "select set_limit(0.25)", [])
+ do_search(trigram_search_subquery(query), for_user)
+ end)
+
+ Enum.uniq_by(fts_results ++ trigram_results, & &1.id)
+ end
+
+ defp do_search(subquery, for_user, options \\ []) do
q =
from(
- s in subquery(inner),
- order_by: s.search_distance,
- limit: 20
+ s in subquery(subquery),
+ order_by: [desc: s.search_rank],
+ limit: ^(options[:limit] || 20)
)
- Repo.all(q)
+ results =
+ q
+ |> Repo.all()
+ |> Enum.filter(&(&1.search_rank > 0))
+
+ boost_search_results(results, for_user)
+ end
+
+ defp fts_search_subquery(query) do
+ processed_query =
+ query
+ |> String.replace(~r/\W+/, " ")
+ |> String.trim()
+ |> String.split()
+ |> Enum.map(&(&1 <> ":*"))
+ |> Enum.join(" | ")
+
+ from(
+ u in User,
+ select_merge: %{
+ search_rank:
+ fragment(
+ """
+ ts_rank_cd(
+ setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
+ setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B'),
+ to_tsquery('simple', ?),
+ 32
+ )
+ """,
+ u.nickname,
+ u.name,
+ ^processed_query
+ )
+ },
+ where:
+ fragment(
+ """
+ (setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
+ setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')) @@ to_tsquery('simple', ?)
+ """,
+ u.nickname,
+ u.name,
+ ^processed_query
+ )
+ )
+ end
+
+ defp trigram_search_subquery(query) do
+ from(
+ u in User,
+ select_merge: %{
+ search_rank:
+ fragment(
+ "similarity(?, trim(? || ' ' || coalesce(?, '')))",
+ ^query,
+ u.nickname,
+ u.name
+ )
+ },
+ where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^query)
+ )
+ end
+
+ defp boost_search_results(results, nil), do: results
+
+ defp boost_search_results(results, for_user) do
+ friends_ids = get_friends_ids(for_user)
+ followers_ids = get_followers_ids(for_user)
+
+ Enum.map(
+ results,
+ fn u ->
+ search_rank_coef =
+ cond do
+ u.id in friends_ids ->
+ 1.2
+
+ u.id in followers_ids ->
+ 1.1
+
+ true ->
+ 1
+ end
+
+ Map.put(u, :search_rank, u.search_rank * search_rank_coef)
+ end
+ )
+ |> Enum.sort_by(&(-&1.search_rank))
end
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
@@ -796,7 +890,7 @@ defmodule Pleroma.User do
update_and_set_cache(cng)
end
- def local_user_query() do
+ def local_user_query do
from(
u in User,
where: u.local == true,
@@ -804,7 +898,14 @@ defmodule Pleroma.User do
)
end
- def moderator_user_query() do
+ def active_local_user_query do
+ from(
+ u in local_user_query(),
+ where: fragment("not (?->'deactivated' @> 'true')", u.info)
+ )
+ end
+
+ def moderator_user_query do
from(
u in User,
where: u.local == true,
@@ -990,7 +1091,7 @@ defmodule Pleroma.User do
end)
bio
- |> CommonUtils.format_input(mentions, tags, "text/plain")
+ |> CommonUtils.format_input(mentions, tags, "text/plain", user_links: [format: :full])
|> Formatter.emojify(emoji)
end
@@ -1040,4 +1141,24 @@ defmodule Pleroma.User do
@strict_local_nickname_regex
end
end
+
+ def local_nickname(nickname_or_mention) do
+ nickname_or_mention
+ |> full_nickname()
+ |> String.split("@")
+ |> hd()
+ end
+
+ def full_nickname(nickname_or_mention),
+ do: String.trim_leading(nickname_or_mention, "@")
+
+ def error_user(ap_id) do
+ %User{
+ name: ap_id,
+ ap_id: ap_id,
+ info: %User.Info{},
+ nickname: "erroruser@example.com",
+ inserted_at: NaiveDateTime.utc_now()
+ }
+ end
end