Commit dc45ec62 authored by Ivan Tashkinov's avatar Ivan Tashkinov
Browse files

[#477] User search improvements: tsquery search with field weights, friends & followers boosting.

parent de1da7b3
...@@ -35,7 +35,7 @@ defmodule Pleroma.User do ...@@ -35,7 +35,7 @@ defmodule Pleroma.User do
field(:avatar, :map) field(:avatar, :map)
field(:local, :boolean, default: true) field(:local, :boolean, default: true)
field(:follower_address, :string) field(:follower_address, :string)
field(:search_distance, :float, virtual: true) field(:search_rank, :float, virtual: true)
field(:tags, {:array, :string}, default: []) field(:tags, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime) field(:last_refreshed_at, :naive_datetime)
has_many(:notifications, Notification) has_many(:notifications, Notification)
...@@ -511,6 +511,12 @@ def get_followers(user, page \\ nil) do ...@@ -511,6 +511,12 @@ def get_followers(user, page \\ nil) do
{:ok, Repo.all(q)} {:ok, Repo.all(q)}
end 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 def get_friends_query(%User{id: id, following: following}, nil) do
from( from(
u in User, u in User,
...@@ -535,6 +541,12 @@ def get_friends(user, page \\ nil) do ...@@ -535,6 +541,12 @@ def get_friends(user, page \\ nil) do
{:ok, Repo.all(q)} {:ok, Repo.all(q)}
end 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 def get_follow_requests_query(%User{} = user) do
from( from(
a in Activity, a in Activity,
...@@ -666,7 +678,7 @@ def get_recipients_from_activity(%Activity{recipients: to}) do ...@@ -666,7 +678,7 @@ def get_recipients_from_activity(%Activity{recipients: to}) do
Repo.all(query) Repo.all(query)
end end
def search(query, resolve \\ false) do def search(query, resolve \\ false, for_user \\ nil) do
# strip the beginning @ off if there is a query # strip the beginning @ off if there is a query
query = String.trim_leading(query, "@") query = String.trim_leading(query, "@")
...@@ -674,16 +686,28 @@ def search(query, resolve \\ false) do ...@@ -674,16 +686,28 @@ def search(query, resolve \\ false) do
User.get_or_fetch_by_nickname(query) User.get_or_fetch_by_nickname(query)
end end
processed_query =
query
|> String.replace(~r/\W+/, " ")
|> String.trim()
|> String.split()
|> Enum.map(&(&1 <> ":*"))
|> Enum.join(" | ")
inner = inner =
from( from(
u in User, u in User,
select_merge: %{ select_merge: %{
search_distance: search_rank:
fragment( fragment(
"? <-> (? || coalesce(?, ''))", """
^query, ts_rank_cd(
u.nickname, setweight(to_tsvector('simple', regexp_replace(nickname, '\\W', ' ', 'g')), 'A') ||
u.name setweight(to_tsvector('simple', regexp_replace(coalesce(name, ''), '\\W', ' ', 'g')), 'B'),
to_tsquery('simple', ?)
)
""",
^processed_query
) )
}, },
where: not is_nil(u.nickname) where: not is_nil(u.nickname)
...@@ -692,11 +716,44 @@ def search(query, resolve \\ false) do ...@@ -692,11 +716,44 @@ def search(query, resolve \\ false) do
q = q =
from( from(
s in subquery(inner), s in subquery(inner),
order_by: s.search_distance, order_by: [desc: s.search_rank],
limit: 20 limit: 20
) )
Repo.all(q) results =
q
|> Repo.all()
|> Enum.filter(&(&1.search_rank > 0))
weighted_results =
if 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))
else
results
end
weighted_results
end end
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
......
...@@ -772,7 +772,7 @@ def status_search(user, query) do ...@@ -772,7 +772,7 @@ def status_search(user, query) do
end end
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true") accounts = User.search(query, params["resolve"] == "true", user)
statuses = status_search(user, query) statuses = status_search(user, query)
...@@ -796,7 +796,7 @@ def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do ...@@ -796,7 +796,7 @@ def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
end end
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true") accounts = User.search(query, params["resolve"] == "true", user)
statuses = status_search(user, query) statuses = status_search(user, query)
...@@ -817,7 +817,7 @@ def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do ...@@ -817,7 +817,7 @@ def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
end end
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true") accounts = User.search(query, params["resolve"] == "true", user)
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user) res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
......
...@@ -675,7 +675,7 @@ def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do ...@@ -675,7 +675,7 @@ def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
end end
def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
users = User.search(query, true) users = User.search(query, true, user)
conn conn
|> put_view(UserView) |> put_view(UserView)
......
...@@ -781,8 +781,7 @@ test "finds a user, ranking by similarity" do ...@@ -781,8 +781,7 @@ test "finds a user, ranking by similarity" do
_user_three = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"}) _user_three = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
user_four = insert(:user, %{nickname: "lain@pleroma.soykaf.com"}) user_four = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
assert user_four == assert user_four == User.search("lain@ple") |> List.first() |> Map.put(:search_rank, nil)
User.search("lain@ple") |> List.first() |> Map.put(:search_distance, nil)
end end
test "finds a user whose name is nil" do test "finds a user whose name is nil" do
...@@ -792,7 +791,7 @@ test "finds a user whose name is nil" do ...@@ -792,7 +791,7 @@ test "finds a user whose name is nil" do
assert user_two == assert user_two ==
User.search("lain@pleroma.soykaf.com") User.search("lain@pleroma.soykaf.com")
|> List.first() |> List.first()
|> Map.put(:search_distance, nil) |> Map.put(:search_rank, nil)
end end
end end
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment