summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--lib/pleroma/user/search.ex67
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex6
-rw-r--r--priv/repo/migrations/20200925065249_make_user_ids_ci.exs19
-rw-r--r--test/emoji_test.exs2
-rw-r--r--test/user_search_test.exs34
-rw-r--r--test/utils_test.exs2
-rw-r--r--test/web/activity_pub/transmogrifier/question_handling_test.exs4
-rw-r--r--test/web/common_api/common_api_test.exs17
9 files changed, 141 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e9aae736..80fbc078d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
+- Search: Users are now findable by their urls.
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
- The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false.
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index b8c648672..03f2c552f 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -3,8 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Search do
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType
alias Pleroma.Pagination
alias Pleroma.User
+
import Ecto.Query
@limit 20
@@ -19,16 +21,46 @@ defmodule Pleroma.User.Search do
query_string = format_query(query_string)
- maybe_resolve(resolve, for_user, query_string)
+ # If this returns anything, it should bounce to the top
+ maybe_resolved = maybe_resolve(resolve, for_user, query_string)
+
+ top_user_ids =
+ []
+ |> maybe_add_resolved(maybe_resolved)
+ |> maybe_add_ap_id_match(query_string)
+ |> maybe_add_uri_match(query_string)
results =
query_string
- |> search_query(for_user, following)
+ |> search_query(for_user, following, top_user_ids)
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
results
end
+ defp maybe_add_resolved(list, {:ok, %User{} = user}) do
+ [user.id | list]
+ end
+
+ defp maybe_add_resolved(list, _), do: list
+
+ defp maybe_add_ap_id_match(list, query) do
+ if user = User.get_cached_by_ap_id(query) do
+ [user.id | list]
+ else
+ list
+ end
+ end
+
+ defp maybe_add_uri_match(list, query) do
+ with {:ok, query} <- UriType.cast(query),
+ %User{} = user <- Pleroma.Repo.get_by(User, uri: query) do
+ [user.id | list]
+ else
+ _ -> list
+ end
+ end
+
defp format_query(query_string) do
# Strip the beginning @ off if there is a query
query_string = String.trim_leading(query_string, "@")
@@ -47,7 +79,7 @@ defmodule Pleroma.User.Search do
end
end
- defp search_query(query_string, for_user, following) do
+ defp search_query(query_string, for_user, following, top_user_ids) do
for_user
|> base_query(following)
|> filter_blocked_user(for_user)
@@ -56,13 +88,20 @@ defmodule Pleroma.User.Search do
|> filter_internal_users()
|> filter_blocked_domains(for_user)
|> fts_search(query_string)
+ |> select_top_users(top_user_ids)
|> trigram_rank(query_string)
- |> boost_search_rank(for_user)
+ |> boost_search_rank(for_user, top_user_ids)
|> subquery()
|> order_by(desc: :search_rank)
|> maybe_restrict_local(for_user)
end
+ defp select_top_users(query, top_user_ids) do
+ from(u in query,
+ or_where: u.id in ^top_user_ids
+ )
+ end
+
defp fts_search(query, query_string) do
query_string = to_tsquery(query_string)
@@ -180,7 +219,7 @@ defmodule Pleroma.User.Search do
defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
- defp boost_search_rank(query, %User{} = for_user) do
+ defp boost_search_rank(query, %User{} = for_user, top_user_ids) do
friends_ids = User.get_friends_ids(for_user)
followers_ids = User.get_followers_ids(for_user)
@@ -192,6 +231,7 @@ defmodule Pleroma.User.Search do
CASE WHEN (?) THEN (?) * 1.5
WHEN (?) THEN (?) * 1.3
WHEN (?) THEN (?) * 1.1
+ WHEN (?) THEN 9001
ELSE (?) END
""",
u.id in ^friends_ids and u.id in ^followers_ids,
@@ -200,11 +240,26 @@ defmodule Pleroma.User.Search do
u.search_rank,
u.id in ^followers_ids,
u.search_rank,
+ u.id in ^top_user_ids,
u.search_rank
)
}
)
end
- defp boost_search_rank(query, _for_user), do: query
+ defp boost_search_rank(query, _for_user, top_user_ids) do
+ from(u in subquery(query),
+ select_merge: %{
+ search_rank:
+ fragment(
+ """
+ CASE WHEN (?) THEN 9001
+ ELSE (?) END
+ """,
+ u.id in ^top_user_ids,
+ u.search_rank
+ )
+ }
+ )
+ end
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index aa6a69463..d7dd9fe6b 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -515,15 +515,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => objtype}} = data,
+ %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
_options
)
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
data = Map.put(data, "object", strip_internal_fields(data["object"]))
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ nil <- Activity.get_create_by_object_ap_id(obj_id),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
+ else
+ %Activity{} = activity -> {:ok, activity}
+ e -> e
end
end
diff --git a/priv/repo/migrations/20200925065249_make_user_ids_ci.exs b/priv/repo/migrations/20200925065249_make_user_ids_ci.exs
new file mode 100644
index 000000000..b7305f137
--- /dev/null
+++ b/priv/repo/migrations/20200925065249_make_user_ids_ci.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.MakeUserIdsCI do
+ use Ecto.Migration
+
+ def up do
+ alter table(:users) do
+ modify(:uri, :citext)
+ end
+
+ create(unique_index(:users, :uri))
+ end
+
+ def don do
+ drop(unique_index(:users, :uri))
+
+ alter table(:users) do
+ modify(:uri, :text)
+ end
+ end
+end
diff --git a/test/emoji_test.exs b/test/emoji_test.exs
index b36047578..1dd3c58c6 100644
--- a/test/emoji_test.exs
+++ b/test/emoji_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EmojiTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case
alias Pleroma.Emoji
describe "is_unicode_emoji?/1" do
diff --git a/test/user_search_test.exs b/test/user_search_test.exs
index 8529ce6db..b99a77b57 100644
--- a/test/user_search_test.exs
+++ b/test/user_search_test.exs
@@ -17,6 +17,40 @@ defmodule Pleroma.UserSearchTest do
describe "User.search" do
setup do: clear_config([:instance, :limit_to_local_content])
+ test "returns a resolved user as the first result" do
+ Pleroma.Config.put([:instance, :limit_to_local_content], false)
+ user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"})
+ _user = insert(:user, %{nickname: "com_user"})
+
+ [first_user, _second_user] = User.search("https://lain.com/users/lain", resolve: true)
+
+ assert first_user.id == user.id
+ end
+
+ test "returns a user with matching ap_id as the first result" do
+ user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"})
+ _user = insert(:user, %{nickname: "com_user"})
+
+ [first_user, _second_user] = User.search("https://lain.com/users/lain")
+
+ assert first_user.id == user.id
+ end
+
+ test "returns a user with matching uri as the first result" do
+ user =
+ insert(:user, %{
+ nickname: "no_relation",
+ ap_id: "https://lain.com/users/lain",
+ uri: "https://lain.com/@Lain"
+ })
+
+ _user = insert(:user, %{nickname: "com_user"})
+
+ [first_user, _second_user] = User.search("https://lain.com/@lain")
+
+ assert first_user.id == user.id
+ end
+
test "excludes invisible users from results" do
user = insert(:user, %{nickname: "john t1000"})
insert(:user, %{invisible: true, nickname: "john t800"})
diff --git a/test/utils_test.exs b/test/utils_test.exs
index 3a730d545..460f7e0b5 100644
--- a/test/utils_test.exs
+++ b/test/utils_test.exs
@@ -8,7 +8,7 @@ defmodule Pleroma.UtilsTest do
describe "tmp_dir/1" do
test "returns unique temporary directory" do
{:ok, path} = Pleroma.Utils.tmp_dir("emoji")
- assert path =~ ~r/\/tmp\/emoji-(.*)-#{:os.getpid()}-(.*)/
+ assert path =~ ~r/\/emoji-(.*)-#{:os.getpid()}-(.*)/
File.rm_rf(path)
end
end
diff --git a/test/web/activity_pub/transmogrifier/question_handling_test.exs b/test/web/activity_pub/transmogrifier/question_handling_test.exs
index 74ee79543..d2822ce75 100644
--- a/test/web/activity_pub/transmogrifier/question_handling_test.exs
+++ b/test/web/activity_pub/transmogrifier/question_handling_test.exs
@@ -157,12 +157,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do
}
end
- test "returns an error if received a second time" do
+ test "returns same activity if received a second time" do
data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
assert {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
- assert {:error, {:validate_object, {:error, _}}} = Transmogrifier.handle_incoming(data)
+ assert {:ok, ^activity} = Transmogrifier.handle_incoming(data)
end
test "accepts a Question with no content" do
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 2eab64e8b..e34f5a49b 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -29,6 +29,23 @@ defmodule Pleroma.Web.CommonAPITest do
setup do: clear_config([:instance, :limit])
setup do: clear_config([:instance, :max_pinned_statuses])
+ describe "posting polls" do
+ test "it posts a poll" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status: "who is the best",
+ poll: %{expires_in: 600, options: ["reimu", "marisa"]}
+ })
+
+ object = Object.normalize(activity)
+
+ assert object.data["type"] == "Question"
+ assert object.data["oneOf"] |> length() == 2
+ end
+ end
+
describe "blocking" do
setup do
blocker = insert(:user)