2020-01-26 20:11:16 +00:00
|
|
|
defmodule Mobilizon.GraphQL.API.Search do
|
2019-02-22 13:18:52 +00:00
|
|
|
@moduledoc """
|
2019-09-08 22:52:49 +00:00
|
|
|
API for search.
|
2019-02-22 13:18:52 +00:00
|
|
|
"""
|
2019-09-08 22:52:49 +00:00
|
|
|
|
2019-02-21 17:11:49 +00:00
|
|
|
alias Mobilizon.Actors
|
2022-04-18 12:38:57 +00:00
|
|
|
alias Mobilizon.Actors.Actor
|
2019-02-21 17:11:49 +00:00
|
|
|
alias Mobilizon.Events
|
2020-10-02 14:18:11 +00:00
|
|
|
alias Mobilizon.Events.Event
|
2020-01-22 01:14:42 +00:00
|
|
|
alias Mobilizon.Federation.ActivityPub
|
2021-04-22 10:17:56 +00:00
|
|
|
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
2022-08-26 14:08:58 +00:00
|
|
|
alias Mobilizon.Service.GlobalSearch
|
|
|
|
alias Mobilizon.Storage.Page
|
2021-11-08 17:46:04 +00:00
|
|
|
import Mobilizon.GraphQL.Resolvers.Event.Utils
|
2020-01-22 01:14:42 +00:00
|
|
|
|
2019-02-21 17:11:49 +00:00
|
|
|
require Logger
|
|
|
|
|
|
|
|
@doc """
|
2019-09-08 22:52:49 +00:00
|
|
|
Searches actors.
|
2019-02-21 17:11:49 +00:00
|
|
|
"""
|
2022-04-18 12:38:57 +00:00
|
|
|
@spec search_actors(map(), integer | nil, integer | nil, atom()) ::
|
2022-04-07 16:37:44 +00:00
|
|
|
{:ok, Page.t(Actor.t())} | {:error, String.t()}
|
2020-08-05 14:44:08 +00:00
|
|
|
def search_actors(%{term: term} = args, page \\ 1, limit \\ 10, result_type) do
|
|
|
|
term = String.trim(term)
|
2019-02-21 17:11:49 +00:00
|
|
|
|
2019-04-12 13:04:32 +00:00
|
|
|
cond do
|
2019-09-08 22:52:49 +00:00
|
|
|
# Some URLs could be domain.tld/@username, so keep this condition above
|
2024-01-04 12:35:02 +00:00
|
|
|
# the `handle?` function
|
|
|
|
url?(term) ->
|
2019-09-08 22:52:49 +00:00
|
|
|
# skip, if it's not an actor
|
2020-08-05 14:44:08 +00:00
|
|
|
case process_from_url(term) do
|
2020-10-02 14:18:11 +00:00
|
|
|
%Page{total: _total, elements: [%Actor{} = _actor]} = page ->
|
2019-09-08 22:52:49 +00:00
|
|
|
{:ok, page}
|
2019-07-23 16:06:22 +00:00
|
|
|
|
2019-04-12 13:04:32 +00:00
|
|
|
_ ->
|
|
|
|
{:ok, %{total: 0, elements: []}}
|
|
|
|
end
|
|
|
|
|
2024-01-04 12:35:02 +00:00
|
|
|
handle?(term) ->
|
2020-08-05 14:44:08 +00:00
|
|
|
{:ok, process_from_username(term)}
|
2019-04-12 13:04:32 +00:00
|
|
|
|
|
|
|
true ->
|
2024-01-04 12:35:02 +00:00
|
|
|
if global_search?(args) do
|
2022-08-26 14:08:58 +00:00
|
|
|
service = GlobalSearch.service()
|
|
|
|
|
|
|
|
{:ok, service.search_groups(Keyword.new(args, fn {k, v} -> {k, v} end))}
|
|
|
|
else
|
|
|
|
page =
|
|
|
|
Actors.search_actors(
|
|
|
|
term,
|
|
|
|
[
|
|
|
|
actor_type: result_type,
|
|
|
|
radius: Map.get(args, :radius),
|
|
|
|
location: Map.get(args, :location),
|
2022-09-01 08:00:17 +00:00
|
|
|
bbox: Map.get(args, :bbox),
|
2022-08-26 14:08:58 +00:00
|
|
|
minimum_visibility: Map.get(args, :minimum_visibility, :public),
|
|
|
|
current_actor_id: Map.get(args, :current_actor_id),
|
|
|
|
exclude_my_groups: Map.get(args, :exclude_my_groups, false),
|
2024-02-09 09:54:00 +00:00
|
|
|
exclude_stale_actors: true,
|
2024-10-24 17:03:55 +00:00
|
|
|
local_only: Map.get(args, :search_target, :internal) == :self,
|
|
|
|
sort_by: Map.get(args, :sort_by)
|
2022-08-26 14:08:58 +00:00
|
|
|
],
|
|
|
|
page,
|
|
|
|
limit
|
|
|
|
)
|
|
|
|
|
|
|
|
{:ok, page}
|
|
|
|
end
|
2019-04-12 13:04:32 +00:00
|
|
|
end
|
2019-02-21 17:11:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
@doc """
|
2019-04-12 13:04:32 +00:00
|
|
|
Search events
|
2019-02-21 17:11:49 +00:00
|
|
|
"""
|
2021-09-24 14:46:42 +00:00
|
|
|
@spec search_events(map(), integer | nil, integer | nil) ::
|
2022-04-07 16:37:44 +00:00
|
|
|
{:ok, Page.t(Event.t())}
|
2020-07-31 15:52:26 +00:00
|
|
|
def search_events(%{term: term} = args, page \\ 1, limit \\ 10) do
|
|
|
|
term = String.trim(term)
|
|
|
|
|
2024-01-04 12:35:02 +00:00
|
|
|
if url?(term) do
|
2021-11-08 17:46:04 +00:00
|
|
|
# skip, if it's not an event
|
2020-07-31 15:52:26 +00:00
|
|
|
case process_from_url(term) do
|
2021-11-08 17:46:04 +00:00
|
|
|
%Page{total: _total, elements: [%Event{} = event]} = page ->
|
|
|
|
if Map.get(args, :current_user) != nil || check_event_access?(event) do
|
|
|
|
{:ok, page}
|
|
|
|
else
|
|
|
|
{:ok, %{total: 0, elements: []}}
|
|
|
|
end
|
2020-07-31 15:52:26 +00:00
|
|
|
|
|
|
|
_ ->
|
|
|
|
{:ok, %{total: 0, elements: []}}
|
|
|
|
end
|
|
|
|
else
|
2024-01-04 12:35:02 +00:00
|
|
|
if global_search?(args) do
|
2022-08-26 14:08:58 +00:00
|
|
|
service = GlobalSearch.service()
|
|
|
|
|
|
|
|
{:ok, service.search_events(Keyword.new(args, fn {k, v} -> {k, v} end))}
|
|
|
|
else
|
2024-02-09 09:54:00 +00:00
|
|
|
results =
|
|
|
|
args
|
|
|
|
|> Map.put(:term, term)
|
|
|
|
|> Map.put(:local_only, Map.get(args, :search_target, :internal) == :self)
|
|
|
|
|> Events.build_events_for_search(page, limit)
|
|
|
|
|
|
|
|
{:ok, results}
|
2022-08-26 14:08:58 +00:00
|
|
|
end
|
2019-02-21 17:11:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-09-24 14:46:42 +00:00
|
|
|
@spec interact(String.t()) :: {:ok, struct()} | {:error, :not_found}
|
2020-11-06 10:34:32 +00:00
|
|
|
def interact(uri) do
|
|
|
|
case ActivityPub.fetch_object_from_url(uri) do
|
|
|
|
{:ok, object} ->
|
|
|
|
{:ok, object}
|
|
|
|
|
|
|
|
{:error, _err} ->
|
|
|
|
Logger.debug(fn -> "Unable to find or make object from URI '#{uri}'" end)
|
|
|
|
|
|
|
|
{:error, :not_found}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-21 17:11:49 +00:00
|
|
|
# If the search string is an username
|
2022-04-07 16:37:44 +00:00
|
|
|
@spec process_from_username(String.t()) :: Page.t(Actor.t())
|
2019-02-21 17:11:49 +00:00
|
|
|
defp process_from_username(search) do
|
2021-04-22 10:17:56 +00:00
|
|
|
case ActivityPubActor.find_or_make_actor_from_nickname(search) do
|
2023-10-17 14:41:31 +00:00
|
|
|
{:ok, %Actor{} = actor} ->
|
2019-09-08 22:52:49 +00:00
|
|
|
%Page{total: 1, elements: [actor]}
|
2019-07-23 16:06:22 +00:00
|
|
|
|
2019-02-21 17:11:49 +00:00
|
|
|
{:error, _err} ->
|
2019-02-22 13:18:52 +00:00
|
|
|
Logger.debug(fn -> "Unable to find or make actor '#{search}'" end)
|
2019-09-08 22:52:49 +00:00
|
|
|
|
|
|
|
%Page{total: 0, elements: []}
|
2019-02-21 17:11:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# If the search string is an URL
|
2022-04-07 16:37:44 +00:00
|
|
|
@spec process_from_url(String.t()) :: Page.t(struct())
|
2019-02-21 17:11:49 +00:00
|
|
|
defp process_from_url(search) do
|
2019-07-23 16:06:22 +00:00
|
|
|
case ActivityPub.fetch_object_from_url(search) do
|
|
|
|
{:ok, object} ->
|
2019-09-08 22:52:49 +00:00
|
|
|
%Page{total: 1, elements: [object]}
|
2019-07-23 16:06:22 +00:00
|
|
|
|
2019-02-21 17:11:49 +00:00
|
|
|
{:error, _err} ->
|
2019-02-22 13:18:52 +00:00
|
|
|
Logger.debug(fn -> "Unable to find or make object from URL '#{search}'" end)
|
2019-09-08 22:52:49 +00:00
|
|
|
|
|
|
|
%Page{total: 0, elements: []}
|
2019-02-21 17:11:49 +00:00
|
|
|
end
|
|
|
|
end
|
2019-04-12 13:04:32 +00:00
|
|
|
|
2024-01-04 12:35:02 +00:00
|
|
|
@spec url?(String.t()) :: boolean
|
|
|
|
defp url?(search), do: String.starts_with?(search, ["http://", "https://"])
|
2019-04-12 13:04:32 +00:00
|
|
|
|
2024-01-04 12:35:02 +00:00
|
|
|
@spec handle?(String.t()) :: boolean
|
|
|
|
defp handle?(search), do: String.match?(search, ~r/@/)
|
2022-08-26 14:08:58 +00:00
|
|
|
|
2024-01-04 12:35:02 +00:00
|
|
|
defp global_search?(%{search_target: :global}) do
|
2022-08-26 14:08:58 +00:00
|
|
|
global_search_enabled?()
|
|
|
|
end
|
|
|
|
|
2024-01-04 12:35:02 +00:00
|
|
|
defp global_search?(_), do: global_search_enabled?() && global_search_default?()
|
2022-08-26 14:08:58 +00:00
|
|
|
|
|
|
|
defp global_search_enabled? do
|
|
|
|
Application.get_env(:mobilizon, :search) |> get_in([:global]) |> get_in([:is_enabled])
|
|
|
|
end
|
|
|
|
|
|
|
|
defp global_search_default? do
|
|
|
|
Application.get_env(:mobilizon, :search) |> get_in([:global]) |> get_in([:is_default_search])
|
|
|
|
end
|
2019-02-21 17:11:49 +00:00
|
|
|
end
|