Add ability to search on Group, Person or Event
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
committed by
Thomas Citharel
parent
20a4f7244c
commit
d66bbc5414
@@ -498,12 +498,17 @@ defmodule Mobilizon.Actors do
|
||||
@doc """
|
||||
Find actors by their name or displayed name
|
||||
"""
|
||||
@spec find_actors_by_username_or_name(String.t(), integer(), integer()) :: list(Actor.t())
|
||||
def find_actors_by_username_or_name(username, page \\ nil, limit \\ nil)
|
||||
def find_actors_by_username_or_name("", _page, _limit), do: []
|
||||
@spec find_and_count_actors_by_username_or_name(
|
||||
String.t(),
|
||||
[ActorTypeEnum.t()],
|
||||
integer() | nil,
|
||||
integer() | nil
|
||||
) ::
|
||||
%{total: integer(), elements: list(Actor.t())}
|
||||
def find_and_count_actors_by_username_or_name(username, _types, page \\ nil, limit \\ nil)
|
||||
|
||||
def find_actors_by_username_or_name(username, page, limit) do
|
||||
Repo.all(
|
||||
def find_and_count_actors_by_username_or_name(username, types, page, limit) do
|
||||
query =
|
||||
from(
|
||||
a in Actor,
|
||||
where:
|
||||
@@ -515,6 +520,7 @@ defmodule Mobilizon.Actors do
|
||||
a.name,
|
||||
^username
|
||||
),
|
||||
where: a.type in ^types,
|
||||
order_by:
|
||||
fragment(
|
||||
"word_similarity(?, ?) + word_similarity(coalesce(?, ''), ?) desc",
|
||||
@@ -525,7 +531,11 @@ defmodule Mobilizon.Actors do
|
||||
)
|
||||
)
|
||||
|> paginate(page, limit)
|
||||
)
|
||||
|
||||
total = Task.async(fn -> Repo.aggregate(query, :count, :id) end)
|
||||
elements = Task.async(fn -> Repo.all(query) end)
|
||||
|
||||
%{total: Task.await(total), elements: Task.await(elements)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -246,10 +246,9 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Find events by name
|
||||
"""
|
||||
def find_events_by_name(name, page \\ nil, limit \\ nil)
|
||||
def find_events_by_name("", page, limit), do: list_events(page, limit)
|
||||
def find_and_count_events_by_name(name, page \\ nil, limit \\ nil)
|
||||
|
||||
def find_events_by_name(name, page, limit) do
|
||||
def find_and_count_events_by_name(name, page, limit) do
|
||||
name = String.trim(name)
|
||||
|
||||
query =
|
||||
@@ -271,7 +270,10 @@ defmodule Mobilizon.Events do
|
||||
)
|
||||
|> paginate(page, limit)
|
||||
|
||||
Repo.all(query)
|
||||
total = Task.async(fn -> Repo.aggregate(query, :count, :id) end)
|
||||
elements = Task.async(fn -> Repo.all(query) end)
|
||||
|
||||
%{total: Task.await(total), elements: Task.await(elements)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -11,92 +11,98 @@ defmodule MobilizonWeb.API.Search do
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Search
|
||||
Search actors
|
||||
"""
|
||||
@spec search(String.t(), integer(), integer()) ::
|
||||
{:ok, list(Actor.t())} | {:ok, []} | {:error, any()}
|
||||
def search(search, page \\ 1, limit \\ 10) do
|
||||
do_search(search, page, limit, %{events: true, actors: true})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Not used at the moment
|
||||
"""
|
||||
# TODO: Use me
|
||||
@spec search_actors(String.t(), integer(), integer()) ::
|
||||
{:ok, list(Actor.t())} | {:ok, []} | {:error, any()}
|
||||
def search_actors(search, page \\ 1, limit \\ 10) do
|
||||
do_search(search, page, limit, %{actors: true})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Not used at the moment
|
||||
"""
|
||||
# TODO: Use me
|
||||
@spec search_events(String.t(), integer(), integer()) ::
|
||||
{:ok, list(Event.t())} | {:ok, []} | {:error, any()}
|
||||
def search_events(search, page \\ 1, limit \\ 10) do
|
||||
do_search(search, page, limit, %{events: true})
|
||||
end
|
||||
|
||||
# Do the actual search
|
||||
@spec do_search(String.t(), integer(), integer(), map()) :: {:ok, list(any())}
|
||||
defp do_search(search, page, limit, opts) do
|
||||
@spec search_actors(String.t(), integer(), integer(), String.t()) ::
|
||||
{:ok, %{total: integer(), elements: list(Actor.t())}} | {:error, any()}
|
||||
def search_actors(search, page \\ 1, limit \\ 10, result_type) do
|
||||
search = String.trim(search)
|
||||
|
||||
cond do
|
||||
search == "" ->
|
||||
{:error, "Search can't be empty"}
|
||||
|
||||
String.match?(search, ~r/@/) ->
|
||||
# Some URLs could be domain.tld/@username, so keep this condition above handle_search? function
|
||||
url_search?(search) ->
|
||||
# If this is not an actor, skip
|
||||
with %{:total => total, :elements => [%Actor{}] = elements} <- process_from_url(search) do
|
||||
{:ok, %{total: total, elements: elements}}
|
||||
else
|
||||
_ ->
|
||||
{:ok, %{total: 0, elements: []}}
|
||||
end
|
||||
|
||||
handle_search?(search) ->
|
||||
{:ok, process_from_username(search)}
|
||||
|
||||
String.starts_with?(search, "https://") ->
|
||||
{:ok, process_from_url(search)}
|
||||
true ->
|
||||
{:ok,
|
||||
Actors.find_and_count_actors_by_username_or_name(search, [result_type], page, limit)}
|
||||
end
|
||||
end
|
||||
|
||||
String.starts_with?(search, "http://") ->
|
||||
{:ok, process_from_url(search)}
|
||||
@doc """
|
||||
Search events
|
||||
"""
|
||||
@spec search_events(String.t(), integer(), integer()) ::
|
||||
{:ok, %{total: integer(), elements: list(Event.t())}} | {:error, any()}
|
||||
def search_events(search, page \\ 1, limit \\ 10) do
|
||||
search = String.trim(search)
|
||||
|
||||
cond do
|
||||
search == "" ->
|
||||
{:error, "Search can't be empty"}
|
||||
|
||||
url_search?(search) ->
|
||||
# If this is not an event, skip
|
||||
with {total = total, [%Event{} = elements]} <- process_from_url(search) do
|
||||
{:ok, %{total: total, elements: elements}}
|
||||
else
|
||||
_ ->
|
||||
{:ok, %{total: 0, elements: []}}
|
||||
end
|
||||
|
||||
true ->
|
||||
events =
|
||||
Task.async(fn ->
|
||||
if Map.get(opts, :events, false),
|
||||
do: Events.find_events_by_name(search, page, limit),
|
||||
else: []
|
||||
end)
|
||||
|
||||
actors =
|
||||
Task.async(fn ->
|
||||
if Map.get(opts, :actors, false),
|
||||
do: Actors.find_actors_by_username_or_name(search, page, limit),
|
||||
else: []
|
||||
end)
|
||||
|
||||
{:ok, Task.await(events) ++ Task.await(actors)}
|
||||
{:ok, Events.find_and_count_events_by_name(search, page, limit)}
|
||||
end
|
||||
end
|
||||
|
||||
# If the search string is an username
|
||||
@spec process_from_username(String.t()) :: Actor.t() | nil
|
||||
@spec process_from_username(String.t()) :: %{total: integer(), elements: [Actor.t()]}
|
||||
defp process_from_username(search) do
|
||||
with {:ok, actor} <- ActivityPub.find_or_make_actor_from_nickname(search) do
|
||||
actor
|
||||
%{total: 1, elements: [actor]}
|
||||
else
|
||||
{:error, _err} ->
|
||||
Logger.debug(fn -> "Unable to find or make actor '#{search}'" end)
|
||||
nil
|
||||
%{total: 0, elements: []}
|
||||
end
|
||||
end
|
||||
|
||||
# If the search string is an URL
|
||||
@spec process_from_url(String.t()) :: Actor.t() | Event.t() | Comment.t() | nil
|
||||
@spec process_from_url(String.t()) :: %{
|
||||
total: integer(),
|
||||
elements: [Actor.t() | Event.t() | Comment.t()]
|
||||
}
|
||||
defp process_from_url(search) do
|
||||
with {:ok, object} <- ActivityPub.fetch_object_from_url(search) do
|
||||
object
|
||||
%{total: 1, elements: [object]}
|
||||
else
|
||||
{:error, _err} ->
|
||||
Logger.debug(fn -> "Unable to find or make object from URL '#{search}'" end)
|
||||
nil
|
||||
%{total: 0, elements: []}
|
||||
end
|
||||
end
|
||||
|
||||
# Is the search an URL search?
|
||||
@spec url_search?(String.t()) :: boolean
|
||||
defp url_search?(search) do
|
||||
String.starts_with?(search, "https://") or String.starts_with?(search, "http://")
|
||||
end
|
||||
|
||||
# Is the search an handle search?
|
||||
@spec handle_search?(String.t()) :: boolean
|
||||
defp handle_search?(search) do
|
||||
String.match?(search, ~r/@/)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,9 +5,23 @@ defmodule MobilizonWeb.Resolvers.Search do
|
||||
alias MobilizonWeb.API.Search
|
||||
|
||||
@doc """
|
||||
Search events and actors by title
|
||||
Search persons
|
||||
"""
|
||||
def search_events_and_actors(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||
Search.search(search, page, limit)
|
||||
def search_persons(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||
Search.search_actors(search, page, limit, :Person)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Search groups
|
||||
"""
|
||||
def search_groups(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||
Search.search_actors(search, page, limit, :Group)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Search events
|
||||
"""
|
||||
def search_events(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||
Search.search_events(search, page, limit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,10 +18,9 @@ defmodule MobilizonWeb.Schema do
|
||||
import_types(MobilizonWeb.Schema.Actors.PersonType)
|
||||
import_types(MobilizonWeb.Schema.Actors.GroupType)
|
||||
import_types(MobilizonWeb.Schema.CommentType)
|
||||
import_types(MobilizonWeb.Schema.SearchType)
|
||||
import_types(MobilizonWeb.Schema.ConfigType)
|
||||
|
||||
alias MobilizonWeb.Resolvers
|
||||
|
||||
@desc "A struct containing the id of the deleted object"
|
||||
object :deleted_object do
|
||||
field(:id, :integer)
|
||||
@@ -85,22 +84,6 @@ defmodule MobilizonWeb.Schema do
|
||||
end)
|
||||
end
|
||||
|
||||
@desc "A search result"
|
||||
union :search_result do
|
||||
types([:event, :person, :group])
|
||||
|
||||
resolve_type(fn
|
||||
%Actor{type: :Person}, _ ->
|
||||
:person
|
||||
|
||||
%Actor{type: :Group}, _ ->
|
||||
:group
|
||||
|
||||
%Event{}, _ ->
|
||||
:event
|
||||
end)
|
||||
end
|
||||
|
||||
def context(ctx) do
|
||||
loader =
|
||||
Dataloader.new()
|
||||
@@ -120,14 +103,7 @@ defmodule MobilizonWeb.Schema do
|
||||
Root Query
|
||||
"""
|
||||
query do
|
||||
@desc "Search through events, persons and groups"
|
||||
field :search, list_of(:search_result) do
|
||||
arg(:search, non_null(:string))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Resolvers.Search.search_events_and_actors/3)
|
||||
end
|
||||
|
||||
import_fields(:search_queries)
|
||||
import_fields(:user_queries)
|
||||
import_fields(:person_queries)
|
||||
import_fields(:group_queries)
|
||||
|
||||
55
lib/mobilizon_web/schema/search.ex
Normal file
55
lib/mobilizon_web/schema/search.ex
Normal file
@@ -0,0 +1,55 @@
|
||||
defmodule MobilizonWeb.Schema.SearchType do
|
||||
@moduledoc """
|
||||
Schema representation for Search
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias MobilizonWeb.Resolvers
|
||||
|
||||
@desc "Search persons result"
|
||||
object :persons do
|
||||
field(:total, non_null(:integer), description: "Total elements")
|
||||
field(:elements, non_null(list_of(:person)), description: "Person elements")
|
||||
end
|
||||
|
||||
@desc "Search groups result"
|
||||
object :groups do
|
||||
field(:total, non_null(:integer), description: "Total elements")
|
||||
field(:elements, non_null(list_of(:group)), description: "Group elements")
|
||||
end
|
||||
|
||||
@desc "Search events result"
|
||||
object :events do
|
||||
field(:total, non_null(:integer), description: "Total elements")
|
||||
field(:elements, non_null(list_of(:event)), description: "Event elements")
|
||||
end
|
||||
|
||||
object :search_queries do
|
||||
@desc "Search persons"
|
||||
field :search_persons, :persons do
|
||||
arg(:search, non_null(:string))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
resolve(&Resolvers.Search.search_persons/3)
|
||||
end
|
||||
|
||||
@desc "Search groups"
|
||||
field :search_groups, :groups do
|
||||
arg(:search, non_null(:string))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
resolve(&Resolvers.Search.search_groups/3)
|
||||
end
|
||||
|
||||
@desc "Search events"
|
||||
field :search_events, :events do
|
||||
arg(:search, non_null(:string))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
|
||||
resolve(&Resolvers.Search.search_events/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user